@markdy/renderer-dom 1.0.10 → 1.0.12

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
@@ -77,8 +77,9 @@ diagram.destroy(); // clean up DOM + cancel animations
77
77
  | `sceneBoundaryProgress` | `boolean \| string` | `true` | Show boundary progress bar, or pass a custom color/gradient string |
78
78
  | `progressColor` | `string` | `plan.meta.progressColor ?? "rainbow"` | Custom progress bar color (e.g. `"#3b82f6"`) or gradient (e.g. `"#ec4899, #8b5cf6"`) |
79
79
  | `playbackRate` | `number` | `plan.meta.playbackRate ?? 1` | Normalized timeline speed multiplier; `1` is Markdy's normal pace |
80
- | `interactiveViewport` | `boolean` | `controls \|\| plan.meta.interactiveViewport` | Enable wheel zoom and drag pan on the rendered viewport |
81
- | `controls` | `boolean` | `plan.meta.controls ?? false` | Show left-aligned footer controls toolbar (play/pause, restart, speed, reset view); also enables viewport interaction |
80
+ | `interactiveViewport` | `boolean` | `controls \|\| player.interaction` | Enable wheel zoom and drag pan on the rendered viewport |
81
+ | `controls` | `boolean` | `player.controls` | Show the footer toolbar (play, prev/next beat, restart, seek, speed, fit, reset view, SVG, share); also enables viewport interaction |
82
+ | `shareUrl` | `string` | Markdy playground | Base URL used by the Share control when building `#code=` links |
82
83
  | `onWarning` | `(warning: Diagnostic) => void` | `console.warn` | Called for each soft parse warning |
83
84
  | `onTimeUpdate` | `(seconds: number, durationSeconds: number) => void` | — | Called whenever playback or seek changes the current time |
84
85
  | `onPlayStateChange` | `(playing: boolean) => void` | — | Called when playback starts or pauses |
@@ -0,0 +1,99 @@
1
+ // src/export/svg-exporter.ts
2
+ function copyRenderedStyles(source, clone) {
3
+ if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
4
+ const sourceElements = [source, ...Array.from(source.querySelectorAll("*"))];
5
+ const cloneElements = [clone, ...Array.from(clone.querySelectorAll("*"))];
6
+ for (let index = 0; index < Math.min(sourceElements.length, cloneElements.length); index++) {
7
+ const computed = window.getComputedStyle(sourceElements[index]);
8
+ const target = cloneElements[index].style;
9
+ for (let propertyIndex = 0; propertyIndex < computed.length; propertyIndex++) {
10
+ const property = computed.item(propertyIndex);
11
+ target.setProperty(property, computed.getPropertyValue(property), computed.getPropertyPriority(property));
12
+ }
13
+ }
14
+ }
15
+ function normalizeExportViewport(scene) {
16
+ scene.querySelectorAll(".markdy-viewport-transform").forEach((viewportTransform) => {
17
+ viewportTransform.style.transform = "translate(0px, 0px) scale(1)";
18
+ viewportTransform.style.transformOrigin = "0 0";
19
+ viewportTransform.style.willChange = "auto";
20
+ });
21
+ }
22
+ function getDiagramSceneElement(containerEl) {
23
+ const sceneEl = containerEl.classList?.contains("markdy-scene-root") ? containerEl : containerEl.querySelector(".markdy-scene-root") || containerEl.querySelector("svg") || (containerEl.tagName?.toLowerCase() === "svg" ? containerEl : null);
24
+ if (!sceneEl) throw new Error("No Markdy scene element found in container");
25
+ return sceneEl;
26
+ }
27
+ function prepareHtmlSceneForExport(sceneEl, options = {}) {
28
+ const clonedScene = sceneEl.cloneNode(true);
29
+ copyRenderedStyles(sceneEl, clonedScene);
30
+ normalizeExportViewport(clonedScene);
31
+ const widthStr = clonedScene.style.width || String(sceneEl.clientWidth || 800);
32
+ const heightStr = clonedScene.style.height || String(sceneEl.clientHeight || 400);
33
+ let width = parseFloat(widthStr);
34
+ let height = parseFloat(heightStr);
35
+ if (isNaN(width)) width = 800;
36
+ if (isNaN(height)) height = 400;
37
+ const scale = options.scale || 1;
38
+ const scaledWidth = width * scale;
39
+ const scaledHeight = height * scale;
40
+ clonedScene.style.transform = `scale(${scale})`;
41
+ clonedScene.style.transformOrigin = "0 0";
42
+ clonedScene.style.position = "relative";
43
+ clonedScene.style.left = "0px";
44
+ clonedScene.style.top = "0px";
45
+ clonedScene.style.margin = "0";
46
+ clonedScene.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
47
+ if (options.transparentBackground) {
48
+ clonedScene.style.background = "transparent";
49
+ }
50
+ return { sceneEl, clonedScene, width, height, scaledWidth, scaledHeight };
51
+ }
52
+ function exportDiagramAsVectorSvg(containerEl, options = {}) {
53
+ const sceneEl = getDiagramSceneElement(containerEl);
54
+ if (sceneEl.tagName?.toLowerCase() === "svg") {
55
+ const clonedSvg = sceneEl.cloneNode(true);
56
+ if (!clonedSvg.getAttribute("xmlns")) {
57
+ clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
58
+ }
59
+ const serializer2 = new XMLSerializer();
60
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
61
+ ${serializer2.serializeToString(clonedSvg)}`;
62
+ }
63
+ const { clonedScene, scaledWidth, scaledHeight } = prepareHtmlSceneForExport(sceneEl, options);
64
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
65
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
66
+ svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
67
+ svg.setAttribute("width", String(scaledWidth));
68
+ svg.setAttribute("height", String(scaledHeight));
69
+ svg.setAttribute("viewBox", `0 0 ${scaledWidth} ${scaledHeight}`);
70
+ if (options.includeThemeStyles !== false) {
71
+ const styleEl = document.createElementNS("http://www.w3.org/2000/svg", "style");
72
+ let combinedStyles = `
73
+ foreignObject { width: 100%; height: 100%; }
74
+ .markdy-node { transition: opacity 0.3s ease; }
75
+ `;
76
+ if (typeof document !== "undefined") {
77
+ const styles = document.querySelectorAll("style[id^='markdy-']");
78
+ for (let i = 0; i < styles.length; i++) {
79
+ combinedStyles += styles[i].textContent + "\n";
80
+ }
81
+ }
82
+ styleEl.textContent = combinedStyles;
83
+ svg.appendChild(styleEl);
84
+ }
85
+ const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
86
+ foreignObject.setAttribute("width", "100%");
87
+ foreignObject.setAttribute("height", "100%");
88
+ foreignObject.appendChild(clonedScene);
89
+ svg.appendChild(foreignObject);
90
+ const serializer = new XMLSerializer();
91
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
92
+ ` + serializer.serializeToString(svg);
93
+ }
94
+
95
+ export {
96
+ getDiagramSceneElement,
97
+ prepareHtmlSceneForExport,
98
+ exportDiagramAsVectorSvg
99
+ };
package/dist/index.d.ts CHANGED
@@ -28,6 +28,8 @@ interface DiagramOptions {
28
28
  playbackRate?: number;
29
29
  /** Enable wheel zoom and drag pan on the rendered viewport. Defaults to false. */
30
30
  interactiveViewport?: boolean;
31
+ /** Base URL for the Share control. Defaults to the Markdy playground. */
32
+ shareUrl?: string;
31
33
  /** Show built-in playback and viewport controls. Also enables viewport interaction. Defaults to false. */
32
34
  controls?: boolean;
33
35
  onWarning?: (warning: Diagnostic) => void;
@@ -46,6 +48,8 @@ interface Diagram {
46
48
  isPlaying(): boolean;
47
49
  beats(): BeatRange[];
48
50
  seekToBeat(name: string): void;
51
+ nextBeat(): void;
52
+ prevBeat(): void;
49
53
  destroy(): void;
50
54
  }
51
55
  declare function createDiagram(opts: DiagramOptions): Diagram;
package/dist/index.js CHANGED
@@ -1,5 +1,15 @@
1
+ import {
2
+ exportDiagramAsVectorSvg,
3
+ getDiagramSceneElement,
4
+ prepareHtmlSceneForExport
5
+ } from "./chunk-VIZHA7GI.js";
6
+
1
7
  // src/diagram.ts
2
- import { parseAndCompile } from "@markdy/core";
8
+ import {
9
+ compressMarkdyToUrlHash,
10
+ parseAndCompile,
11
+ resolvePlayer
12
+ } from "@markdy/core";
3
13
 
4
14
  // src/geometry/rect.ts
5
15
  function boxRect(box) {
@@ -2484,24 +2494,178 @@ function ensureSceneStyles(doc) {
2484
2494
  }
2485
2495
  .markdy-footer {
2486
2496
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2497
+ width: 100%;
2498
+ box-sizing: border-box;
2499
+ }
2500
+ .markdy-controls {
2501
+ display: flex;
2502
+ align-items: center;
2503
+ flex-wrap: nowrap;
2504
+ gap: 2.5px;
2505
+ max-width: 100%;
2506
+ overflow-x: auto;
2507
+ scrollbar-width: none;
2508
+ }
2509
+ .markdy-controls::-webkit-scrollbar {
2510
+ display: none;
2511
+ }
2512
+ @media (max-width: 640px) {
2513
+ .markdy-controls {
2514
+ flex-wrap: wrap;
2515
+ justify-content: center;
2516
+ gap: 3px;
2517
+ }
2487
2518
  }
2488
2519
  .markdy-controls button {
2520
+ appearance: none;
2489
2521
  touch-action: manipulation;
2490
2522
  user-select: none;
2491
2523
  -webkit-user-select: none;
2492
- transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.05s ease;
2524
+ border: 1px solid var(--md-control-border, rgba(148, 163, 184, 0.45));
2525
+ border-radius: 5px;
2526
+ background: var(--md-control-bg, rgba(248, 250, 252, 0.92));
2527
+ color: var(--md-control-text, #334155);
2528
+ cursor: pointer;
2529
+ font: 600 10px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2530
+ padding: 2.5px 5.5px;
2531
+ min-height: 23px;
2532
+ min-width: 22px;
2533
+ display: inline-flex;
2534
+ align-items: center;
2535
+ justify-content: center;
2536
+ gap: 2px;
2537
+ white-space: nowrap;
2538
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
2539
+ transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
2493
2540
  }
2494
2541
  .markdy-controls button:hover:not([aria-pressed="true"]) {
2495
- background: rgba(241, 245, 249, 1);
2496
- color: #1e293b;
2497
- border-color: rgba(100, 116, 139, 0.6);
2542
+ background: var(--md-control-hover-bg, #ffffff);
2543
+ color: var(--md-control-hover-text, #0f172a);
2544
+ border-color: var(--md-control-hover-border, #94a3b8);
2545
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
2546
+ }
2547
+ .markdy-controls button[aria-pressed="true"] {
2548
+ background: #0f172a !important;
2549
+ color: #ffffff !important;
2550
+ border-color: #0f172a !important;
2551
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25) !important;
2498
2552
  }
2499
2553
  .markdy-controls button:focus-visible {
2500
2554
  outline: 2px solid #3b82f6;
2501
2555
  outline-offset: 1px;
2502
2556
  }
2503
2557
  .markdy-controls button:active {
2504
- transform: scale(0.96);
2558
+ transform: scale(0.95);
2559
+ }
2560
+ .markdy-control-play::before { content: "\u25B6"; font-size: 7.5px; margin-right: 1px; }
2561
+ .markdy-control-play[aria-label*="Pause"]::before,
2562
+ .markdy-control-play[title*="Pause"]::before { content: "\u23F8"; font-size: 7.5px; margin-right: 1px; }
2563
+ .markdy-control-restart::before { content: "\u21BA"; font-size: 10px; font-weight: 700; margin-right: 1px; }
2564
+ .markdy-control-prev-beat::before { content: "\u23EE"; font-size: 7.5px; margin-right: 1px; }
2565
+ .markdy-control-next-beat::before { content: "\u23ED"; font-size: 7.5px; margin-right: 1px; }
2566
+ .markdy-control-fit::before { content: "\u26F6"; font-size: 8.5px; margin-right: 1px; }
2567
+ .markdy-control-reset-view::before { content: "\u2299"; font-size: 8.5px; margin-right: 1px; }
2568
+ .markdy-control-fullscreen::before { content: "\u26F6"; font-size: 8.5px; margin-right: 1px; }
2569
+ .markdy-control-svg::before { content: "\u{1F4E5}"; font-size: 8.5px; margin-right: 1px; }
2570
+ .markdy-control-share::before { content: "\u{1F517}"; font-size: 8.5px; margin-right: 1px; }
2571
+ .markdy-control-rate {
2572
+ font-family: "JetBrains Mono", monospace, system-ui;
2573
+ font-size: 9px;
2574
+ font-weight: 600;
2575
+ padding: 2px 4.5px;
2576
+ min-width: 18px;
2577
+ min-height: 21px;
2578
+ }
2579
+ .markdy-control-seek {
2580
+ appearance: none;
2581
+ -webkit-appearance: none;
2582
+ background: transparent;
2583
+ cursor: pointer;
2584
+ height: 20px;
2585
+ width: clamp(35px, 5vw, 65px);
2586
+ min-width: 35px;
2587
+ max-width: 70px;
2588
+ margin: 0 1px;
2589
+ }
2590
+ .markdy-control-seek:focus {
2591
+ outline: none;
2592
+ }
2593
+ .markdy-control-seek::-webkit-slider-runnable-track {
2594
+ width: 100%;
2595
+ height: 4.5px;
2596
+ background: rgba(148, 163, 184, 0.35);
2597
+ border-radius: 9999px;
2598
+ transition: background 0.15s ease;
2599
+ }
2600
+ .markdy-control-seek:hover::-webkit-slider-runnable-track {
2601
+ background: rgba(148, 163, 184, 0.55);
2602
+ }
2603
+ .markdy-control-seek::-webkit-slider-thumb {
2604
+ -webkit-appearance: none;
2605
+ height: 13px;
2606
+ width: 13px;
2607
+ border-radius: 50%;
2608
+ background: #2563eb;
2609
+ border: 2px solid #ffffff;
2610
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
2611
+ margin-top: -4.25px;
2612
+ cursor: grab;
2613
+ transition: transform 0.1s ease, background 0.15s ease;
2614
+ }
2615
+ .markdy-control-seek:active::-webkit-slider-thumb {
2616
+ cursor: grabbing;
2617
+ transform: scale(1.2);
2618
+ background: #1d4ed8;
2619
+ }
2620
+ .markdy-control-seek::-moz-range-track {
2621
+ width: 100%;
2622
+ height: 4.5px;
2623
+ background: rgba(148, 163, 184, 0.35);
2624
+ border-radius: 9999px;
2625
+ }
2626
+ .markdy-control-seek::-moz-range-thumb {
2627
+ height: 13px;
2628
+ width: 13px;
2629
+ border-radius: 50%;
2630
+ background: #2563eb;
2631
+ border: 2px solid #ffffff;
2632
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
2633
+ cursor: grab;
2634
+ }
2635
+
2636
+ [data-markdy-theme="midnight"] .markdy-controls button,
2637
+ [data-markdy-theme="blueprint"] .markdy-controls button,
2638
+ [data-markdy-theme="terminal"] .markdy-controls button,
2639
+ [data-markdy-theme="graphite"] .markdy-controls button,
2640
+ [data-markdy-theme="nebula"] .markdy-controls button,
2641
+ :root[data-theme="dark"] .markdy-controls button,
2642
+ .theme-dark .markdy-controls button {
2643
+ background: var(--md-control-bg, rgba(30, 41, 59, 0.85));
2644
+ border-color: var(--md-control-border, rgba(71, 85, 105, 0.55));
2645
+ color: var(--md-control-text, #cbd5e1);
2646
+ }
2647
+ [data-markdy-theme="midnight"] .markdy-controls button:hover:not([aria-pressed="true"]),
2648
+ [data-markdy-theme="blueprint"] .markdy-controls button:hover:not([aria-pressed="true"]),
2649
+ [data-markdy-theme="terminal"] .markdy-controls button:hover:not([aria-pressed="true"]),
2650
+ [data-markdy-theme="graphite"] .markdy-controls button:hover:not([aria-pressed="true"]),
2651
+ [data-markdy-theme="nebula"] .markdy-controls button:hover:not([aria-pressed="true"]),
2652
+ :root[data-theme="dark"] .markdy-controls button:hover:not([aria-pressed="true"]),
2653
+ .theme-dark .markdy-controls button:hover:not([aria-pressed="true"]) {
2654
+ background: rgba(51, 65, 85, 0.95);
2655
+ color: #f8fafc;
2656
+ border-color: #94a3b8;
2657
+ }
2658
+ [data-markdy-theme="midnight"] .markdy-controls button[aria-pressed="true"],
2659
+ [data-markdy-theme="blueprint"] .markdy-controls button[aria-pressed="true"],
2660
+ [data-markdy-theme="terminal"] .markdy-controls button[aria-pressed="true"],
2661
+ [data-markdy-theme="graphite"] .markdy-controls button[aria-pressed="true"],
2662
+ [data-markdy-theme="nebula"] .markdy-controls button[aria-pressed="true"],
2663
+ :root[data-theme="dark"] .markdy-controls button[aria-pressed="true"],
2664
+ .theme-dark .markdy-controls button[aria-pressed="true"] {
2665
+ background: #3b82f6 !important;
2666
+ color: #ffffff !important;
2667
+ border-color: #3b82f6 !important;
2668
+ box-shadow: 0 0 10px rgba(59, 130, 246, 0.5) !important;
2505
2669
  }
2506
2670
  .markdy-footer a {
2507
2671
  transition: opacity 0.15s ease, color 0.15s ease;
@@ -2571,11 +2735,11 @@ function applyThemeToScene(scene, theme) {
2571
2735
 
2572
2736
  // src/diagram.ts
2573
2737
  var NORMAL_PLAYBACK_RATE = 4 / 5;
2574
- var DEFAULT_PLAYBACK_RATE = 1;
2575
2738
  var MIN_VIEWPORT_ZOOM = 0.5;
2576
2739
  var MAX_VIEWPORT_ZOOM = 3;
2577
2740
  var VIEWPORT_ZOOM_STEP = 15e-4;
2578
2741
  var DRAG_CLICK_THRESHOLD_PX = 4;
2742
+ var BEAT_NAV_EPSILON_S = 0.05;
2579
2743
  var MARKDY_PLAYGROUND_URL = "https://markdy.com/playground/";
2580
2744
  function encodeCodeForPlaygroundHash(code) {
2581
2745
  return encodeURIComponent(btoa(encodeURIComponent(code)));
@@ -2628,6 +2792,7 @@ function createDiagram(opts) {
2628
2792
  playbackRate: initialPlaybackRate,
2629
2793
  controls: explicitControls,
2630
2794
  interactiveViewport: explicitInteractiveViewport,
2795
+ shareUrl,
2631
2796
  autoplay: explicitAutoplay,
2632
2797
  loop: explicitLoop,
2633
2798
  copyright: explicitCopyright,
@@ -2640,15 +2805,47 @@ function createDiagram(opts) {
2640
2805
  for (const w of ast.diagnostics) {
2641
2806
  if (w.severity === "warning") onWarning(w);
2642
2807
  }
2643
- const controls = explicitControls ?? plan.meta.controls ?? false;
2644
- const interactiveViewport = explicitInteractiveViewport ?? (explicitControls !== void 0 ? explicitControls : plan.meta.interactiveViewport ?? plan.meta.controls ?? false);
2645
- const autoplay = explicitAutoplay ?? plan.meta.autoplay ?? true;
2646
- const loop = explicitLoop ?? plan.meta.loop ?? true;
2647
- const copyright = explicitCopyright ?? plan.meta.copyright ?? true;
2648
- const rawPlaybackRate = initialPlaybackRate ?? plan.meta.playbackRate ?? DEFAULT_PLAYBACK_RATE;
2649
- let playbackRate = Number.isFinite(rawPlaybackRate) && rawPlaybackRate > 0 ? rawPlaybackRate : DEFAULT_PLAYBACK_RATE;
2650
- const showSceneBoundaryProgress = sceneBoundaryProgress === false || sceneBoundaryProgress === void 0 && progressBar === false ? false : plan.meta.progressColor === "none" ? false : true;
2651
- const rawColor = progressColor ?? progressBarColor ?? (typeof sceneBoundaryProgress === "string" && sceneBoundaryProgress !== "true" && sceneBoundaryProgress !== "false" ? sceneBoundaryProgress : typeof progressBar === "string" && progressBar !== "true" && progressBar !== "false" ? progressBar : void 0) ?? (plan.meta.progressColor && plan.meta.progressColor !== "none" ? plan.meta.progressColor : void 0);
2808
+ const hostProgress = sceneBoundaryProgress === false || sceneBoundaryProgress === void 0 && progressBar === false ? "none" : void 0;
2809
+ const hostProgressColor = progressColor ?? progressBarColor ?? (typeof sceneBoundaryProgress === "string" && sceneBoundaryProgress !== "true" && sceneBoundaryProgress !== "false" ? sceneBoundaryProgress : typeof progressBar === "string" && progressBar !== "true" && progressBar !== "false" ? progressBar : void 0);
2810
+ const player = resolvePlayer(plan.meta.player, {
2811
+ autoplay: explicitAutoplay,
2812
+ loop: explicitLoop,
2813
+ playbackRate: initialPlaybackRate,
2814
+ copyright: explicitCopyright,
2815
+ controls: explicitControls,
2816
+ interactiveViewport: explicitInteractiveViewport,
2817
+ progress: hostProgress,
2818
+ progressColor: hostProgressColor
2819
+ });
2820
+ const { autoplay, loop } = player.playback;
2821
+ const {
2822
+ enabled: interactiveViewport,
2823
+ zoom: allowZoom,
2824
+ pan: allowPan,
2825
+ clickToPlay,
2826
+ doubleClickToReset,
2827
+ keyboard: keyboardShortcuts
2828
+ } = player.interaction;
2829
+ const {
2830
+ enabled: showControls,
2831
+ play: playButton,
2832
+ restart: restartButton,
2833
+ prevBeat: prevBeatButton,
2834
+ nextBeat: nextBeatButton,
2835
+ seek: seekBar,
2836
+ speed: speedControls,
2837
+ speeds: speedOptions,
2838
+ fit: fitViewButton,
2839
+ resetView: resetViewButton,
2840
+ fullscreen: fullscreenButton,
2841
+ svg: svgButton,
2842
+ share: shareButton
2843
+ } = player.controls;
2844
+ const copyright = player.chrome.badge;
2845
+ const progressMode = player.chrome.progress;
2846
+ const showProgress = progressMode !== "none";
2847
+ let playbackRate = player.playback.rate;
2848
+ const rawColor = player.chrome.progressColor;
2652
2849
  const customColor = rawColor && rawColor.trim() !== "rainbow" ? rawColor.trim() : null;
2653
2850
  const DEFAULT_RAINBOW = "hsl(0,90%,60%), hsl(45,90%,55%), hsl(90,80%,50%), hsl(180,80%,50%), hsl(270,80%,55%), hsl(330,90%,60%)";
2654
2851
  const totalDurationMs = plan.duration * 1e3;
@@ -2667,11 +2864,11 @@ function createDiagram(opts) {
2667
2864
  });
2668
2865
  container.appendChild(viewport);
2669
2866
  let progressEl = null;
2670
- if (showSceneBoundaryProgress) {
2867
+ if (showProgress) {
2671
2868
  progressEl = document.createElement("div");
2672
2869
  Object.assign(progressEl.style, {
2673
2870
  position: "absolute",
2674
- inset: "0",
2871
+ ...progressMode === "bar" ? { left: "0", right: "0", bottom: "0", height: "3px" } : { inset: "0" },
2675
2872
  zIndex: "9999",
2676
2873
  pointerEvents: "none",
2677
2874
  borderRadius: "inherit"
@@ -2682,6 +2879,12 @@ function createDiagram(opts) {
2682
2879
  const tlAngleNorm = (tlAngle % 360 + 360) % 360;
2683
2880
  function updateProgressBar(pct) {
2684
2881
  if (!progressEl) return;
2882
+ if (progressMode === "bar") {
2883
+ progressEl.style.background = customColor ?? "#2563eb";
2884
+ progressEl.style.transformOrigin = "left center";
2885
+ progressEl.style.transform = `scaleX(${pct})`;
2886
+ return;
2887
+ }
2685
2888
  const deg = pct * 360;
2686
2889
  const colorStops = customColor ? customColor.includes(",") ? customColor : `${customColor} 0deg, ${customColor}` : DEFAULT_RAINBOW;
2687
2890
  progressEl.style.background = `conic-gradient(from ${tlAngleNorm}deg, ${colorStops} ${deg}deg, transparent ${deg}deg)`;
@@ -2955,15 +3158,40 @@ function createDiagram(opts) {
2955
3158
  let suppressNextClick = false;
2956
3159
  let controlsPlayButton = null;
2957
3160
  let controlsRateButtons = [];
3161
+ let controlsSeekBar = null;
3162
+ let controlsFitButton = null;
3163
+ let fitViewActive = false;
2958
3164
  function applyViewportTransform() {
2959
3165
  viewportTransform.style.transform = `translate(${viewportPanX}px, ${viewportPanY}px) scale(${viewportScale})`;
2960
3166
  }
2961
3167
  function resetViewportTransform() {
3168
+ releaseFitView();
2962
3169
  viewportScale = 1;
2963
3170
  viewportPanX = 0;
2964
3171
  viewportPanY = 0;
2965
3172
  applyViewportTransform();
2966
3173
  }
3174
+ function releaseFitView() {
3175
+ if (!fitViewActive) return;
3176
+ fitViewActive = false;
3177
+ cameraLayer.style.removeProperty("transform");
3178
+ }
3179
+ function toggleFitView() {
3180
+ if (fitViewActive) {
3181
+ resetViewportTransform();
3182
+ syncControls();
3183
+ return;
3184
+ }
3185
+ const bounds = computeContentBounds();
3186
+ const scale = Math.min(plan.meta.width / bounds.width, plan.meta.height / bounds.height);
3187
+ viewportScale = Number.isFinite(scale) && scale > 0 ? scale : 1;
3188
+ viewportPanX = -bounds.minX * viewportScale + (plan.meta.width - bounds.width * viewportScale) / 2;
3189
+ viewportPanY = -bounds.minY * viewportScale + (plan.meta.height - bounds.height * viewportScale) / 2;
3190
+ applyViewportTransform();
3191
+ fitViewActive = true;
3192
+ cameraLayer.style.setProperty("transform", "none", "important");
3193
+ syncControls();
3194
+ }
2967
3195
  function handleViewportWheel(event) {
2968
3196
  event.preventDefault();
2969
3197
  const rect = viewport.getBoundingClientRect();
@@ -2979,6 +3207,7 @@ function createDiagram(opts) {
2979
3207
  applyViewportTransform();
2980
3208
  }
2981
3209
  function handleViewportPointerDown(event) {
3210
+ if (!allowPan) return;
2982
3211
  if (event.button !== 0 || activePointerId !== null) return;
2983
3212
  activePointerId = event.pointerId;
2984
3213
  dragStartX = event.clientX;
@@ -3027,9 +3256,10 @@ function createDiagram(opts) {
3027
3256
  const rate = Number(button.dataset.rate ?? "1");
3028
3257
  const active = Math.abs(rate - playbackRate) < 1e-3;
3029
3258
  button.setAttribute("aria-pressed", active ? "true" : "false");
3030
- button.style.background = active ? "#1e293b" : "rgba(248, 250, 252, 0.92)";
3031
- button.style.color = active ? "#ffffff" : "#475569";
3032
- button.style.borderColor = active ? "#0f172a" : "rgba(148, 163, 184, 0.55)";
3259
+ }
3260
+ if (controlsSeekBar) controlsSeekBar.value = String(sceneMs / 1e3);
3261
+ if (controlsFitButton) {
3262
+ controlsFitButton.setAttribute("aria-pressed", fitViewActive ? "true" : "false");
3033
3263
  }
3034
3264
  }
3035
3265
  function emitPlayStateChange(playing) {
@@ -3105,8 +3335,23 @@ function createDiagram(opts) {
3105
3335
  const beat = plan.beats.find((b) => b.name === name);
3106
3336
  if (beat) diagram.seek(beat.start);
3107
3337
  },
3338
+ nextBeat() {
3339
+ const next = plan.beats.find((beat) => beat.start > sceneMs / 1e3 + BEAT_NAV_EPSILON_S);
3340
+ if (next) diagram.seek(next.start);
3341
+ },
3342
+ prevBeat() {
3343
+ const current = sceneMs / 1e3;
3344
+ let target = 0;
3345
+ for (const beat of plan.beats) {
3346
+ if (beat.start < current - BEAT_NAV_EPSILON_S) target = beat.start;
3347
+ }
3348
+ diagram.seek(target);
3349
+ },
3108
3350
  destroy() {
3109
3351
  diagram.pause();
3352
+ if (keyboardShortcuts && typeof window !== "undefined") {
3353
+ window.removeEventListener("keydown", handleKeyDown);
3354
+ }
3110
3355
  for (const anim of allAnims) anim.cancel();
3111
3356
  resizeObserver?.disconnect();
3112
3357
  if (progressEl?.parentNode === viewport) viewport.removeChild(progressEl);
@@ -3128,21 +3373,194 @@ function createDiagram(opts) {
3128
3373
  button.textContent = label;
3129
3374
  button.setAttribute("aria-label", ariaLabel);
3130
3375
  button.title = ariaLabel;
3131
- Object.assign(button.style, {
3132
- appearance: "none",
3133
- border: "1px solid rgba(148, 163, 184, 0.55)",
3134
- borderRadius: "5px",
3135
- background: "rgba(248, 250, 252, 0.92)",
3136
- color: "#475569",
3137
- cursor: "pointer",
3138
- font: "600 10px/1.1 system-ui, sans-serif",
3139
- padding: "4px 6px",
3140
- minWidth: "28px",
3141
- whiteSpace: "nowrap",
3142
- boxShadow: "none"
3143
- });
3144
3376
  return button;
3145
3377
  }
3378
+ function mountPlayControl(toolbar) {
3379
+ if (!playButton) return;
3380
+ controlsPlayButton = makeControlButton("Play", "Play diagram");
3381
+ controlsPlayButton.className = "markdy-control-play";
3382
+ controlsPlayButton.addEventListener("click", togglePlayback);
3383
+ toolbar.appendChild(controlsPlayButton);
3384
+ }
3385
+ function mountBeatNavControls(toolbar, position) {
3386
+ const wanted = position === "prev" ? prevBeatButton : nextBeatButton;
3387
+ if (!wanted || plan.beats.length < 2) return;
3388
+ const label = position === "prev" ? "Prev" : "Next";
3389
+ const button = makeControlButton(label, `${label === "Prev" ? "Previous" : "Next"} beat`);
3390
+ button.className = `markdy-control-${position}-beat`;
3391
+ button.addEventListener("click", () => position === "prev" ? diagram.prevBeat() : diagram.nextBeat());
3392
+ toolbar.appendChild(button);
3393
+ }
3394
+ function mountRestartControl(toolbar) {
3395
+ if (!restartButton) return;
3396
+ const button = makeControlButton("Restart", "Restart diagram");
3397
+ button.className = "markdy-control-restart";
3398
+ button.addEventListener("click", () => {
3399
+ diagram.seek(0);
3400
+ diagram.play();
3401
+ });
3402
+ toolbar.appendChild(button);
3403
+ }
3404
+ function mountSeekControl(toolbar) {
3405
+ if (!seekBar) return;
3406
+ controlsSeekBar = document.createElement("input");
3407
+ controlsSeekBar.className = "markdy-control-seek";
3408
+ controlsSeekBar.type = "range";
3409
+ controlsSeekBar.min = "0";
3410
+ controlsSeekBar.max = String(durationSeconds);
3411
+ controlsSeekBar.step = "0.01";
3412
+ controlsSeekBar.value = String(sceneMs / 1e3);
3413
+ controlsSeekBar.setAttribute("aria-label", "Seek diagram timeline");
3414
+ controlsSeekBar.addEventListener("input", () => diagram.seek(Number(controlsSeekBar?.value ?? 0)));
3415
+ toolbar.appendChild(controlsSeekBar);
3416
+ }
3417
+ function mountSpeedControls(toolbar) {
3418
+ if (!speedControls) return;
3419
+ controlsRateButtons = speedOptions.map((rate) => {
3420
+ const button = makeControlButton(`${rate}x`, `Set playback speed to ${rate}x`);
3421
+ button.className = "markdy-control-rate";
3422
+ button.dataset.rate = String(rate);
3423
+ button.setAttribute("aria-pressed", "false");
3424
+ button.addEventListener("click", () => diagram.setPlaybackRate(rate));
3425
+ toolbar.appendChild(button);
3426
+ return button;
3427
+ });
3428
+ }
3429
+ function flashControlLabel(button, message) {
3430
+ const original = button.textContent ?? "";
3431
+ button.textContent = message;
3432
+ setTimeout(() => {
3433
+ button.textContent = original;
3434
+ }, 1400);
3435
+ }
3436
+ function downloadFile(filename, contents, type) {
3437
+ const blob = new Blob([contents], { type });
3438
+ const url = URL.createObjectURL(blob);
3439
+ const link = document.createElement("a");
3440
+ link.href = url;
3441
+ link.download = filename;
3442
+ link.style.display = "none";
3443
+ document.body.appendChild(link);
3444
+ link.click();
3445
+ document.body.removeChild(link);
3446
+ URL.revokeObjectURL(url);
3447
+ }
3448
+ function mountSvgControl(toolbar) {
3449
+ if (!svgButton) return;
3450
+ const button = makeControlButton("SVG", "Export diagram as SVG");
3451
+ button.className = "markdy-control-svg";
3452
+ button.addEventListener("click", async () => {
3453
+ const resumeAt = sceneMs;
3454
+ const wasPlaying = isPlaying;
3455
+ try {
3456
+ diagram.pause();
3457
+ diagram.seek(durationSeconds);
3458
+ const { exportDiagramAsVectorSvg: exportDiagramAsVectorSvg2 } = await import("./svg-exporter-7FW6RIP6.js");
3459
+ const svg = exportDiagramAsVectorSvg2(container);
3460
+ const name = (plan.title || "markdy-diagram").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3461
+ downloadFile(`${name || "markdy-diagram"}.svg`, svg, "image/svg+xml");
3462
+ } catch (error) {
3463
+ onWarning({ severity: "warning", message: `SVG export failed: ${String(error)}`, line: 0 });
3464
+ flashControlLabel(button, "Failed");
3465
+ } finally {
3466
+ diagram.seek(resumeAt / 1e3);
3467
+ if (wasPlaying) diagram.play();
3468
+ }
3469
+ });
3470
+ toolbar.appendChild(button);
3471
+ }
3472
+ function mountShareControl(toolbar) {
3473
+ if (!shareButton) return;
3474
+ const button = makeControlButton("Share", "Copy a share link for this diagram");
3475
+ button.className = "markdy-control-share";
3476
+ button.addEventListener("click", async () => {
3477
+ try {
3478
+ const hash = await compressMarkdyToUrlHash(code);
3479
+ const base = shareUrl ?? MARKDY_PLAYGROUND_URL;
3480
+ await navigator.clipboard.writeText(`${base}#code=${hash}`);
3481
+ flashControlLabel(button, "Copied");
3482
+ } catch (error) {
3483
+ onWarning({ severity: "warning", message: `Share link failed: ${String(error)}`, line: 0 });
3484
+ flashControlLabel(button, "Failed");
3485
+ }
3486
+ });
3487
+ toolbar.appendChild(button);
3488
+ }
3489
+ function mountFitControl(toolbar) {
3490
+ if (!fitViewButton) return;
3491
+ controlsFitButton = makeControlButton("Fit", "Fit all items in view and ignore camera zoom");
3492
+ controlsFitButton.className = "markdy-control-fit";
3493
+ controlsFitButton.setAttribute("aria-pressed", "false");
3494
+ controlsFitButton.addEventListener("click", toggleFitView);
3495
+ toolbar.appendChild(controlsFitButton);
3496
+ }
3497
+ function mountResetViewControl(toolbar) {
3498
+ if (!resetViewButton) return;
3499
+ const button = makeControlButton("Reset", "Reset diagram view");
3500
+ button.className = "markdy-control-reset-view";
3501
+ button.addEventListener("click", resetViewportTransform);
3502
+ toolbar.appendChild(button);
3503
+ }
3504
+ function mountFullscreenControl(toolbar) {
3505
+ if (!fullscreenButton) return;
3506
+ const button = makeControlButton("Full", "Toggle fullscreen view");
3507
+ button.className = "markdy-control-fullscreen";
3508
+ button.setAttribute("aria-pressed", "false");
3509
+ const host = container.parentElement ?? container;
3510
+ function syncFullscreenState() {
3511
+ const isFull = document.fullscreenElement === host || document.fullscreenElement === container || document.fullscreenElement === viewport;
3512
+ button.setAttribute("aria-pressed", isFull ? "true" : "false");
3513
+ button.title = isFull ? "Exit fullscreen" : "Toggle fullscreen view";
3514
+ }
3515
+ button.addEventListener("click", async () => {
3516
+ try {
3517
+ if (!document.fullscreenElement) {
3518
+ if (host.requestFullscreen) {
3519
+ await host.requestFullscreen();
3520
+ } else if (host.webkitRequestFullscreen) {
3521
+ await host.webkitRequestFullscreen();
3522
+ }
3523
+ } else {
3524
+ if (document.exitFullscreen) {
3525
+ await document.exitFullscreen();
3526
+ } else if (document.webkitExitFullscreen) {
3527
+ await document.webkitExitFullscreen();
3528
+ }
3529
+ }
3530
+ } catch (err) {
3531
+ onWarning({ severity: "warning", message: `Fullscreen toggle failed: ${String(err)}`, line: 0 });
3532
+ }
3533
+ });
3534
+ document.addEventListener("fullscreenchange", syncFullscreenState);
3535
+ toolbar.appendChild(button);
3536
+ }
3537
+ function handleKeyDown(event) {
3538
+ const target = event.target;
3539
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target?.isContentEditable) {
3540
+ return;
3541
+ }
3542
+ switch (event.key) {
3543
+ case "ArrowRight":
3544
+ case "PageDown":
3545
+ event.preventDefault();
3546
+ diagram.nextBeat();
3547
+ break;
3548
+ case "ArrowLeft":
3549
+ case "PageUp":
3550
+ event.preventDefault();
3551
+ diagram.prevBeat();
3552
+ break;
3553
+ case " ":
3554
+ event.preventDefault();
3555
+ togglePlayback();
3556
+ break;
3557
+ case "Home":
3558
+ event.preventDefault();
3559
+ diagram.seek(0);
3560
+ break;
3561
+ default:
3562
+ }
3563
+ }
3146
3564
  function mountControls() {
3147
3565
  const toolbar = document.createElement("div");
3148
3566
  toolbar.className = "markdy-controls";
@@ -3153,8 +3571,6 @@ function createDiagram(opts) {
3153
3571
  display: "flex",
3154
3572
  alignItems: "center",
3155
3573
  justifyContent: "flex-start",
3156
- flexWrap: "wrap",
3157
- gap: "4px",
3158
3574
  maxWidth: "100%",
3159
3575
  padding: "0",
3160
3576
  border: "0",
@@ -3168,53 +3584,45 @@ function createDiagram(opts) {
3168
3584
  for (const eventName of ["click", "dblclick", "pointerdown", "pointermove", "pointerup", "wheel"]) {
3169
3585
  toolbar.addEventListener(eventName, (event) => event.stopPropagation());
3170
3586
  }
3171
- controlsPlayButton = makeControlButton("Play", "Play diagram");
3172
- controlsPlayButton.className = "markdy-control-play";
3173
- controlsPlayButton.addEventListener("click", togglePlayback);
3174
- toolbar.appendChild(controlsPlayButton);
3175
- const restartButton = makeControlButton("Restart", "Restart diagram");
3176
- restartButton.className = "markdy-control-restart";
3177
- restartButton.addEventListener("click", () => {
3178
- diagram.seek(0);
3179
- diagram.play();
3180
- });
3181
- toolbar.appendChild(restartButton);
3182
- controlsRateButtons = [0.5, 1, 2].map((rate) => {
3183
- const button = makeControlButton(`${rate}x`, `Set playback speed to ${rate}x`);
3184
- button.className = "markdy-control-rate";
3185
- button.dataset.rate = String(rate);
3186
- button.setAttribute("aria-pressed", "false");
3187
- button.addEventListener("click", () => diagram.setPlaybackRate(rate));
3188
- toolbar.appendChild(button);
3189
- return button;
3190
- });
3191
- if (interactiveViewport) {
3192
- const resetButton = makeControlButton("Reset", "Reset diagram view");
3193
- resetButton.className = "markdy-control-reset-view";
3194
- resetButton.addEventListener("click", resetViewportTransform);
3195
- toolbar.appendChild(resetButton);
3196
- }
3587
+ mountPlayControl(toolbar);
3588
+ mountBeatNavControls(toolbar, "prev");
3589
+ mountBeatNavControls(toolbar, "next");
3590
+ mountRestartControl(toolbar);
3591
+ mountSeekControl(toolbar);
3592
+ mountSpeedControls(toolbar);
3593
+ mountFitControl(toolbar);
3594
+ mountResetViewControl(toolbar);
3595
+ mountFullscreenControl(toolbar);
3596
+ mountSvgControl(toolbar);
3597
+ mountShareControl(toolbar);
3197
3598
  ensureFooter().insertBefore(toolbar, badge ?? null);
3198
3599
  syncControls();
3199
3600
  }
3200
3601
  viewport.style.cursor = interactiveViewport ? "grab" : "pointer";
3201
3602
  if (interactiveViewport) {
3202
3603
  viewport.style.touchAction = "none";
3203
- viewport.addEventListener("wheel", handleViewportWheel, { passive: false });
3204
- viewport.addEventListener("pointerdown", handleViewportPointerDown);
3205
- viewport.addEventListener("pointermove", handleViewportPointerMove);
3206
- viewport.addEventListener("pointerup", handleViewportPointerEnd);
3207
- viewport.addEventListener("pointercancel", handleViewportPointerEnd);
3208
- viewport.addEventListener("dblclick", handleViewportDoubleClick);
3209
- }
3210
- if (controls) mountControls();
3211
- viewport.addEventListener("click", () => {
3212
- if (suppressNextClick) {
3213
- suppressNextClick = false;
3214
- return;
3604
+ if (allowZoom) viewport.addEventListener("wheel", handleViewportWheel, { passive: false });
3605
+ if (allowPan) {
3606
+ viewport.addEventListener("pointerdown", handleViewportPointerDown);
3607
+ viewport.addEventListener("pointermove", handleViewportPointerMove);
3608
+ viewport.addEventListener("pointerup", handleViewportPointerEnd);
3609
+ viewport.addEventListener("pointercancel", handleViewportPointerEnd);
3215
3610
  }
3216
- togglePlayback();
3217
- });
3611
+ if (doubleClickToReset) viewport.addEventListener("dblclick", handleViewportDoubleClick);
3612
+ }
3613
+ if (showControls) mountControls();
3614
+ if (clickToPlay) {
3615
+ viewport.addEventListener("click", () => {
3616
+ if (suppressNextClick) {
3617
+ suppressNextClick = false;
3618
+ return;
3619
+ }
3620
+ togglePlayback();
3621
+ });
3622
+ }
3623
+ if (keyboardShortcuts && typeof window !== "undefined") {
3624
+ window.addEventListener("keydown", handleKeyDown);
3625
+ }
3218
3626
  if (autoplay) diagram.play();
3219
3627
  return diagram;
3220
3628
  }
@@ -3410,100 +3818,6 @@ function encodeGifSequence(frames, options = {}) {
3410
3818
  return new Uint8Array(buffer);
3411
3819
  }
3412
3820
 
3413
- // src/export/svg-exporter.ts
3414
- function copyRenderedStyles(source, clone) {
3415
- if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
3416
- const sourceElements = [source, ...Array.from(source.querySelectorAll("*"))];
3417
- const cloneElements = [clone, ...Array.from(clone.querySelectorAll("*"))];
3418
- for (let index = 0; index < Math.min(sourceElements.length, cloneElements.length); index++) {
3419
- const computed = window.getComputedStyle(sourceElements[index]);
3420
- const target = cloneElements[index].style;
3421
- for (let propertyIndex = 0; propertyIndex < computed.length; propertyIndex++) {
3422
- const property = computed.item(propertyIndex);
3423
- target.setProperty(property, computed.getPropertyValue(property), computed.getPropertyPriority(property));
3424
- }
3425
- }
3426
- }
3427
- function normalizeExportViewport(scene) {
3428
- scene.querySelectorAll(".markdy-viewport-transform").forEach((viewportTransform) => {
3429
- viewportTransform.style.transform = "translate(0px, 0px) scale(1)";
3430
- viewportTransform.style.transformOrigin = "0 0";
3431
- viewportTransform.style.willChange = "auto";
3432
- });
3433
- }
3434
- function getDiagramSceneElement(containerEl) {
3435
- const sceneEl = containerEl.classList?.contains("markdy-scene-root") ? containerEl : containerEl.querySelector(".markdy-scene-root") || containerEl.querySelector("svg") || (containerEl.tagName?.toLowerCase() === "svg" ? containerEl : null);
3436
- if (!sceneEl) throw new Error("No Markdy scene element found in container");
3437
- return sceneEl;
3438
- }
3439
- function prepareHtmlSceneForExport(sceneEl, options = {}) {
3440
- const clonedScene = sceneEl.cloneNode(true);
3441
- copyRenderedStyles(sceneEl, clonedScene);
3442
- normalizeExportViewport(clonedScene);
3443
- const widthStr = clonedScene.style.width || String(sceneEl.clientWidth || 800);
3444
- const heightStr = clonedScene.style.height || String(sceneEl.clientHeight || 400);
3445
- let width = parseFloat(widthStr);
3446
- let height = parseFloat(heightStr);
3447
- if (isNaN(width)) width = 800;
3448
- if (isNaN(height)) height = 400;
3449
- const scale = options.scale || 1;
3450
- const scaledWidth = width * scale;
3451
- const scaledHeight = height * scale;
3452
- clonedScene.style.transform = `scale(${scale})`;
3453
- clonedScene.style.transformOrigin = "0 0";
3454
- clonedScene.style.position = "relative";
3455
- clonedScene.style.left = "0px";
3456
- clonedScene.style.top = "0px";
3457
- clonedScene.style.margin = "0";
3458
- clonedScene.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3459
- if (options.transparentBackground) {
3460
- clonedScene.style.background = "transparent";
3461
- }
3462
- return { sceneEl, clonedScene, width, height, scaledWidth, scaledHeight };
3463
- }
3464
- function exportDiagramAsVectorSvg(containerEl, options = {}) {
3465
- const sceneEl = getDiagramSceneElement(containerEl);
3466
- if (sceneEl.tagName?.toLowerCase() === "svg") {
3467
- const clonedSvg = sceneEl.cloneNode(true);
3468
- if (!clonedSvg.getAttribute("xmlns")) {
3469
- clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
3470
- }
3471
- const serializer2 = new XMLSerializer();
3472
- return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3473
- ${serializer2.serializeToString(clonedSvg)}`;
3474
- }
3475
- const { clonedScene, scaledWidth, scaledHeight } = prepareHtmlSceneForExport(sceneEl, options);
3476
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3477
- svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
3478
- svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
3479
- svg.setAttribute("width", String(scaledWidth));
3480
- svg.setAttribute("height", String(scaledHeight));
3481
- svg.setAttribute("viewBox", `0 0 ${scaledWidth} ${scaledHeight}`);
3482
- if (options.includeThemeStyles !== false) {
3483
- const styleEl = document.createElementNS("http://www.w3.org/2000/svg", "style");
3484
- let combinedStyles = `
3485
- foreignObject { width: 100%; height: 100%; }
3486
- .markdy-node { transition: opacity 0.3s ease; }
3487
- `;
3488
- if (typeof document !== "undefined") {
3489
- const styles = document.querySelectorAll("style[id^='markdy-']");
3490
- for (let i = 0; i < styles.length; i++) {
3491
- combinedStyles += styles[i].textContent + "\n";
3492
- }
3493
- }
3494
- styleEl.textContent = combinedStyles;
3495
- svg.appendChild(styleEl);
3496
- }
3497
- const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
3498
- foreignObject.setAttribute("width", "100%");
3499
- foreignObject.setAttribute("height", "100%");
3500
- foreignObject.appendChild(clonedScene);
3501
- svg.appendChild(foreignObject);
3502
- const serializer = new XMLSerializer();
3503
- return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3504
- ` + serializer.serializeToString(svg);
3505
- }
3506
-
3507
3821
  // src/export/inline-resources.ts
3508
3822
  var TRANSPARENT_PIXEL_DATA_URI = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
3509
3823
  var RESOURCE_FETCH_TIMEOUT_MS = 3e3;
@@ -0,0 +1,10 @@
1
+ import {
2
+ exportDiagramAsVectorSvg,
3
+ getDiagramSceneElement,
4
+ prepareHtmlSceneForExport
5
+ } from "./chunk-VIZHA7GI.js";
6
+ export {
7
+ exportDiagramAsVectorSvg,
8
+ getDiagramSceneElement,
9
+ prepareHtmlSceneForExport
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
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",
@@ -45,14 +45,14 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "html2canvas": "^1.4.1",
48
- "@markdy/core": "1.0.10"
48
+ "@markdy/core": "1.0.12"
49
49
  },
50
50
  "devDependencies": {
51
51
  "jsdom": "^29.1.1",
52
52
  "tsup": "^8.5.1",
53
53
  "typescript": "^5.9.3",
54
54
  "vitest": "^4.1.7",
55
- "@markdy/stdlib-systems": "1.0.10"
55
+ "@markdy/stdlib-systems": "1.0.12"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",