@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 +3 -2
- package/dist/chunk-VIZHA7GI.js +99 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +484 -170
- package/dist/svg-exporter-7FW6RIP6.js +10 -0
- package/package.json +3 -3
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 \|\|
|
|
81
|
-
| `controls` | `boolean` | `
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
2496
|
-
color: #
|
|
2497
|
-
border-color:
|
|
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.
|
|
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
|
|
2644
|
-
const
|
|
2645
|
-
const
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
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 (
|
|
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
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
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
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
toolbar
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
toolbar
|
|
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
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
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
|
-
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
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.
|
|
55
|
+
"@markdy/stdlib-systems": "1.0.12"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsup",
|