@markdy/renderer-dom 0.8.19 → 0.8.21
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 +11 -8
- package/dist/index.d.ts +59 -5
- package/dist/index.js +523 -22
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -70,18 +70,21 @@ diagram.destroy(); // clean up DOM + cancel animations
|
|
|
70
70
|
|---|---|---|---|
|
|
71
71
|
| `container` | `HTMLElement` | *(required)* | DOM element to mount the scene into |
|
|
72
72
|
| `code` | `string` | *(required)* | MarkdyScript source code |
|
|
73
|
-
| `autoplay` | `boolean` | `true` | Start playing immediately |
|
|
74
|
-
| `loop` | `boolean` | `true` | Loop the animation when it reaches the end |
|
|
75
|
-
| `copyright` | `boolean` | `true` | Show a small "Powered by Markdy" badge below the animation |
|
|
76
|
-
| `progressBar` | `boolean` | `true` | Deprecated compatibility flag for the
|
|
77
|
-
| `sceneBoundaryProgress` | `boolean` | `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
73
|
+
| `autoplay` | `boolean` | `plan.meta.autoplay ?? true` | Start playing immediately |
|
|
74
|
+
| `loop` | `boolean` | `plan.meta.loop ?? true` | Loop the animation when it reaches the end |
|
|
75
|
+
| `copyright` | `boolean` | `plan.meta.copyright ?? true` | Show a small "Powered by Markdy" badge below the animation |
|
|
76
|
+
| `progressBar` | `boolean \| string` | `true` | Deprecated compatibility flag for the scene-boundary progress bar |
|
|
77
|
+
| `sceneBoundaryProgress` | `boolean \| string` | `true` | Show boundary progress bar, or pass a custom color/gradient string |
|
|
78
|
+
| `progressColor` | `string` | `plan.meta.progressColor ?? "rainbow"` | Custom progress bar color (e.g. `"#3b82f6"`) or gradient (e.g. `"#ec4899, #8b5cf6"`) |
|
|
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 |
|
|
81
82
|
| `onWarning` | `(warning: Diagnostic) => void` | `console.warn` | Called for each soft parse warning |
|
|
82
83
|
| `onTimeUpdate` | `(seconds: number, durationSeconds: number) => void` | — | Called whenever playback or seek changes the current time |
|
|
83
84
|
| `onPlayStateChange` | `(playing: boolean) => void` | — | Called when playback starts or pauses |
|
|
84
85
|
|
|
86
|
+
> **Note:** Directives declared inside the MarkdyScript code (e.g. `controls true`, `interactive true`, `progressColor "#3b82f6"`) apply automatically if the corresponding JavaScript option is omitted (`undefined`). Options passed to `createDiagram()` override the in-script declarations.
|
|
87
|
+
|
|
85
88
|
### `Diagram`
|
|
86
89
|
|
|
87
90
|
| Method | Description |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BeatRange, Diagnostic } from '@markdy/core';
|
|
1
|
+
import { BeatRange, Diagnostic, RenderPlan } from '@markdy/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Markdy diagram runtime — renders a RenderPlan via WAAPI.
|
|
@@ -17,9 +17,13 @@ interface DiagramOptions {
|
|
|
17
17
|
*/
|
|
18
18
|
assets?: Record<string, string>;
|
|
19
19
|
/** @deprecated Prefer sceneBoundaryProgress. */
|
|
20
|
-
progressBar?: boolean;
|
|
21
|
-
/** Show rainbow progress around scene boundary. Defaults to true. */
|
|
22
|
-
sceneBoundaryProgress?: boolean;
|
|
20
|
+
progressBar?: boolean | string;
|
|
21
|
+
/** Show rainbow progress around scene boundary, or specify a custom color/gradient. Defaults to true. */
|
|
22
|
+
sceneBoundaryProgress?: boolean | string;
|
|
23
|
+
/** Custom progress bar color or gradient. Defaults to rainbow. */
|
|
24
|
+
progressColor?: string;
|
|
25
|
+
/** @deprecated Prefer progressColor. */
|
|
26
|
+
progressBarColor?: string;
|
|
23
27
|
/** Playback speed multiplier. Defaults to 1, where 1 is Markdy's normal pace. */
|
|
24
28
|
playbackRate?: number;
|
|
25
29
|
/** Enable wheel zoom and drag pan on the rendered viewport. Defaults to false. */
|
|
@@ -50,4 +54,54 @@ type IconSpec = SvgSpec;
|
|
|
50
54
|
/** Read-only monochrome glyph registry; callers can inspect or choose keys without injecting markup. */
|
|
51
55
|
declare const ICON_REGISTRY: Readonly<Record<string, IconSpec>>;
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
/**
|
|
58
|
+
* packages/renderer-dom/src/export/gif-encoder.ts
|
|
59
|
+
* Pure TypeScript GIF89a Encoder with LZW Compression & Floyd-Steinberg Dithering.
|
|
60
|
+
* Zero external dependencies.
|
|
61
|
+
*/
|
|
62
|
+
interface AnimationRecordFrame {
|
|
63
|
+
imageData: ImageData;
|
|
64
|
+
delayMs: number;
|
|
65
|
+
}
|
|
66
|
+
interface GifExportOptions {
|
|
67
|
+
dither?: boolean;
|
|
68
|
+
loop?: boolean;
|
|
69
|
+
}
|
|
70
|
+
declare function encodeGifSequence(frames: AnimationRecordFrame[], options?: GifExportOptions): Uint8Array;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* packages/renderer-dom/src/export/svg-exporter.ts
|
|
74
|
+
* Standalone Vector SVG & Figma-compatible design token asset export.
|
|
75
|
+
* Zero external dependencies.
|
|
76
|
+
*/
|
|
77
|
+
interface SvgExportOptions {
|
|
78
|
+
includeThemeStyles?: boolean;
|
|
79
|
+
transparentBackground?: boolean;
|
|
80
|
+
scale?: number;
|
|
81
|
+
}
|
|
82
|
+
declare function exportDiagramAsVectorSvg(containerEl: HTMLElement, options?: SvgExportOptions): string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* packages/renderer-dom/src/presentation-controller.ts
|
|
86
|
+
* Interactive Beat Navigation & Keyboard-driven presentation controller.
|
|
87
|
+
* Zero external dependencies.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
interface ControllerOptions {
|
|
91
|
+
enableKeyboard?: boolean;
|
|
92
|
+
onBeatChange?: (beatName: string, index: number) => void;
|
|
93
|
+
}
|
|
94
|
+
declare class DiagramPresentationController {
|
|
95
|
+
private diagram;
|
|
96
|
+
private plan;
|
|
97
|
+
private currentBeatIndex;
|
|
98
|
+
constructor(diagram: Diagram, plan: RenderPlan, options?: ControllerOptions);
|
|
99
|
+
nextBeat(): void;
|
|
100
|
+
prevBeat(): void;
|
|
101
|
+
getCurrentBeatIndex(): number;
|
|
102
|
+
togglePlay(): void;
|
|
103
|
+
setSpeed(rate: number): void;
|
|
104
|
+
private attachKeyboardListener;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { type AnimationRecordFrame, type ControllerOptions, type Diagram, type DiagramOptions, DiagramPresentationController, type GifExportOptions, ICON_REGISTRY, type IconSpec, type SvgExportOptions, createDiagram, encodeGifSequence, exportDiagramAsVectorSvg };
|
package/dist/index.js
CHANGED
|
@@ -327,6 +327,25 @@ function ensureDefs(svg, theme, id) {
|
|
|
327
327
|
arrow.appendChild(path);
|
|
328
328
|
defs.appendChild(arrow);
|
|
329
329
|
}
|
|
330
|
+
const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
|
331
|
+
filter.setAttribute("id", `${id}-sketchy`);
|
|
332
|
+
filter.setAttribute("x", "-4%");
|
|
333
|
+
filter.setAttribute("y", "-4%");
|
|
334
|
+
filter.setAttribute("width", "108%");
|
|
335
|
+
filter.setAttribute("height", "108%");
|
|
336
|
+
const turb = document.createElementNS("http://www.w3.org/2000/svg", "feTurbulence");
|
|
337
|
+
turb.setAttribute("type", "fractalNoise");
|
|
338
|
+
turb.setAttribute("baseFrequency", "0.02");
|
|
339
|
+
turb.setAttribute("numOctaves", "2");
|
|
340
|
+
turb.setAttribute("seed", "4");
|
|
341
|
+
turb.setAttribute("result", "noise");
|
|
342
|
+
const disp = document.createElementNS("http://www.w3.org/2000/svg", "feDisplacementMap");
|
|
343
|
+
disp.setAttribute("in", "SourceGraphic");
|
|
344
|
+
disp.setAttribute("in2", "noise");
|
|
345
|
+
disp.setAttribute("scale", "1.8");
|
|
346
|
+
filter.appendChild(turb);
|
|
347
|
+
filter.appendChild(disp);
|
|
348
|
+
defs.appendChild(filter);
|
|
330
349
|
svg.prepend(defs);
|
|
331
350
|
}
|
|
332
351
|
function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane) {
|
|
@@ -742,11 +761,25 @@ function ensureAnnotationStyles(doc) {
|
|
|
742
761
|
.markdy-annotation {
|
|
743
762
|
position: absolute;
|
|
744
763
|
max-width: 220px;
|
|
745
|
-
font-family: var(--md-font-title, Georgia, serif);
|
|
764
|
+
font-family: var(--md-font-title, Georgia, "Times New Roman", serif);
|
|
746
765
|
font-size: 14px;
|
|
747
766
|
font-style: italic;
|
|
748
767
|
color: var(--md-text);
|
|
749
|
-
line-height: 1.
|
|
768
|
+
line-height: 1.4;
|
|
769
|
+
letter-spacing: 0.01em;
|
|
770
|
+
opacity: 0;
|
|
771
|
+
transform: translateY(6px);
|
|
772
|
+
transition: opacity 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
773
|
+
}
|
|
774
|
+
.markdy-annotation[data-visible="1"] {
|
|
775
|
+
opacity: 1;
|
|
776
|
+
transform: none;
|
|
777
|
+
}
|
|
778
|
+
.markdy-annotation[data-intent="accent"] {
|
|
779
|
+
color: var(--md-accent);
|
|
780
|
+
}
|
|
781
|
+
.markdy-annotation[data-intent="muted"] {
|
|
782
|
+
color: var(--md-text-muted);
|
|
750
783
|
}
|
|
751
784
|
.markdy-annotation__leader {
|
|
752
785
|
position: absolute;
|
|
@@ -790,10 +823,13 @@ function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
|
790
823
|
const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
791
824
|
path.setAttribute("d", `M ${ax} ${ay} Q ${(ax + tx) / 2} ${(ay + ty) / 2 - 20} ${tx} ${ty}`);
|
|
792
825
|
path.setAttribute("fill", "none");
|
|
793
|
-
|
|
826
|
+
const intent = typeof ann.intent === "string" ? ann.intent : "neutral";
|
|
827
|
+
const leaderColor = intent === "accent" ? theme.accent : intent === "muted" ? theme.soft ?? theme.textMuted : theme.textMuted;
|
|
828
|
+
const leaderOpacity = intent === "accent" ? "0.60" : intent === "muted" ? "0.40" : "0.50";
|
|
829
|
+
path.setAttribute("stroke", leaderColor);
|
|
794
830
|
path.setAttribute("stroke-width", "1");
|
|
795
831
|
path.setAttribute("stroke-dasharray", "4 3");
|
|
796
|
-
path.setAttribute("opacity",
|
|
832
|
+
path.setAttribute("opacity", leaderOpacity);
|
|
797
833
|
svg.appendChild(path);
|
|
798
834
|
const dot = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
799
835
|
dot.setAttribute("cx", String(tx));
|
|
@@ -1161,6 +1197,45 @@ function ensureNodeStyles(doc) {
|
|
|
1161
1197
|
.markdy-scene-root[data-flat="1"] .markdy-node[data-visible="1"] {
|
|
1162
1198
|
box-shadow: inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent));
|
|
1163
1199
|
}
|
|
1200
|
+
.markdy-scene-root[data-flat="1"] .markdy-node[data-focal="1"] {
|
|
1201
|
+
box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--md-accent) 60%, transparent);
|
|
1202
|
+
background:
|
|
1203
|
+
linear-gradient(180deg,
|
|
1204
|
+
color-mix(in srgb, var(--md-accent-tint, var(--md-accent)) 8%, transparent),
|
|
1205
|
+
color-mix(in srgb, var(--md-accent-tint, var(--md-accent)) 4%, transparent));
|
|
1206
|
+
}
|
|
1207
|
+
.markdy-node[data-kind="external"] {
|
|
1208
|
+
border: 1.2px dashed color-mix(in srgb, var(--md-ink, var(--md-text)) 30%, transparent);
|
|
1209
|
+
background: color-mix(in srgb, var(--md-ink, var(--md-text)) 3%, transparent);
|
|
1210
|
+
box-shadow: none;
|
|
1211
|
+
}
|
|
1212
|
+
.markdy-node[data-kind="optional"] {
|
|
1213
|
+
border: 1px dashed color-mix(in srgb, var(--md-ink, var(--md-text)) 20%, transparent);
|
|
1214
|
+
background: color-mix(in srgb, var(--md-ink, var(--md-text)) 2%, transparent);
|
|
1215
|
+
box-shadow: none;
|
|
1216
|
+
opacity: 0.7;
|
|
1217
|
+
}
|
|
1218
|
+
.markdy-scene-root[data-markdy-theme="terminal"] .markdy-node {
|
|
1219
|
+
background: var(--md-node-surface, #141414);
|
|
1220
|
+
border: 1px solid #2b2b2b;
|
|
1221
|
+
box-shadow: none;
|
|
1222
|
+
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
1223
|
+
}
|
|
1224
|
+
.markdy-scene-root[data-markdy-theme="terminal"] .markdy-node[data-focal="1"] {
|
|
1225
|
+
border-color: #ff5a36;
|
|
1226
|
+
box-shadow: 0 0 18px -8px rgba(255, 90, 54, 0.45);
|
|
1227
|
+
}
|
|
1228
|
+
.markdy-scene-root[data-markdy-theme="sketchy"] .markdy-node {
|
|
1229
|
+
background: #ffffff;
|
|
1230
|
+
border: 1.5px solid #2d3142;
|
|
1231
|
+
box-shadow: 3px 3px 0 rgba(45, 49, 66, 0.10);
|
|
1232
|
+
border-radius: var(--md-radius-md, 4px);
|
|
1233
|
+
}
|
|
1234
|
+
.markdy-scene-root[data-markdy-theme="sketchy"] .markdy-node[data-focal="1"] {
|
|
1235
|
+
border-color: #eb6c36;
|
|
1236
|
+
background: rgba(235, 108, 54, 0.06);
|
|
1237
|
+
box-shadow: 3px 3px 0 rgba(235, 108, 54, 0.12);
|
|
1238
|
+
}
|
|
1164
1239
|
`;
|
|
1165
1240
|
doc.head.appendChild(style);
|
|
1166
1241
|
}
|
|
@@ -1314,6 +1389,60 @@ var ICONS = {
|
|
|
1314
1389
|
["path", { d: "M12 3 21 12 12 21 3 12 12 3Z" }],
|
|
1315
1390
|
["path", { d: "M12 8v4" }],
|
|
1316
1391
|
["path", { d: "M12 16h.01" }]
|
|
1392
|
+
],
|
|
1393
|
+
hub: [
|
|
1394
|
+
["circle", { cx: "12", cy: "12", r: "7.5" }],
|
|
1395
|
+
["circle", { cx: "12", cy: "12", r: "2.8" }]
|
|
1396
|
+
],
|
|
1397
|
+
station: [
|
|
1398
|
+
["rect", { x: "4", y: "4", width: "16", height: "16", rx: "4" }],
|
|
1399
|
+
["circle", { cx: "12", cy: "12", r: "3" }]
|
|
1400
|
+
],
|
|
1401
|
+
bronze: [
|
|
1402
|
+
["rect", { x: "4", y: "5", width: "16", height: "5", rx: "1.5" }],
|
|
1403
|
+
["rect", { x: "4", y: "14", width: "16", height: "5", rx: "1.5" }]
|
|
1404
|
+
],
|
|
1405
|
+
silver: [
|
|
1406
|
+
["path", { d: "M12 2 20 7v10l-8 5-8-5V7z" }]
|
|
1407
|
+
],
|
|
1408
|
+
gold: [
|
|
1409
|
+
["path", { d: "M12 2.5 15 8.5l6.5 1-4.7 4.6 1.1 6.4L12 17.5 6.1 20.5l1.1-6.4L2.5 9.5l6.5-1z" }]
|
|
1410
|
+
],
|
|
1411
|
+
terminal: [
|
|
1412
|
+
["rect", { x: "3", y: "4", width: "18", height: "16", rx: "2.5" }],
|
|
1413
|
+
["path", { d: "m7 9 3 3-3 3M13 15h4" }]
|
|
1414
|
+
],
|
|
1415
|
+
cloud: [
|
|
1416
|
+
["path", { d: "M7 16a4 4 0 0 1-.88-7.9 5 5 0 0 1 9.76-1.1A4 4 0 0 1 17 16H7z" }]
|
|
1417
|
+
],
|
|
1418
|
+
firewall: [
|
|
1419
|
+
["path", { d: "M12 3v18M3 8h18M3 16h18M7.5 3v5M16.5 3v5M7.5 16v5M16.5 16v5M12 8v8" }]
|
|
1420
|
+
],
|
|
1421
|
+
alert: [
|
|
1422
|
+
["path", { d: "M12 3 2 20h20L12 3z" }],
|
|
1423
|
+
["path", { d: "M12 9v4M12 17h.01" }]
|
|
1424
|
+
],
|
|
1425
|
+
sync: [
|
|
1426
|
+
["path", { d: "M20 11A8 8 0 0 0 5.6 6.4L3 9" }],
|
|
1427
|
+
["path", { d: "M3 4v5h5M4 13a8 8 0 0 0 14.4 4.6L21 15" }],
|
|
1428
|
+
["path", { d: "M21 20v-5h-5" }]
|
|
1429
|
+
],
|
|
1430
|
+
search: [
|
|
1431
|
+
["circle", { cx: "11", cy: "11", r: "6.5" }],
|
|
1432
|
+
["path", { d: "m19 19-3.5-3.5" }]
|
|
1433
|
+
],
|
|
1434
|
+
log: [
|
|
1435
|
+
["rect", { x: "4", y: "3", width: "16", height: "18", rx: "2" }],
|
|
1436
|
+
["path", { d: "M8 7h8M8 11h8M8 15h4" }]
|
|
1437
|
+
],
|
|
1438
|
+
layers: [
|
|
1439
|
+
["path", { d: "M12 2 2 7l10 5 10-5-10-5z" }],
|
|
1440
|
+
["path", { d: "M2 17l10 5 10-5" }],
|
|
1441
|
+
["path", { d: "M2 12l10 5 10-5" }]
|
|
1442
|
+
],
|
|
1443
|
+
nested: [
|
|
1444
|
+
["rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" }],
|
|
1445
|
+
["rect", { x: "7", y: "7", width: "10", height: "10", rx: "2" }]
|
|
1317
1446
|
]
|
|
1318
1447
|
};
|
|
1319
1448
|
var ICON_REGISTRY = Object.freeze(ICONS);
|
|
@@ -1340,6 +1469,14 @@ function iconKeyForNode(node) {
|
|
|
1340
1469
|
if (node.kind === "api" || node.kind === "service" || node.kind === "microservice" || node.kind === "backend" || node.kind === "server" || node.kind === "handler" || node.kind === "controller") return "server";
|
|
1341
1470
|
if (node.kind === "browser" || node.kind === "web" || node.kind === "frontend" || node.kind === "app") return "browser";
|
|
1342
1471
|
if (node.kind === "user" || node.kind === "client") return "user";
|
|
1472
|
+
if (node.kind === "cloud") return "cloud";
|
|
1473
|
+
if (node.kind === "firewall") return "firewall";
|
|
1474
|
+
if (node.kind === "alert" || node.kind === "alarm") return "alert";
|
|
1475
|
+
if (node.kind === "sync") return "sync";
|
|
1476
|
+
if (node.kind === "search") return "search";
|
|
1477
|
+
if (node.kind === "log" || node.kind === "audit") return "log";
|
|
1478
|
+
if (node.kind === "layers" || node.kind === "stack") return "layers";
|
|
1479
|
+
if (node.kind === "nested") return "nested";
|
|
1343
1480
|
if (node.kind === "decision" || node.kind === "condition") return "decision";
|
|
1344
1481
|
if (node.kind === "cache") return "cache";
|
|
1345
1482
|
return ICONS[node.kind] ? node.kind : node.role;
|
|
@@ -1720,6 +1857,26 @@ function ensureSceneStyles(doc) {
|
|
|
1720
1857
|
linear-gradient(180deg, transparent 0%, var(--md-vignette) 100%);
|
|
1721
1858
|
opacity: 0.8;
|
|
1722
1859
|
}
|
|
1860
|
+
.markdy-scene-root[data-markdy-theme="terminal"]::before {
|
|
1861
|
+
background:
|
|
1862
|
+
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.06) 1px, transparent 1px) 0 0 / 22px 22px;
|
|
1863
|
+
mask-image: none;
|
|
1864
|
+
opacity: 0.4;
|
|
1865
|
+
}
|
|
1866
|
+
.markdy-scene-root[data-markdy-theme="terminal"]::after {
|
|
1867
|
+
background:
|
|
1868
|
+
radial-gradient(ellipse at 50% 0%, rgba(255, 90, 54, 0.08), transparent 50%),
|
|
1869
|
+
linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.5) 100%);
|
|
1870
|
+
opacity: 0.7;
|
|
1871
|
+
}
|
|
1872
|
+
.markdy-scene-root[data-markdy-theme="sketchy"]::before {
|
|
1873
|
+
background: none;
|
|
1874
|
+
opacity: 0;
|
|
1875
|
+
}
|
|
1876
|
+
.markdy-scene-root[data-markdy-theme="sketchy"]::after {
|
|
1877
|
+
background: none;
|
|
1878
|
+
opacity: 0;
|
|
1879
|
+
}
|
|
1723
1880
|
@keyframes markdy-star-twinkle {
|
|
1724
1881
|
from { opacity: 0.24; transform: scale(0.85); }
|
|
1725
1882
|
to { opacity: 0.9; transform: scale(1.15); }
|
|
@@ -1769,6 +1926,49 @@ function ensureSceneStyles(doc) {
|
|
|
1769
1926
|
transform: translateY(8px);
|
|
1770
1927
|
will-change: opacity, transform;
|
|
1771
1928
|
}
|
|
1929
|
+
.markdy-footer {
|
|
1930
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
1931
|
+
}
|
|
1932
|
+
.markdy-controls button {
|
|
1933
|
+
touch-action: manipulation;
|
|
1934
|
+
user-select: none;
|
|
1935
|
+
-webkit-user-select: none;
|
|
1936
|
+
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.05s ease;
|
|
1937
|
+
}
|
|
1938
|
+
.markdy-controls button:hover:not([aria-pressed="true"]) {
|
|
1939
|
+
background: rgba(241, 245, 249, 1);
|
|
1940
|
+
color: #1e293b;
|
|
1941
|
+
border-color: rgba(100, 116, 139, 0.6);
|
|
1942
|
+
}
|
|
1943
|
+
.markdy-controls button:focus-visible {
|
|
1944
|
+
outline: 2px solid #3b82f6;
|
|
1945
|
+
outline-offset: 1px;
|
|
1946
|
+
}
|
|
1947
|
+
.markdy-controls button:active {
|
|
1948
|
+
transform: scale(0.96);
|
|
1949
|
+
}
|
|
1950
|
+
.markdy-footer a {
|
|
1951
|
+
transition: opacity 0.15s ease, color 0.15s ease;
|
|
1952
|
+
}
|
|
1953
|
+
.markdy-footer a:hover {
|
|
1954
|
+
opacity: 1 !important;
|
|
1955
|
+
color: #64748b !important;
|
|
1956
|
+
}
|
|
1957
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1958
|
+
.markdy-node,
|
|
1959
|
+
.markdy-beat-caption,
|
|
1960
|
+
.markdy-constellation-star {
|
|
1961
|
+
animation-duration: 0.001ms !important;
|
|
1962
|
+
animation-iteration-count: 1 !important;
|
|
1963
|
+
transition-duration: 0.001ms !important;
|
|
1964
|
+
}
|
|
1965
|
+
.markdy-node { opacity: 1 !important; transform: none !important; }
|
|
1966
|
+
.markdy-constellation-star { animation: none !important; opacity: 0.7 !important; }
|
|
1967
|
+
}
|
|
1968
|
+
@media print {
|
|
1969
|
+
.markdy-node { opacity: 1 !important; transform: none !important; }
|
|
1970
|
+
.markdy-scene-root::before, .markdy-scene-root::after { display: none !important; }
|
|
1971
|
+
}
|
|
1772
1972
|
`;
|
|
1773
1973
|
doc.head.appendChild(style);
|
|
1774
1974
|
}
|
|
@@ -1856,24 +2056,36 @@ function createDiagram(opts) {
|
|
|
1856
2056
|
const {
|
|
1857
2057
|
container,
|
|
1858
2058
|
code,
|
|
1859
|
-
autoplay = true,
|
|
1860
|
-
loop = true,
|
|
1861
|
-
copyright = true,
|
|
1862
2059
|
assets,
|
|
1863
2060
|
progressBar,
|
|
1864
2061
|
sceneBoundaryProgress,
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2062
|
+
progressColor,
|
|
2063
|
+
progressBarColor,
|
|
2064
|
+
playbackRate: initialPlaybackRate,
|
|
2065
|
+
controls: explicitControls,
|
|
2066
|
+
interactiveViewport: explicitInteractiveViewport,
|
|
2067
|
+
autoplay: explicitAutoplay,
|
|
2068
|
+
loop: explicitLoop,
|
|
2069
|
+
copyright: explicitCopyright,
|
|
1868
2070
|
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}`),
|
|
1869
2071
|
onTimeUpdate,
|
|
1870
2072
|
onPlayStateChange
|
|
1871
2073
|
} = opts;
|
|
1872
|
-
const showSceneBoundaryProgress = sceneBoundaryProgress ?? progressBar ?? true;
|
|
1873
2074
|
const { ast, plan } = parseAndCompile(code);
|
|
1874
2075
|
for (const w of ast.diagnostics) {
|
|
1875
2076
|
if (w.severity === "warning") onWarning(w);
|
|
1876
2077
|
}
|
|
2078
|
+
const controls = explicitControls ?? plan.meta.controls ?? false;
|
|
2079
|
+
const interactiveViewport = explicitInteractiveViewport ?? (explicitControls !== void 0 ? explicitControls : plan.meta.interactiveViewport ?? plan.meta.controls ?? false);
|
|
2080
|
+
const autoplay = explicitAutoplay ?? plan.meta.autoplay ?? true;
|
|
2081
|
+
const loop = explicitLoop ?? plan.meta.loop ?? true;
|
|
2082
|
+
const copyright = explicitCopyright ?? plan.meta.copyright ?? true;
|
|
2083
|
+
const rawPlaybackRate = initialPlaybackRate ?? plan.meta.playbackRate ?? DEFAULT_PLAYBACK_RATE;
|
|
2084
|
+
let playbackRate = Number.isFinite(rawPlaybackRate) && rawPlaybackRate > 0 ? rawPlaybackRate : DEFAULT_PLAYBACK_RATE;
|
|
2085
|
+
const showSceneBoundaryProgress = sceneBoundaryProgress === false || sceneBoundaryProgress === void 0 && progressBar === false ? false : plan.meta.progressColor === "none" ? false : true;
|
|
2086
|
+
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);
|
|
2087
|
+
const customColor = rawColor && rawColor.trim() !== "rainbow" ? rawColor.trim() : null;
|
|
2088
|
+
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%)";
|
|
1877
2089
|
const totalDurationMs = plan.duration * 1e3;
|
|
1878
2090
|
const durationSeconds = plan.duration;
|
|
1879
2091
|
const viewport = document.createElement("div");
|
|
@@ -1901,8 +2113,8 @@ function createDiagram(opts) {
|
|
|
1901
2113
|
function updateProgressBar(pct) {
|
|
1902
2114
|
if (!progressEl) return;
|
|
1903
2115
|
const deg = pct * 360;
|
|
1904
|
-
const
|
|
1905
|
-
progressEl.style.background = `conic-gradient(from ${tlAngleNorm}deg, ${
|
|
2116
|
+
const colorStops = customColor ? customColor.includes(",") ? customColor : `${customColor} 0deg, ${customColor}` : DEFAULT_RAINBOW;
|
|
2117
|
+
progressEl.style.background = `conic-gradient(from ${tlAngleNorm}deg, ${colorStops} ${deg}deg, transparent ${deg}deg)`;
|
|
1906
2118
|
progressEl.style.mask = "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)";
|
|
1907
2119
|
progressEl.style.webkitMask = progressEl.style.mask;
|
|
1908
2120
|
progressEl.style.maskComposite = "exclude";
|
|
@@ -1917,7 +2129,7 @@ function createDiagram(opts) {
|
|
|
1917
2129
|
Object.assign(footer.style, {
|
|
1918
2130
|
display: "flex",
|
|
1919
2131
|
alignItems: "center",
|
|
1920
|
-
justifyContent: "
|
|
2132
|
+
justifyContent: "space-between",
|
|
1921
2133
|
flexWrap: "wrap",
|
|
1922
2134
|
gap: "6px",
|
|
1923
2135
|
width: "100%",
|
|
@@ -1944,7 +2156,8 @@ function createDiagram(opts) {
|
|
|
1944
2156
|
color: "#999",
|
|
1945
2157
|
textDecoration: "none",
|
|
1946
2158
|
padding: "0",
|
|
1947
|
-
opacity: "0.7"
|
|
2159
|
+
opacity: "0.7",
|
|
2160
|
+
marginLeft: "auto"
|
|
1948
2161
|
});
|
|
1949
2162
|
ensureFooter().appendChild(badge);
|
|
1950
2163
|
}
|
|
@@ -2080,7 +2293,6 @@ function createDiagram(opts) {
|
|
|
2080
2293
|
anim.currentTime = 0;
|
|
2081
2294
|
}
|
|
2082
2295
|
let sceneMs = 0;
|
|
2083
|
-
let playbackRate = Number.isFinite(initialPlaybackRate) && initialPlaybackRate > 0 ? initialPlaybackRate : DEFAULT_PLAYBACK_RATE;
|
|
2084
2296
|
let lastRafTs = null;
|
|
2085
2297
|
let isPlaying = false;
|
|
2086
2298
|
let rafId = null;
|
|
@@ -2158,15 +2370,19 @@ function createDiagram(opts) {
|
|
|
2158
2370
|
}
|
|
2159
2371
|
function syncControls() {
|
|
2160
2372
|
if (controlsPlayButton) {
|
|
2161
|
-
|
|
2162
|
-
controlsPlayButton.
|
|
2373
|
+
const playing = isPlaying;
|
|
2374
|
+
controlsPlayButton.textContent = playing ? "Pause" : "Play";
|
|
2375
|
+
const playLabel = playing ? "Pause diagram" : "Play diagram";
|
|
2376
|
+
controlsPlayButton.setAttribute("aria-label", playLabel);
|
|
2377
|
+
controlsPlayButton.title = playLabel;
|
|
2163
2378
|
}
|
|
2164
2379
|
for (const button of controlsRateButtons) {
|
|
2165
2380
|
const rate = Number(button.dataset.rate ?? "1");
|
|
2166
2381
|
const active = Math.abs(rate - playbackRate) < 1e-3;
|
|
2167
2382
|
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
2168
|
-
button.style.background = active ? "
|
|
2169
|
-
button.style.color = active ? "#ffffff" : "#
|
|
2383
|
+
button.style.background = active ? "#1e293b" : "rgba(248, 250, 252, 0.92)";
|
|
2384
|
+
button.style.color = active ? "#ffffff" : "#475569";
|
|
2385
|
+
button.style.borderColor = active ? "#0f172a" : "rgba(148, 163, 184, 0.55)";
|
|
2170
2386
|
}
|
|
2171
2387
|
}
|
|
2172
2388
|
function emitPlayStateChange(playing) {
|
|
@@ -2288,7 +2504,7 @@ function createDiagram(opts) {
|
|
|
2288
2504
|
position: "static",
|
|
2289
2505
|
display: "flex",
|
|
2290
2506
|
alignItems: "center",
|
|
2291
|
-
justifyContent: "flex-
|
|
2507
|
+
justifyContent: "flex-start",
|
|
2292
2508
|
flexWrap: "wrap",
|
|
2293
2509
|
gap: "4px",
|
|
2294
2510
|
maxWidth: "100%",
|
|
@@ -2354,7 +2570,292 @@ function createDiagram(opts) {
|
|
|
2354
2570
|
if (autoplay) diagram.play();
|
|
2355
2571
|
return diagram;
|
|
2356
2572
|
}
|
|
2573
|
+
|
|
2574
|
+
// src/export/gif-encoder.ts
|
|
2575
|
+
var GIF_HEADER = "GIF89a";
|
|
2576
|
+
var PALETTE_RGB332 = (() => {
|
|
2577
|
+
const palette = new Uint8Array(256 * 3);
|
|
2578
|
+
for (let i = 0; i < 256; i++) {
|
|
2579
|
+
const r = Math.round((i >> 5 & 7) * 255 / 7);
|
|
2580
|
+
const g = Math.round((i >> 2 & 7) * 255 / 7);
|
|
2581
|
+
const b = Math.round((i & 3) * 255 / 3);
|
|
2582
|
+
palette[i * 3] = r;
|
|
2583
|
+
palette[i * 3 + 1] = g;
|
|
2584
|
+
palette[i * 3 + 2] = b;
|
|
2585
|
+
}
|
|
2586
|
+
return palette;
|
|
2587
|
+
})();
|
|
2588
|
+
function clamp2(value, min = 0, max = 255) {
|
|
2589
|
+
return value < min ? min : value > max ? max : value;
|
|
2590
|
+
}
|
|
2591
|
+
function nearestColorIndex(r, g, b) {
|
|
2592
|
+
return (clamp2(r) & 224 | (clamp2(g) & 224) >> 3 | (clamp2(b) & 192) >> 6) & 255;
|
|
2593
|
+
}
|
|
2594
|
+
function quantizeFrame(imageData, dither) {
|
|
2595
|
+
const { width, height, data } = imageData;
|
|
2596
|
+
const pixelCount = width * height;
|
|
2597
|
+
const out = new Uint8Array(pixelCount);
|
|
2598
|
+
if (!dither) {
|
|
2599
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
2600
|
+
const idx = i * 4;
|
|
2601
|
+
out[i] = (data[idx] & 224 | (data[idx + 1] & 224) >> 3 | (data[idx + 2] & 192) >> 6) & 255;
|
|
2602
|
+
}
|
|
2603
|
+
return out;
|
|
2604
|
+
}
|
|
2605
|
+
const errR = new Float32Array(pixelCount);
|
|
2606
|
+
const errG = new Float32Array(pixelCount);
|
|
2607
|
+
const errB = new Float32Array(pixelCount);
|
|
2608
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
2609
|
+
const idx = i * 4;
|
|
2610
|
+
errR[i] = data[idx];
|
|
2611
|
+
errG[i] = data[idx + 1];
|
|
2612
|
+
errB[i] = data[idx + 2];
|
|
2613
|
+
}
|
|
2614
|
+
for (let y = 0; y < height; y++) {
|
|
2615
|
+
for (let x = 0; x < width; x++) {
|
|
2616
|
+
const idx = y * width + x;
|
|
2617
|
+
const r = clamp2(Math.round(errR[idx]));
|
|
2618
|
+
const g = clamp2(Math.round(errG[idx]));
|
|
2619
|
+
const b = clamp2(Math.round(errB[idx]));
|
|
2620
|
+
const colorIdx = nearestColorIndex(r, g, b);
|
|
2621
|
+
out[idx] = colorIdx;
|
|
2622
|
+
const pr = PALETTE_RGB332[colorIdx * 3];
|
|
2623
|
+
const pg = PALETTE_RGB332[colorIdx * 3 + 1];
|
|
2624
|
+
const pb = PALETTE_RGB332[colorIdx * 3 + 2];
|
|
2625
|
+
const dr = errR[idx] - pr;
|
|
2626
|
+
const dg = errG[idx] - pg;
|
|
2627
|
+
const db = errB[idx] - pb;
|
|
2628
|
+
if (x + 1 < width) {
|
|
2629
|
+
errR[idx + 1] += dr * 7 / 16;
|
|
2630
|
+
errG[idx + 1] += dg * 7 / 16;
|
|
2631
|
+
errB[idx + 1] += db * 7 / 16;
|
|
2632
|
+
}
|
|
2633
|
+
if (y + 1 < height) {
|
|
2634
|
+
if (x > 0) {
|
|
2635
|
+
errR[idx + width - 1] += dr * 3 / 16;
|
|
2636
|
+
errG[idx + width - 1] += dg * 3 / 16;
|
|
2637
|
+
errB[idx + width - 1] += db * 3 / 16;
|
|
2638
|
+
}
|
|
2639
|
+
errR[idx + width] += dr * 5 / 16;
|
|
2640
|
+
errG[idx + width] += dg * 5 / 16;
|
|
2641
|
+
errB[idx + width] += db * 5 / 16;
|
|
2642
|
+
if (x + 1 < width) {
|
|
2643
|
+
errR[idx + width + 1] += dr * 1 / 16;
|
|
2644
|
+
errG[idx + width + 1] += dg * 1 / 16;
|
|
2645
|
+
errB[idx + width + 1] += db * 1 / 16;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
return out;
|
|
2651
|
+
}
|
|
2652
|
+
function lzwCompress(pixels, minCodeSize = 8) {
|
|
2653
|
+
const clearCode = 1 << minCodeSize;
|
|
2654
|
+
const endCode = clearCode + 1;
|
|
2655
|
+
let codeSize = minCodeSize + 1;
|
|
2656
|
+
let nextCode = endCode + 1;
|
|
2657
|
+
let dict = /* @__PURE__ */ new Map();
|
|
2658
|
+
for (let i = 0; i < clearCode; i++) {
|
|
2659
|
+
dict.set(String(i), i);
|
|
2660
|
+
}
|
|
2661
|
+
const codes = [clearCode];
|
|
2662
|
+
let prefix = String(pixels[0] ?? 0);
|
|
2663
|
+
for (let i = 1; i < pixels.length; i++) {
|
|
2664
|
+
const suffix = String(pixels[i]);
|
|
2665
|
+
const combo = `${prefix},${suffix}`;
|
|
2666
|
+
if (dict.has(combo)) {
|
|
2667
|
+
prefix = combo;
|
|
2668
|
+
continue;
|
|
2669
|
+
}
|
|
2670
|
+
codes.push(dict.get(prefix));
|
|
2671
|
+
dict.set(combo, nextCode++);
|
|
2672
|
+
prefix = suffix;
|
|
2673
|
+
if (nextCode === 1 << codeSize && codeSize < 12) {
|
|
2674
|
+
codeSize++;
|
|
2675
|
+
}
|
|
2676
|
+
if (nextCode >= 4095) {
|
|
2677
|
+
codes.push(clearCode);
|
|
2678
|
+
dict = /* @__PURE__ */ new Map();
|
|
2679
|
+
for (let j = 0; j < clearCode; j++) dict.set(String(j), j);
|
|
2680
|
+
codeSize = minCodeSize + 1;
|
|
2681
|
+
nextCode = endCode + 1;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
codes.push(dict.get(prefix));
|
|
2685
|
+
codes.push(endCode);
|
|
2686
|
+
const bytes = [];
|
|
2687
|
+
let bitBuffer = 0;
|
|
2688
|
+
let bitCount = 0;
|
|
2689
|
+
codeSize = minCodeSize + 1;
|
|
2690
|
+
nextCode = endCode + 1;
|
|
2691
|
+
for (const code of codes) {
|
|
2692
|
+
bitBuffer |= code << bitCount;
|
|
2693
|
+
bitCount += codeSize;
|
|
2694
|
+
while (bitCount >= 8) {
|
|
2695
|
+
bytes.push(bitBuffer & 255);
|
|
2696
|
+
bitBuffer >>= 8;
|
|
2697
|
+
bitCount -= 8;
|
|
2698
|
+
}
|
|
2699
|
+
if (code === clearCode) {
|
|
2700
|
+
codeSize = minCodeSize + 1;
|
|
2701
|
+
nextCode = endCode + 1;
|
|
2702
|
+
} else if (code !== endCode) {
|
|
2703
|
+
nextCode++;
|
|
2704
|
+
if (nextCode === 1 << codeSize && codeSize < 12) {
|
|
2705
|
+
codeSize++;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (bitCount > 0) {
|
|
2710
|
+
bytes.push(bitBuffer & 255);
|
|
2711
|
+
}
|
|
2712
|
+
return new Uint8Array(bytes);
|
|
2713
|
+
}
|
|
2714
|
+
function encodeGifSequence(frames, options = {}) {
|
|
2715
|
+
if (frames.length === 0) {
|
|
2716
|
+
throw new Error("Cannot encode GIF without frames");
|
|
2717
|
+
}
|
|
2718
|
+
const { width, height } = frames[0].imageData;
|
|
2719
|
+
const dither = options.dither ?? true;
|
|
2720
|
+
const buffer = [];
|
|
2721
|
+
const pushString = (str) => {
|
|
2722
|
+
for (let i = 0; i < str.length; i++) buffer.push(str.charCodeAt(i));
|
|
2723
|
+
};
|
|
2724
|
+
const pushU16 = (val) => {
|
|
2725
|
+
buffer.push(val & 255, val >> 8 & 255);
|
|
2726
|
+
};
|
|
2727
|
+
pushString(GIF_HEADER);
|
|
2728
|
+
pushU16(width);
|
|
2729
|
+
pushU16(height);
|
|
2730
|
+
buffer.push(247);
|
|
2731
|
+
buffer.push(0);
|
|
2732
|
+
buffer.push(0);
|
|
2733
|
+
for (let i = 0; i < PALETTE_RGB332.length; i++) {
|
|
2734
|
+
buffer.push(PALETTE_RGB332[i]);
|
|
2735
|
+
}
|
|
2736
|
+
if (options.loop !== false) {
|
|
2737
|
+
buffer.push(33, 255, 11);
|
|
2738
|
+
pushString("NETSCAPE2.0");
|
|
2739
|
+
buffer.push(3, 1, 0, 0, 0);
|
|
2740
|
+
}
|
|
2741
|
+
for (const frame of frames) {
|
|
2742
|
+
const quantized = quantizeFrame(frame.imageData, dither);
|
|
2743
|
+
const compressed = lzwCompress(quantized);
|
|
2744
|
+
const delayHundredths = Math.max(2, Math.round(frame.delayMs / 10));
|
|
2745
|
+
buffer.push(33, 249, 4, 4);
|
|
2746
|
+
pushU16(delayHundredths);
|
|
2747
|
+
buffer.push(0, 0);
|
|
2748
|
+
buffer.push(44);
|
|
2749
|
+
pushU16(0);
|
|
2750
|
+
pushU16(0);
|
|
2751
|
+
pushU16(width);
|
|
2752
|
+
pushU16(height);
|
|
2753
|
+
buffer.push(0);
|
|
2754
|
+
buffer.push(8);
|
|
2755
|
+
for (let offset = 0; offset < compressed.length; offset += 255) {
|
|
2756
|
+
const chunk = compressed.slice(offset, offset + 255);
|
|
2757
|
+
buffer.push(chunk.length, ...chunk);
|
|
2758
|
+
}
|
|
2759
|
+
buffer.push(0);
|
|
2760
|
+
}
|
|
2761
|
+
buffer.push(59);
|
|
2762
|
+
return new Uint8Array(buffer);
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
// src/export/svg-exporter.ts
|
|
2766
|
+
function exportDiagramAsVectorSvg(containerEl, options = {}) {
|
|
2767
|
+
const svgEl = containerEl.querySelector("svg");
|
|
2768
|
+
if (!svgEl) throw new Error("No Markdy SVG element found in container");
|
|
2769
|
+
const clonedSvg = svgEl.cloneNode(true);
|
|
2770
|
+
const width = svgEl.getAttribute("width") || String(svgEl.clientWidth || 800);
|
|
2771
|
+
const height = svgEl.getAttribute("height") || String(svgEl.clientHeight || 400);
|
|
2772
|
+
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
2773
|
+
clonedSvg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
2774
|
+
clonedSvg.setAttribute("width", width);
|
|
2775
|
+
clonedSvg.setAttribute("height", height);
|
|
2776
|
+
clonedSvg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
2777
|
+
if (options.transparentBackground) {
|
|
2778
|
+
const bgRect = clonedSvg.querySelector("rect");
|
|
2779
|
+
if (bgRect) bgRect.remove();
|
|
2780
|
+
}
|
|
2781
|
+
if (options.includeThemeStyles !== false) {
|
|
2782
|
+
const styleEl = document.createElementNS("http://www.w3.org/2000/svg", "style");
|
|
2783
|
+
styleEl.textContent = `
|
|
2784
|
+
text { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
|
2785
|
+
.markdy-node { transition: opacity 0.3s ease; }
|
|
2786
|
+
`;
|
|
2787
|
+
clonedSvg.insertBefore(styleEl, clonedSvg.firstChild);
|
|
2788
|
+
}
|
|
2789
|
+
const serializer = new XMLSerializer();
|
|
2790
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2791
|
+
` + serializer.serializeToString(clonedSvg);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// src/presentation-controller.ts
|
|
2795
|
+
var DiagramPresentationController = class {
|
|
2796
|
+
diagram;
|
|
2797
|
+
plan;
|
|
2798
|
+
currentBeatIndex = 0;
|
|
2799
|
+
constructor(diagram, plan, options = {}) {
|
|
2800
|
+
this.diagram = diagram;
|
|
2801
|
+
this.plan = plan;
|
|
2802
|
+
if (options.enableKeyboard !== false && typeof window !== "undefined") {
|
|
2803
|
+
this.attachKeyboardListener();
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
nextBeat() {
|
|
2807
|
+
if (this.currentBeatIndex < this.plan.beats.length - 1) {
|
|
2808
|
+
this.currentBeatIndex++;
|
|
2809
|
+
const beat = this.plan.beats[this.currentBeatIndex];
|
|
2810
|
+
this.diagram.seek(beat.start);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
prevBeat() {
|
|
2814
|
+
if (this.currentBeatIndex > 0) {
|
|
2815
|
+
this.currentBeatIndex--;
|
|
2816
|
+
const beat = this.plan.beats[this.currentBeatIndex];
|
|
2817
|
+
this.diagram.seek(beat.start);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
getCurrentBeatIndex() {
|
|
2821
|
+
return this.currentBeatIndex;
|
|
2822
|
+
}
|
|
2823
|
+
togglePlay() {
|
|
2824
|
+
this.diagram.play();
|
|
2825
|
+
}
|
|
2826
|
+
setSpeed(rate) {
|
|
2827
|
+
this.diagram.setPlaybackRate(rate);
|
|
2828
|
+
}
|
|
2829
|
+
attachKeyboardListener() {
|
|
2830
|
+
window.addEventListener("keydown", (e) => {
|
|
2831
|
+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
|
2832
|
+
switch (e.key) {
|
|
2833
|
+
case "ArrowRight":
|
|
2834
|
+
case "PageDown":
|
|
2835
|
+
this.nextBeat();
|
|
2836
|
+
break;
|
|
2837
|
+
case "ArrowLeft":
|
|
2838
|
+
case "PageUp":
|
|
2839
|
+
this.prevBeat();
|
|
2840
|
+
break;
|
|
2841
|
+
case " ":
|
|
2842
|
+
e.preventDefault();
|
|
2843
|
+
this.togglePlay();
|
|
2844
|
+
break;
|
|
2845
|
+
case "1":
|
|
2846
|
+
this.setSpeed(1);
|
|
2847
|
+
break;
|
|
2848
|
+
case "2":
|
|
2849
|
+
this.setSpeed(2);
|
|
2850
|
+
break;
|
|
2851
|
+
}
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2357
2855
|
export {
|
|
2856
|
+
DiagramPresentationController,
|
|
2358
2857
|
ICON_REGISTRY,
|
|
2359
|
-
createDiagram
|
|
2858
|
+
createDiagram,
|
|
2859
|
+
encodeGifSequence,
|
|
2860
|
+
exportDiagramAsVectorSvg
|
|
2360
2861
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.21",
|
|
4
4
|
"description": "Browser renderer for diagram-native animated MarkdyScript architecture diagrams, built on the Web Animations API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@markdy/core": "0.8.
|
|
47
|
+
"@markdy/core": "0.8.21"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"jsdom": "^29.1.1",
|
|
51
51
|
"tsup": "^8.5.1",
|
|
52
52
|
"typescript": "^5.9.3",
|
|
53
53
|
"vitest": "^4.1.7",
|
|
54
|
-
"@markdy/stdlib-systems": "0.8.
|
|
54
|
+
"@markdy/stdlib-systems": "0.8.21"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup",
|