@markdy/renderer-dom 0.8.1 → 0.8.3
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 +8 -2
- package/dist/index.d.ts +13 -0
- package/dist/index.js +374 -65
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ Web Animations API renderer for [MarkdyScript](../../docs/SYNTAX.md) scenes. Tra
|
|
|
8
8
|
- **Auto-layout diagrams** — renders positioned nodes and orthogonal, obstacle-aware edges from a compiled `RenderPlan`
|
|
9
9
|
- **Flow edges** — `->` request, `<-` response, `~>` event, `--` dependency, each with its own stroke, plus a pulse that travels the edge as it draws
|
|
10
10
|
- **Beat-driven cues** — `show`, `hide`, `glow`, and `focus`, sequenced by named beats
|
|
11
|
+
- **Semantic node cards** — compact SVG glyphs for browsers, services, gateways, queues, workers, databases, storage, CDN, security, platform, and more
|
|
11
12
|
- **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
|
|
13
|
+
- **Playback-rate controls** — set timeline speed to slow down or speed up diagrams without rebuilding animations
|
|
12
14
|
- **Semantic themes** — `midnight` and `paper`, with per-role node colors
|
|
13
15
|
- **Single dependency** — only `@markdy/core`
|
|
14
16
|
|
|
@@ -40,7 +42,7 @@ import { createPlayer } from "@markdy/renderer-dom";
|
|
|
40
42
|
const player = createPlayer({
|
|
41
43
|
container: document.getElementById("scene")!,
|
|
42
44
|
code: `
|
|
43
|
-
scene "Request" theme=
|
|
45
|
+
scene "Request" theme=paper
|
|
44
46
|
browser Web
|
|
45
47
|
service API
|
|
46
48
|
beat main:
|
|
@@ -68,7 +70,9 @@ player.destroy(); // clean up DOM + cancel animations
|
|
|
68
70
|
| `autoplay` | `boolean` | `true` | Start playing immediately |
|
|
69
71
|
| `loop` | `boolean` | `true` | Loop the animation when it reaches the end |
|
|
70
72
|
| `copyright` | `boolean` | `true` | Show a small "Powered by Markdy" badge below the animation |
|
|
71
|
-
| `progressBar` | `boolean` | `true` |
|
|
73
|
+
| `progressBar` | `boolean` | `true` | Deprecated compatibility flag for the rainbow scene-boundary progress bar |
|
|
74
|
+
| `sceneBoundaryProgress` | `boolean` | `progressBar ?? true` | Preferred flag for the rainbow scene-boundary progress bar |
|
|
75
|
+
| `playbackRate` | `number` | `1` | Timeline speed multiplier |
|
|
72
76
|
| `onWarning` | `(warning: Diagnostic) => void` | `console.warn` | Called for each soft parse warning |
|
|
73
77
|
| `onTimeUpdate` | `(seconds: number, durationSeconds: number) => void` | — | Called whenever playback or seek changes the current time |
|
|
74
78
|
| `onPlayStateChange` | `(playing: boolean) => void` | — | Called when playback starts or pauses |
|
|
@@ -80,6 +84,8 @@ player.destroy(); // clean up DOM + cancel animations
|
|
|
80
84
|
| `play()` | Start or resume playback |
|
|
81
85
|
| `pause()` | Pause at current position |
|
|
82
86
|
| `seek(seconds)` | Jump to a specific time |
|
|
87
|
+
| `setPlaybackRate(rate)` | Change timeline speed; ignores non-positive or non-finite values |
|
|
88
|
+
| `playbackRate()` | Current timeline speed multiplier |
|
|
83
89
|
| `currentTime()` | Current playback position in seconds |
|
|
84
90
|
| `duration()` | Total scene duration in seconds |
|
|
85
91
|
| `isPlaying()` | Whether the scene is currently playing |
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,18 @@ interface PlayerOptions {
|
|
|
10
10
|
autoplay?: boolean;
|
|
11
11
|
loop?: boolean;
|
|
12
12
|
copyright?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Maps node `image=`/`logo=` values to resolved URLs. Lets a host (Astro,
|
|
15
|
+
* MDX) remap DSL asset references to CDN/data URLs. Values not present here
|
|
16
|
+
* are used verbatim as the image `src`.
|
|
17
|
+
*/
|
|
18
|
+
assets?: Record<string, string>;
|
|
19
|
+
/** @deprecated Prefer sceneBoundaryProgress. */
|
|
13
20
|
progressBar?: boolean;
|
|
21
|
+
/** Show rainbow progress around scene boundary. Defaults to true. */
|
|
22
|
+
sceneBoundaryProgress?: boolean;
|
|
23
|
+
/** Playback speed multiplier. Defaults to 1. */
|
|
24
|
+
playbackRate?: number;
|
|
14
25
|
onWarning?: (warning: Diagnostic) => void;
|
|
15
26
|
onTimeUpdate?: (seconds: number, durationSeconds: number) => void;
|
|
16
27
|
onPlayStateChange?: (playing: boolean) => void;
|
|
@@ -19,6 +30,8 @@ interface Player {
|
|
|
19
30
|
play(): void;
|
|
20
31
|
pause(): void;
|
|
21
32
|
seek(seconds: number): void;
|
|
33
|
+
setPlaybackRate(rate: number): void;
|
|
34
|
+
playbackRate(): number;
|
|
22
35
|
currentTime(): number;
|
|
23
36
|
duration(): number;
|
|
24
37
|
isPlaying(): boolean;
|
package/dist/index.js
CHANGED
|
@@ -233,13 +233,13 @@ function ensureDefs(svg, theme, id) {
|
|
|
233
233
|
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
234
234
|
arrow.setAttribute("id", `${id}-arrow-${kind}`);
|
|
235
235
|
arrow.setAttribute("viewBox", "0 0 10 10");
|
|
236
|
-
arrow.setAttribute("refX", "8");
|
|
236
|
+
arrow.setAttribute("refX", "8.5");
|
|
237
237
|
arrow.setAttribute("refY", "5");
|
|
238
|
-
arrow.setAttribute("markerWidth", "
|
|
239
|
-
arrow.setAttribute("markerHeight", "
|
|
238
|
+
arrow.setAttribute("markerWidth", "7");
|
|
239
|
+
arrow.setAttribute("markerHeight", "7");
|
|
240
240
|
arrow.setAttribute("orient", "auto-start-reverse");
|
|
241
241
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
242
|
-
path.setAttribute("d", "M 1 1 L 9 5 L 1
|
|
242
|
+
path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
|
|
243
243
|
path.setAttribute("fill", color);
|
|
244
244
|
arrow.appendChild(path);
|
|
245
245
|
defs.appendChild(arrow);
|
|
@@ -260,19 +260,21 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
260
260
|
path.setAttribute("d", d);
|
|
261
261
|
path.setAttribute("fill", "none");
|
|
262
262
|
path.setAttribute("stroke", color);
|
|
263
|
-
path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2
|
|
263
|
+
path.setAttribute("stroke-width", kind === "dependency" ? "1.5" : "2");
|
|
264
264
|
path.setAttribute("stroke-linejoin", "round");
|
|
265
265
|
path.setAttribute("stroke-linecap", "round");
|
|
266
266
|
if (style.dash) path.setAttribute("stroke-dasharray", style.dash);
|
|
267
267
|
if (style.marker !== "none") {
|
|
268
268
|
path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
|
|
269
269
|
}
|
|
270
|
-
|
|
270
|
+
if (kind !== "dependency") {
|
|
271
|
+
path.style.filter = `drop-shadow(0 0 3px ${color}33)`;
|
|
272
|
+
}
|
|
271
273
|
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
272
|
-
dot.setAttribute("r", "
|
|
274
|
+
dot.setAttribute("r", "3.5");
|
|
273
275
|
dot.setAttribute("fill", color);
|
|
274
276
|
dot.style.opacity = "0";
|
|
275
|
-
dot.style.filter = `drop-shadow(0 0
|
|
277
|
+
dot.style.filter = `drop-shadow(0 0 4px ${color}aa)`;
|
|
276
278
|
group.append(path, dot);
|
|
277
279
|
let labelEl;
|
|
278
280
|
let labelRect;
|
|
@@ -280,6 +282,20 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
280
282
|
const textWidth = label.length * 6.6 + 10;
|
|
281
283
|
const placement = placeFlowLabel(points, textWidth, labelObstacles, bounds);
|
|
282
284
|
labelRect = placement.rect;
|
|
285
|
+
const plate = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
286
|
+
const padX = 7;
|
|
287
|
+
const halfW = textWidth / 2;
|
|
288
|
+
plate.setAttribute("x", String(placement.x - halfW - padX));
|
|
289
|
+
plate.setAttribute("y", String(placement.y - 9));
|
|
290
|
+
plate.setAttribute("width", String(textWidth + padX * 2));
|
|
291
|
+
plate.setAttribute("height", "18");
|
|
292
|
+
plate.setAttribute("rx", "6");
|
|
293
|
+
plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
|
|
294
|
+
plate.setAttribute("fill-opacity", "0.9");
|
|
295
|
+
plate.setAttribute("stroke", theme.hairline ?? `color-mix(in srgb, ${theme.border} 60%, transparent)`);
|
|
296
|
+
plate.setAttribute("stroke-width", "1");
|
|
297
|
+
plate.style.opacity = "0";
|
|
298
|
+
group.appendChild(plate);
|
|
283
299
|
labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
284
300
|
labelEl.setAttribute("x", String(placement.x));
|
|
285
301
|
labelEl.setAttribute("y", String(placement.y));
|
|
@@ -287,14 +303,11 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
287
303
|
labelEl.setAttribute("dominant-baseline", "middle");
|
|
288
304
|
labelEl.setAttribute("font-size", "11");
|
|
289
305
|
labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, monospace");
|
|
290
|
-
labelEl.setAttribute("fill", theme.
|
|
291
|
-
labelEl.setAttribute("stroke", theme.canvas);
|
|
292
|
-
labelEl.setAttribute("stroke-width", "3");
|
|
293
|
-
labelEl.setAttribute("paint-order", "stroke");
|
|
294
|
-
labelEl.setAttribute("stroke-linejoin", "round");
|
|
306
|
+
labelEl.setAttribute("fill", theme.text);
|
|
295
307
|
labelEl.textContent = label;
|
|
296
308
|
labelEl.style.opacity = "0";
|
|
297
309
|
group.appendChild(labelEl);
|
|
310
|
+
labelEl.__plate = plate;
|
|
298
311
|
}
|
|
299
312
|
svg.appendChild(group);
|
|
300
313
|
return { group, path, label: labelEl, dot, pathLen: len, points, labelRect };
|
|
@@ -332,6 +345,10 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
332
345
|
);
|
|
333
346
|
if (label) {
|
|
334
347
|
anims.push(label.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
348
|
+
const plate = label.__plate;
|
|
349
|
+
if (plate) {
|
|
350
|
+
anims.push(plate.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
351
|
+
}
|
|
335
352
|
}
|
|
336
353
|
if (points.length >= 2) {
|
|
337
354
|
anims.push(
|
|
@@ -448,62 +465,118 @@ function ensureNodeStyles(doc) {
|
|
|
448
465
|
box-sizing: border-box;
|
|
449
466
|
width: var(--md-node-w, 184px);
|
|
450
467
|
height: var(--md-node-h, 88px);
|
|
451
|
-
border-radius:
|
|
452
|
-
|
|
453
|
-
|
|
468
|
+
border-radius: 12px;
|
|
469
|
+
background:
|
|
470
|
+
linear-gradient(180deg,
|
|
471
|
+
var(--md-node-surface-raised, color-mix(in srgb, var(--md-surface-raised) 88%, #ffffff 12%)),
|
|
472
|
+
var(--md-node-surface, var(--md-surface)));
|
|
454
473
|
color: var(--md-text);
|
|
455
|
-
box-shadow:
|
|
474
|
+
box-shadow:
|
|
475
|
+
0 1px 1px color-mix(in srgb, var(--md-shadow, rgba(2, 6, 23, 0.5)) 50%, transparent),
|
|
476
|
+
0 10px 22px -12px var(--md-shadow, rgba(2, 6, 23, 0.55)),
|
|
477
|
+
inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent)),
|
|
478
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
456
479
|
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
457
480
|
overflow: hidden;
|
|
458
481
|
opacity: 0;
|
|
459
482
|
transform: translateY(8px);
|
|
460
|
-
transition: box-shadow 0.
|
|
483
|
+
transition: box-shadow 0.25s ease, transform 0.25s ease;
|
|
461
484
|
}
|
|
462
485
|
.markdy-node[data-visible="1"] {
|
|
463
486
|
opacity: 1;
|
|
464
487
|
transform: translateY(0);
|
|
465
488
|
}
|
|
466
489
|
.markdy-node[data-focused="1"] {
|
|
467
|
-
box-shadow:
|
|
490
|
+
box-shadow:
|
|
491
|
+
0 2px 4px rgba(2, 6, 23, 0.32),
|
|
492
|
+
0 16px 34px -14px rgba(2, 6, 23, 0.6),
|
|
493
|
+
inset 0 0 0 1px color-mix(in srgb, var(--md-accent) 65%, transparent),
|
|
494
|
+
0 0 0 3px color-mix(in srgb, var(--md-accent) 20%, transparent);
|
|
468
495
|
}
|
|
469
496
|
.markdy-node[data-glow="1"] {
|
|
470
|
-
box-shadow:
|
|
497
|
+
box-shadow:
|
|
498
|
+
0 2px 4px rgba(2, 6, 23, 0.32),
|
|
499
|
+
0 0 0 1px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 55%, transparent),
|
|
500
|
+
0 0 20px -2px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 42%, transparent),
|
|
501
|
+
inset 0 0 18px -8px color-mix(in srgb, var(--md-glow-color, var(--md-accent)) 45%, transparent);
|
|
471
502
|
}
|
|
472
|
-
.markdy-node__rail {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
503
|
+
.markdy-node__rail { display: none; }
|
|
504
|
+
.markdy-node__type { display: none; }
|
|
505
|
+
.markdy-node__body {
|
|
506
|
+
height: 100%;
|
|
507
|
+
padding: 0 13px;
|
|
508
|
+
display: flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
gap: 10px;
|
|
511
|
+
min-width: 0;
|
|
512
|
+
}
|
|
513
|
+
.markdy-node__icon {
|
|
514
|
+
flex: 0 0 auto;
|
|
515
|
+
width: 30px;
|
|
516
|
+
height: 30px;
|
|
517
|
+
border-radius: 8px;
|
|
518
|
+
display: flex;
|
|
519
|
+
align-items: center;
|
|
520
|
+
justify-content: center;
|
|
521
|
+
color: var(--md-role-color, var(--md-accent));
|
|
522
|
+
background:
|
|
523
|
+
linear-gradient(180deg,
|
|
524
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 24%, transparent),
|
|
525
|
+
color-mix(in srgb, var(--md-role-color, var(--md-accent)) 11%, transparent));
|
|
526
|
+
box-shadow:
|
|
527
|
+
inset 0 0 0 1px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 34%, transparent),
|
|
528
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
|
529
|
+
}
|
|
530
|
+
.markdy-node__icon svg {
|
|
531
|
+
width: 17px;
|
|
532
|
+
height: 17px;
|
|
533
|
+
display: block;
|
|
534
|
+
stroke: currentColor;
|
|
535
|
+
}
|
|
536
|
+
.markdy-node__icon[data-media="image"] {
|
|
537
|
+
background: color-mix(in srgb, var(--md-surface-raised) 68%, #ffffff 32%);
|
|
538
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-border) 60%, transparent);
|
|
539
|
+
padding: 3px;
|
|
540
|
+
overflow: hidden;
|
|
541
|
+
}
|
|
542
|
+
.markdy-node__icon img {
|
|
543
|
+
width: 100%;
|
|
544
|
+
height: 100%;
|
|
545
|
+
object-fit: contain;
|
|
546
|
+
border-radius: 6px;
|
|
547
|
+
display: block;
|
|
548
|
+
}
|
|
549
|
+
.markdy-node__icon[data-fit="cover"] img {
|
|
550
|
+
object-fit: cover;
|
|
551
|
+
}
|
|
552
|
+
.markdy-node[data-icon="decision"] .markdy-node__icon,
|
|
553
|
+
.markdy-node[data-icon="flow"] .markdy-node__icon {
|
|
554
|
+
border-radius: 999px;
|
|
488
555
|
}
|
|
489
556
|
.markdy-node__label {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
557
|
+
flex: 1 1 auto;
|
|
558
|
+
min-width: 0;
|
|
559
|
+
padding: 0;
|
|
560
|
+
font-size: 14px;
|
|
561
|
+
font-weight: 600;
|
|
562
|
+
letter-spacing: -0.01em;
|
|
563
|
+
line-height: 1.18;
|
|
564
|
+
display: -webkit-box;
|
|
565
|
+
-webkit-box-orient: vertical;
|
|
566
|
+
-webkit-line-clamp: 3;
|
|
567
|
+
line-clamp: 3;
|
|
495
568
|
overflow: hidden;
|
|
496
|
-
|
|
569
|
+
overflow-wrap: anywhere;
|
|
570
|
+
word-break: break-word;
|
|
571
|
+
text-wrap: balance;
|
|
497
572
|
}
|
|
498
|
-
.markdy-node[data-role="client"] { border-radius:
|
|
499
|
-
.markdy-node[data-role="data"] { border-radius:
|
|
500
|
-
.markdy-node[data-role="flow"] { transform: rotate(0deg); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
|
|
501
|
-
.markdy-node[data-role="network"] { clip-path: polygon(8% 0%, 92% 0%, 100% 50%, 92% 100%, 8% 100%, 0% 50%); }
|
|
573
|
+
.markdy-node[data-role="client"] { border-radius: 15px 15px 9px 9px; }
|
|
574
|
+
.markdy-node[data-role="data"] { border-radius: 12px 12px 20px 20px; }
|
|
502
575
|
.markdy-scene-title {
|
|
503
576
|
position: absolute;
|
|
504
|
-
left:
|
|
505
|
-
top:
|
|
506
|
-
right:
|
|
577
|
+
left: 44px;
|
|
578
|
+
top: 26px;
|
|
579
|
+
right: 44px;
|
|
507
580
|
font-size: 32px;
|
|
508
581
|
font-weight: 700;
|
|
509
582
|
line-height: 1.2;
|
|
@@ -519,7 +592,223 @@ function ensureNodeStyles(doc) {
|
|
|
519
592
|
`;
|
|
520
593
|
doc.head.appendChild(style);
|
|
521
594
|
}
|
|
522
|
-
|
|
595
|
+
var ICONS = {
|
|
596
|
+
compute: [
|
|
597
|
+
["rect", { x: "5", y: "5", width: "14", height: "14", rx: "3" }],
|
|
598
|
+
["path", { d: "M9 9h6v6H9zM9 2.5v2.5M15 2.5v2.5M9 19v2.5M15 19v2.5M2.5 9h2.5M2.5 15h2.5M19 9h2.5M19 15h2.5" }]
|
|
599
|
+
],
|
|
600
|
+
user: [
|
|
601
|
+
["circle", { cx: "12", cy: "8", r: "3.4" }],
|
|
602
|
+
["path", { d: "M5.5 20a6.5 6.5 0 0 1 13 0" }]
|
|
603
|
+
],
|
|
604
|
+
browser: [
|
|
605
|
+
["rect", { x: "3", y: "5", width: "18", height: "14", rx: "2.5" }],
|
|
606
|
+
["path", { d: "M3 9h18" }],
|
|
607
|
+
["path", { d: "M7 7h.01M10 7h.01" }]
|
|
608
|
+
],
|
|
609
|
+
service: [
|
|
610
|
+
["path", { d: "M12 3 4.5 7.2 12 11.4l7.5-4.2L12 3Z" }],
|
|
611
|
+
["path", { d: "M4.5 12 12 16.2 19.5 12" }],
|
|
612
|
+
["path", { d: "M4.5 16.8 12 21l7.5-4.2" }]
|
|
613
|
+
],
|
|
614
|
+
gateway: [
|
|
615
|
+
["circle", { cx: "5", cy: "12", r: "2" }],
|
|
616
|
+
["circle", { cx: "19", cy: "6", r: "2" }],
|
|
617
|
+
["circle", { cx: "19", cy: "18", r: "2" }],
|
|
618
|
+
["path", { d: "M7 12h4l5.5-5" }],
|
|
619
|
+
["path", { d: "M11 12l5.5 5" }]
|
|
620
|
+
],
|
|
621
|
+
queue: [
|
|
622
|
+
["rect", { x: "5", y: "5", width: "14", height: "4", rx: "1.4" }],
|
|
623
|
+
["rect", { x: "5", y: "10", width: "14", height: "4", rx: "1.4" }],
|
|
624
|
+
["rect", { x: "5", y: "15", width: "14", height: "4", rx: "1.4" }]
|
|
625
|
+
],
|
|
626
|
+
worker: [
|
|
627
|
+
["circle", { cx: "12", cy: "12", r: "3.2" }],
|
|
628
|
+
["path", { d: "M12 2.8v3M12 18.2v3M2.8 12h3M18.2 12h3M5.5 5.5l2.1 2.1M16.4 16.4l2.1 2.1M18.5 5.5l-2.1 2.1M7.6 16.4l-2.1 2.1" }]
|
|
629
|
+
],
|
|
630
|
+
database: [
|
|
631
|
+
["ellipse", { cx: "12", cy: "5.5", rx: "7", ry: "3" }],
|
|
632
|
+
["path", { d: "M5 5.5v10c0 1.7 3.1 3 7 3s7-1.3 7-3v-10" }],
|
|
633
|
+
["path", { d: "M5 10.5c0 1.7 3.1 3 7 3s7-1.3 7-3" }]
|
|
634
|
+
],
|
|
635
|
+
storage: [
|
|
636
|
+
["path", { d: "M4 8.5 12 4l8 4.5v7L12 20l-8-4.5v-7Z" }],
|
|
637
|
+
["path", { d: "M4 8.5 12 13l8-4.5" }],
|
|
638
|
+
["path", { d: "M12 13v7" }]
|
|
639
|
+
],
|
|
640
|
+
cdn: [
|
|
641
|
+
["circle", { cx: "12", cy: "12", r: "8" }],
|
|
642
|
+
["path", { d: "M4 12h16M12 4c2.2 2.1 3.3 4.8 3.3 8S14.2 17.9 12 20M12 4c-2.2 2.1-3.3 4.8-3.3 8S9.8 17.9 12 20" }]
|
|
643
|
+
],
|
|
644
|
+
cache: [
|
|
645
|
+
["path", { d: "M13 2 5 13h6l-1 9 9-13h-6l0-7Z" }]
|
|
646
|
+
],
|
|
647
|
+
code: [
|
|
648
|
+
["path", { d: "m9 18-6-6 6-6" }],
|
|
649
|
+
["path", { d: "m15 6 6 6-6 6" }],
|
|
650
|
+
["path", { d: "m14 4-4 16" }]
|
|
651
|
+
],
|
|
652
|
+
messaging: [
|
|
653
|
+
["path", { d: "M4 6h16v10H8l-4 4V6Z" }],
|
|
654
|
+
["path", { d: "M8 10h8M8 13h5" }]
|
|
655
|
+
],
|
|
656
|
+
network: [
|
|
657
|
+
["circle", { cx: "6", cy: "7", r: "2" }],
|
|
658
|
+
["circle", { cx: "18", cy: "7", r: "2" }],
|
|
659
|
+
["circle", { cx: "12", cy: "18", r: "2" }],
|
|
660
|
+
["path", { d: "M8 8.5 11 16M16 8.5 13 16M8 7h8" }]
|
|
661
|
+
],
|
|
662
|
+
platform: [
|
|
663
|
+
["rect", { x: "4", y: "5", width: "16", height: "14", rx: "2.5" }],
|
|
664
|
+
["path", { d: "M8 9h8M8 13h8M8 17h4" }]
|
|
665
|
+
],
|
|
666
|
+
security: [
|
|
667
|
+
["path", { d: "M12 3 5 6v5c0 4.5 3 7.7 7 10 4-2.3 7-5.5 7-10V6l-7-3Z" }],
|
|
668
|
+
["path", { d: "M9.5 12.5 11.2 14 15 10" }]
|
|
669
|
+
],
|
|
670
|
+
delivery: [
|
|
671
|
+
["path", { d: "M4 7h10" }],
|
|
672
|
+
["path", { d: "M4 12h16" }],
|
|
673
|
+
["path", { d: "M4 17h10" }],
|
|
674
|
+
["path", { d: "m16 7 4 5-4 5" }]
|
|
675
|
+
],
|
|
676
|
+
observability: [
|
|
677
|
+
["path", { d: "M4 14s2.5-5 8-5 8 5 8 5-2.5 5-8 5-8-5-8-5Z" }],
|
|
678
|
+
["circle", { cx: "12", cy: "14", r: "2.5" }]
|
|
679
|
+
],
|
|
680
|
+
distributed: [
|
|
681
|
+
["circle", { cx: "6", cy: "12", r: "2.5" }],
|
|
682
|
+
["circle", { cx: "18", cy: "6", r: "2.5" }],
|
|
683
|
+
["circle", { cx: "18", cy: "18", r: "2.5" }],
|
|
684
|
+
["path", { d: "M8.4 11 15.6 7M8.4 13 15.6 17" }]
|
|
685
|
+
],
|
|
686
|
+
server: [
|
|
687
|
+
["rect", { x: "4", y: "4.5", width: "16", height: "6.5", rx: "2" }],
|
|
688
|
+
["rect", { x: "4", y: "13", width: "16", height: "6.5", rx: "2" }],
|
|
689
|
+
["path", { d: "M7.5 7.75h.01M7.5 16.25h.01" }],
|
|
690
|
+
["path", { d: "M11 7.75h6M11 16.25h6" }]
|
|
691
|
+
],
|
|
692
|
+
scheduler: [
|
|
693
|
+
["circle", { cx: "12", cy: "12", r: "8" }],
|
|
694
|
+
["path", { d: "M12 7.5V12l3.2 2" }]
|
|
695
|
+
],
|
|
696
|
+
key: [
|
|
697
|
+
["circle", { cx: "8.5", cy: "8.5", r: "3.6" }],
|
|
698
|
+
["path", { d: "M11.1 11.1 19 19" }],
|
|
699
|
+
["path", { d: "M16.4 16.4l1.8 1.8M18.2 13.6l1.8 1.8" }]
|
|
700
|
+
],
|
|
701
|
+
pod: [
|
|
702
|
+
["path", { d: "M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Z" }],
|
|
703
|
+
["path", { d: "M8.75 10.5h6.5v5h-6.5z" }]
|
|
704
|
+
],
|
|
705
|
+
function: [
|
|
706
|
+
["rect", { x: "4", y: "4", width: "16", height: "16", rx: "4.5" }],
|
|
707
|
+
["path", { d: "M13.2 7.5 9.5 12.4h3.4L11 16.5" }]
|
|
708
|
+
],
|
|
709
|
+
lock: [
|
|
710
|
+
["rect", { x: "5", y: "11", width: "14", height: "9", rx: "2.2" }],
|
|
711
|
+
["path", { d: "M8 11V8a4 4 0 0 1 8 0v3" }],
|
|
712
|
+
["path", { d: "M12 14.8v2.4" }]
|
|
713
|
+
],
|
|
714
|
+
metrics: [
|
|
715
|
+
["path", { d: "M4 20h16" }],
|
|
716
|
+
["rect", { x: "5", y: "11", width: "2.6", height: "6", rx: "0.6" }],
|
|
717
|
+
["rect", { x: "10.7", y: "6.5", width: "2.6", height: "10.5", rx: "0.6" }],
|
|
718
|
+
["rect", { x: "16.4", y: "13", width: "2.6", height: "4", rx: "0.6" }]
|
|
719
|
+
],
|
|
720
|
+
mobile: [
|
|
721
|
+
["rect", { x: "7", y: "3", width: "10", height: "18", rx: "2.6" }],
|
|
722
|
+
["path", { d: "M10.5 18h3" }]
|
|
723
|
+
],
|
|
724
|
+
registry: [
|
|
725
|
+
["rect", { x: "3.5", y: "13", width: "7", height: "6", rx: "1" }],
|
|
726
|
+
["rect", { x: "13.5", y: "13", width: "7", height: "6", rx: "1" }],
|
|
727
|
+
["rect", { x: "8.5", y: "5", width: "7", height: "6", rx: "1" }]
|
|
728
|
+
],
|
|
729
|
+
flow: [
|
|
730
|
+
["path", { d: "M5 12h14" }],
|
|
731
|
+
["path", { d: "m13 6 6 6-6 6" }]
|
|
732
|
+
],
|
|
733
|
+
decision: [
|
|
734
|
+
["path", { d: "M12 3 21 12 12 21 3 12 12 3Z" }],
|
|
735
|
+
["path", { d: "M12 8v4" }],
|
|
736
|
+
["path", { d: "M12 16h.01" }]
|
|
737
|
+
]
|
|
738
|
+
};
|
|
739
|
+
function iconKeyForNode(node) {
|
|
740
|
+
const override = typeof node.props?.icon === "string" ? node.props.icon.toLowerCase() : void 0;
|
|
741
|
+
if (override && ICONS[override]) return override;
|
|
742
|
+
if (node.kind === "api_gateway" || node.kind === "gateway" || node.kind === "load_balancer" || node.kind === "ingress") return "gateway";
|
|
743
|
+
if (node.kind === "db" || node.kind === "database" || node.kind === "sql" || node.kind === "nosql" || node.kind === "warehouse") return "database";
|
|
744
|
+
if (node.kind === "bucket" || node.kind === "object_store" || node.kind === "blob" || node.kind === "volume" || node.kind === "disk") return "storage";
|
|
745
|
+
if (node.kind === "cdn" || node.kind === "dns" || node.kind === "internet") return "cdn";
|
|
746
|
+
if (node.kind === "queue" || node.kind === "topic" || node.kind === "stream" || node.kind === "event_bus" || node.kind === "broker") return "queue";
|
|
747
|
+
if (node.kind === "scheduler" || node.kind === "cron") return "scheduler";
|
|
748
|
+
if (node.kind === "worker" || node.kind === "job" || node.kind === "batch") return "worker";
|
|
749
|
+
if (node.kind === "function" || node.kind === "lambda") return "function";
|
|
750
|
+
if (node.kind === "pod" || node.kind === "container" || node.kind === "sidecar") return "pod";
|
|
751
|
+
if (node.kind === "secret" || node.kind === "key" || node.kind === "certificate" || node.kind === "vault") return "key";
|
|
752
|
+
if (node.kind === "auth" || node.kind === "oauth" || node.kind === "oidc" || node.kind === "jwt" || node.kind === "lock") return "lock";
|
|
753
|
+
if (node.kind === "firewall" || node.kind === "waf" || node.kind === "vpn" || node.kind === "bastion") return "security";
|
|
754
|
+
if (node.kind === "proxy" || node.kind === "reverse_proxy" || node.kind === "router" || node.kind === "nat" || node.kind === "switch") return "gateway";
|
|
755
|
+
if (node.kind === "monitor" || node.kind === "metrics" || node.kind === "dashboard" || node.kind === "slo" || node.kind === "probe") return "metrics";
|
|
756
|
+
if (node.kind === "registry" || node.kind === "artifact") return "registry";
|
|
757
|
+
if (node.kind === "mobile") return "mobile";
|
|
758
|
+
if (node.kind === "api" || node.kind === "service" || node.kind === "microservice" || node.kind === "backend" || node.kind === "server" || node.kind === "handler" || node.kind === "controller") return "server";
|
|
759
|
+
if (node.kind === "browser" || node.kind === "web" || node.kind === "frontend" || node.kind === "app") return "browser";
|
|
760
|
+
if (node.kind === "user" || node.kind === "client") return "user";
|
|
761
|
+
if (node.kind === "decision" || node.kind === "condition") return "decision";
|
|
762
|
+
if (node.kind === "cache") return "cache";
|
|
763
|
+
return ICONS[node.kind] ? node.kind : node.role;
|
|
764
|
+
}
|
|
765
|
+
function appendGlyph(doc, wrap, spec) {
|
|
766
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
767
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
768
|
+
svg.setAttribute("fill", "none");
|
|
769
|
+
svg.setAttribute("stroke-width", "1.75");
|
|
770
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
771
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
772
|
+
for (const [tag, attrs] of spec) {
|
|
773
|
+
const child = doc.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
774
|
+
for (const [name, value] of Object.entries(attrs)) child.setAttribute(name, value);
|
|
775
|
+
svg.appendChild(child);
|
|
776
|
+
}
|
|
777
|
+
wrap.appendChild(svg);
|
|
778
|
+
}
|
|
779
|
+
function resolveImageSrc(node, assets) {
|
|
780
|
+
const raw = node.props?.image ?? node.props?.logo;
|
|
781
|
+
if (typeof raw !== "string" || raw.length === 0) return void 0;
|
|
782
|
+
return assets?.[raw] ?? raw;
|
|
783
|
+
}
|
|
784
|
+
function createNodeMediaEl(doc, node, assets) {
|
|
785
|
+
const wrap = doc.createElement("div");
|
|
786
|
+
wrap.className = "markdy-node__icon";
|
|
787
|
+
wrap.setAttribute("aria-hidden", "true");
|
|
788
|
+
const imgSrc = resolveImageSrc(node, assets);
|
|
789
|
+
if (imgSrc) {
|
|
790
|
+
wrap.dataset.media = "image";
|
|
791
|
+
const fit = typeof node.props?.imageFit === "string" ? node.props.imageFit.toLowerCase() : "contain";
|
|
792
|
+
wrap.dataset.fit = fit === "cover" ? "cover" : "contain";
|
|
793
|
+
const img = doc.createElement("img");
|
|
794
|
+
img.src = imgSrc;
|
|
795
|
+
img.alt = "";
|
|
796
|
+
img.decoding = "async";
|
|
797
|
+
img.loading = "lazy";
|
|
798
|
+
img.referrerPolicy = "no-referrer";
|
|
799
|
+
img.addEventListener("error", () => {
|
|
800
|
+
wrap.removeAttribute("data-media");
|
|
801
|
+
wrap.removeAttribute("data-fit");
|
|
802
|
+
wrap.textContent = "";
|
|
803
|
+
appendGlyph(doc, wrap, ICONS[iconKeyForNode(node)] ?? ICONS.service);
|
|
804
|
+
});
|
|
805
|
+
wrap.appendChild(img);
|
|
806
|
+
return wrap;
|
|
807
|
+
}
|
|
808
|
+
appendGlyph(doc, wrap, ICONS[iconKeyForNode(node)] ?? ICONS.service);
|
|
809
|
+
return wrap;
|
|
810
|
+
}
|
|
811
|
+
function createNodeEl(node, theme, assets) {
|
|
523
812
|
const el = document.createElement("div");
|
|
524
813
|
el.className = "markdy-node markdy-scene-actor";
|
|
525
814
|
el.dataset.actor = node.id;
|
|
@@ -530,15 +819,19 @@ function createNodeEl(node, theme) {
|
|
|
530
819
|
el.style.setProperty("--md-node-h", `${node.height}px`);
|
|
531
820
|
const roleColor = theme.roles[node.role] ?? theme.accent;
|
|
532
821
|
el.style.setProperty("--md-role-color", roleColor);
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
822
|
+
const typeText = node.kind.replace(/_/g, " ");
|
|
823
|
+
el.dataset.kind = node.kind;
|
|
824
|
+
el.dataset.icon = iconKeyForNode(node);
|
|
825
|
+
el.title = `${node.label} (${typeText})`;
|
|
826
|
+
el.setAttribute("aria-label", el.title);
|
|
827
|
+
const body = document.createElement("div");
|
|
828
|
+
body.className = "markdy-node__body";
|
|
829
|
+
const icon = createNodeMediaEl(document, node, assets);
|
|
538
830
|
const label = document.createElement("div");
|
|
539
831
|
label.className = "markdy-node__label";
|
|
540
832
|
label.textContent = node.label;
|
|
541
|
-
|
|
833
|
+
body.append(icon, label);
|
|
834
|
+
el.append(body);
|
|
542
835
|
return el;
|
|
543
836
|
}
|
|
544
837
|
function createTitleEl(title) {
|
|
@@ -572,15 +865,15 @@ function ensureSceneStyles(doc) {
|
|
|
572
865
|
linear-gradient(90deg, var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
|
|
573
866
|
linear-gradient(var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px,
|
|
574
867
|
linear-gradient(90deg, var(--md-grid-major) 1px, transparent 1px) 0 0 / 160px 160px;
|
|
575
|
-
mask-image:
|
|
576
|
-
opacity: 0.
|
|
868
|
+
mask-image: radial-gradient(ellipse at 50% 42%, #000 55%, transparent 100%);
|
|
869
|
+
opacity: 0.5;
|
|
577
870
|
}
|
|
578
871
|
.markdy-scene-root::after {
|
|
579
872
|
z-index: 1;
|
|
580
873
|
background:
|
|
581
|
-
radial-gradient(ellipse at 50%
|
|
582
|
-
linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.
|
|
583
|
-
opacity: 0.
|
|
874
|
+
radial-gradient(ellipse at 50% -8%, color-mix(in srgb, var(--md-accent) 10%, transparent), transparent 46%),
|
|
875
|
+
linear-gradient(180deg, transparent 0%, rgba(2, 6, 23, 0.05) 62%, var(--md-vignette) 100%);
|
|
876
|
+
opacity: 0.62;
|
|
584
877
|
}
|
|
585
878
|
.markdy-scene-content { z-index: 2; }
|
|
586
879
|
.markdy-scene-actor-layer {
|
|
@@ -604,6 +897,10 @@ function applyThemeToScene(scene, theme) {
|
|
|
604
897
|
scene.style.setProperty("--md-grid-major", theme.gridMajor);
|
|
605
898
|
scene.style.setProperty("--md-vignette", theme.vignette);
|
|
606
899
|
scene.style.setProperty("--md-accent", theme.accent);
|
|
900
|
+
scene.style.setProperty("--md-node-surface", theme.nodeSurface ?? theme.surface);
|
|
901
|
+
scene.style.setProperty("--md-node-surface-raised", theme.nodeSurfaceRaised ?? theme.surfaceRaised);
|
|
902
|
+
scene.style.setProperty("--md-hairline", theme.hairline ?? `color-mix(in srgb, ${theme.border} 50%, transparent)`);
|
|
903
|
+
scene.style.setProperty("--md-shadow", theme.shadow ?? "rgba(2, 6, 23, 0.55)");
|
|
607
904
|
scene.dataset.markdyTheme = theme.name;
|
|
608
905
|
}
|
|
609
906
|
|
|
@@ -615,11 +912,15 @@ function createPlayer(opts) {
|
|
|
615
912
|
autoplay = true,
|
|
616
913
|
loop = true,
|
|
617
914
|
copyright = true,
|
|
618
|
-
|
|
915
|
+
assets,
|
|
916
|
+
progressBar,
|
|
917
|
+
sceneBoundaryProgress,
|
|
918
|
+
playbackRate: initialPlaybackRate = 1,
|
|
619
919
|
onWarning = (w) => console.warn(`[markdy] line ${w.line}: ${w.message}`),
|
|
620
920
|
onTimeUpdate,
|
|
621
921
|
onPlayStateChange
|
|
622
922
|
} = opts;
|
|
923
|
+
const showSceneBoundaryProgress = sceneBoundaryProgress ?? progressBar ?? true;
|
|
623
924
|
const { ast, plan } = parseAndCompile(code);
|
|
624
925
|
for (const w of ast.diagnostics) {
|
|
625
926
|
if (w.severity === "warning") onWarning(w);
|
|
@@ -635,7 +936,7 @@ function createPlayer(opts) {
|
|
|
635
936
|
});
|
|
636
937
|
container.appendChild(viewport);
|
|
637
938
|
let progressEl = null;
|
|
638
|
-
if (
|
|
939
|
+
if (showSceneBoundaryProgress) {
|
|
639
940
|
progressEl = document.createElement("div");
|
|
640
941
|
Object.assign(progressEl.style, {
|
|
641
942
|
position: "absolute",
|
|
@@ -706,7 +1007,7 @@ function createPlayer(opts) {
|
|
|
706
1007
|
actorLayer.appendChild(titleEl);
|
|
707
1008
|
const nodeEls = /* @__PURE__ */ new Map();
|
|
708
1009
|
for (const node of plan.nodes) {
|
|
709
|
-
const el = createNodeEl(node, plan.theme);
|
|
1010
|
+
const el = createNodeEl(node, plan.theme, assets);
|
|
710
1011
|
actorLayer.appendChild(el);
|
|
711
1012
|
nodeEls.set(node.id, el);
|
|
712
1013
|
}
|
|
@@ -726,6 +1027,7 @@ function createPlayer(opts) {
|
|
|
726
1027
|
anim.currentTime = 0;
|
|
727
1028
|
}
|
|
728
1029
|
let sceneMs = 0;
|
|
1030
|
+
let playbackRate = Number.isFinite(initialPlaybackRate) && initialPlaybackRate > 0 ? initialPlaybackRate : 1;
|
|
729
1031
|
let lastRafTs = null;
|
|
730
1032
|
let isPlaying = false;
|
|
731
1033
|
let rafId = null;
|
|
@@ -734,7 +1036,7 @@ function createPlayer(opts) {
|
|
|
734
1036
|
onTimeUpdate?.(sceneMs / 1e3, durationSeconds);
|
|
735
1037
|
}
|
|
736
1038
|
function rafTick(timestamp) {
|
|
737
|
-
if (lastRafTs !== null) sceneMs += timestamp - lastRafTs;
|
|
1039
|
+
if (lastRafTs !== null) sceneMs += (timestamp - lastRafTs) * playbackRate;
|
|
738
1040
|
lastRafTs = timestamp;
|
|
739
1041
|
if (totalDurationMs > 0 && sceneMs >= totalDurationMs) {
|
|
740
1042
|
if (loop) sceneMs = sceneMs % totalDurationMs;
|
|
@@ -773,6 +1075,13 @@ function createPlayer(opts) {
|
|
|
773
1075
|
applyCurrentTime();
|
|
774
1076
|
if (totalDurationMs > 0) updateProgressBar(sceneMs / totalDurationMs);
|
|
775
1077
|
},
|
|
1078
|
+
setPlaybackRate(rate) {
|
|
1079
|
+
if (!Number.isFinite(rate) || rate <= 0) return;
|
|
1080
|
+
playbackRate = rate;
|
|
1081
|
+
},
|
|
1082
|
+
playbackRate() {
|
|
1083
|
+
return playbackRate;
|
|
1084
|
+
},
|
|
776
1085
|
currentTime() {
|
|
777
1086
|
return sceneMs / 1e3;
|
|
778
1087
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Browser renderer for animated MarkdyScript architecture diagrams, built on the Web Animations API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,19 +43,20 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@markdy/core": "0.8.
|
|
46
|
+
"@markdy/core": "0.8.3"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"jsdom": "^29.1.1",
|
|
50
50
|
"tsup": "^8.5.1",
|
|
51
51
|
"typescript": "^5.9.3",
|
|
52
52
|
"vitest": "^4.1.7",
|
|
53
|
-
"@markdy/stdlib-systems": "0.8.
|
|
53
|
+
"@markdy/stdlib-systems": "0.8.3"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsup",
|
|
57
57
|
"typecheck": "tsc --noEmit",
|
|
58
58
|
"lint": "tsc --noEmit",
|
|
59
|
-
"test": "vitest run"
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"harness": "pnpm --filter @markdy/core run build && pnpm run build && node harness/serve.mjs"
|
|
60
61
|
}
|
|
61
62
|
}
|