@markdy/renderer-dom 0.8.11 → 0.8.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 +7 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +938 -63
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,10 +8,11 @@ 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
|
+
- **Focused diagram visuals** — flowchart shapes, tree buses, state self-loops, sequence lifelines/messages, constellation orbits, group zones, and annotations
|
|
11
12
|
- **Semantic node cards** — compact SVG glyphs for browsers, services, gateways, queues, workers, databases, storage, CDN, security, platform, and more
|
|
12
13
|
- **Seek-safe** — manual `currentTime` control enables reliable `seek()` in any direction
|
|
13
14
|
- **Playback-rate controls** — set timeline speed to slow down or speed up diagrams without rebuilding animations
|
|
14
|
-
- **Semantic themes** — `midnight` and `
|
|
15
|
+
- **Semantic themes** — `paper`, `editorial`, `nebula`, `midnight`, `blueprint`, and `graphite`, with per-role node and edge colors
|
|
15
16
|
- **Single dependency** — only `@markdy/core`
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
@@ -101,6 +102,11 @@ src/
|
|
|
101
102
|
diagram.ts — Public API, rAF loop, progress bar, responsive scaling
|
|
102
103
|
nodes.ts — Node element factory + scene title
|
|
103
104
|
edges.ts — Flow-edge SVG runtime, routing, and cue animations
|
|
105
|
+
sequence.ts — Participant lifelines, messages, and activation spans
|
|
106
|
+
tree.ts — Shared parent/child bus connectors
|
|
107
|
+
groups.ts — Group boundary zones
|
|
108
|
+
annotations.ts — Editorial callouts and leader lines
|
|
109
|
+
constellation.ts — Nebula halos, orbit rings, and deterministic stars
|
|
104
110
|
geometry/
|
|
105
111
|
rect.ts — Rects, points, and hit-testing (DOM-free, unit tested)
|
|
106
112
|
path.ts — Polyline measurement + obstacle-aware orthogonal routing
|
package/dist/index.d.ts
CHANGED
|
@@ -41,4 +41,9 @@ interface Diagram {
|
|
|
41
41
|
}
|
|
42
42
|
declare function createDiagram(opts: DiagramOptions): Diagram;
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
type SvgSpec = Array<[string, Record<string, string>]>;
|
|
45
|
+
type IconSpec = SvgSpec;
|
|
46
|
+
/** Read-only monochrome glyph registry; callers can inspect or choose keys without injecting markup. */
|
|
47
|
+
declare const ICON_REGISTRY: Readonly<Record<string, IconSpec>>;
|
|
48
|
+
|
|
49
|
+
export { type Diagram, type DiagramOptions, ICON_REGISTRY, type IconSpec, createDiagram };
|
package/dist/index.js
CHANGED
|
@@ -49,8 +49,55 @@ function countPathIntersections(points, obstacles) {
|
|
|
49
49
|
function round1(n) {
|
|
50
50
|
return Math.round(n * 10) / 10;
|
|
51
51
|
}
|
|
52
|
-
function toPathD(points) {
|
|
53
|
-
|
|
52
|
+
function toPathD(points, cornerRadius = 8) {
|
|
53
|
+
if (points.length < 2) return "";
|
|
54
|
+
if (points.length === 2) {
|
|
55
|
+
const [a, b] = points;
|
|
56
|
+
return `M ${round1(a.x)} ${round1(a.y)} L ${round1(b.x)} ${round1(b.y)}`;
|
|
57
|
+
}
|
|
58
|
+
if (cornerRadius <= 0) {
|
|
59
|
+
return points.map((p, i) => i === 0 ? `M ${round1(p.x)} ${round1(p.y)}` : `L ${round1(p.x)} ${round1(p.y)}`).join(" ");
|
|
60
|
+
}
|
|
61
|
+
const parts = [`M ${round1(points[0].x)} ${round1(points[0].y)}`];
|
|
62
|
+
for (let i = 1; i < points.length; i++) {
|
|
63
|
+
const prev = points[i - 1];
|
|
64
|
+
const cur = points[i];
|
|
65
|
+
const next = points[i + 1];
|
|
66
|
+
if (!next) {
|
|
67
|
+
parts.push(`L ${round1(cur.x)} ${round1(cur.y)}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const dx1 = cur.x - prev.x;
|
|
71
|
+
const dy1 = cur.y - prev.y;
|
|
72
|
+
const dx2 = next.x - cur.x;
|
|
73
|
+
const dy2 = next.y - cur.y;
|
|
74
|
+
const len1 = Math.hypot(dx1, dy1);
|
|
75
|
+
const len2 = Math.hypot(dx2, dy2);
|
|
76
|
+
if (len1 < 0.01 || len2 < 0.01) {
|
|
77
|
+
parts.push(`L ${round1(cur.x)} ${round1(cur.y)}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const r = Math.min(cornerRadius, len1 / 2, len2 / 2);
|
|
81
|
+
const bx = cur.x - dx1 / len1 * r;
|
|
82
|
+
const by = cur.y - dy1 / len1 * r;
|
|
83
|
+
const ax = cur.x + dx2 / len2 * r;
|
|
84
|
+
const ay = cur.y + dy2 / len2 * r;
|
|
85
|
+
parts.push(`L ${round1(bx)} ${round1(by)}`);
|
|
86
|
+
parts.push(`Q ${round1(cur.x)} ${round1(cur.y)} ${round1(ax)} ${round1(ay)}`);
|
|
87
|
+
}
|
|
88
|
+
return parts.join(" ");
|
|
89
|
+
}
|
|
90
|
+
function selfLoopPath(rect) {
|
|
91
|
+
const top = rect.y;
|
|
92
|
+
const left = rect.x + rect.width * 0.25;
|
|
93
|
+
const right = rect.x + rect.width * 0.75;
|
|
94
|
+
const apex = top - 36;
|
|
95
|
+
return [
|
|
96
|
+
{ x: left, y: top },
|
|
97
|
+
{ x: left, y: apex },
|
|
98
|
+
{ x: right, y: apex },
|
|
99
|
+
{ x: right, y: top }
|
|
100
|
+
];
|
|
54
101
|
}
|
|
55
102
|
function polylineLength(points) {
|
|
56
103
|
let total = 0;
|
|
@@ -125,6 +172,11 @@ function clampPointToScene(point, bounds) {
|
|
|
125
172
|
y: round1(clamp(point.y, pad, bounds.height - pad))
|
|
126
173
|
};
|
|
127
174
|
}
|
|
175
|
+
function laneOffset(lane) {
|
|
176
|
+
if (lane <= 0) return 0;
|
|
177
|
+
const step = Math.ceil(lane / 2);
|
|
178
|
+
return (lane % 2 === 1 ? 1 : -1) * step * 18;
|
|
179
|
+
}
|
|
128
180
|
function routeLength(points) {
|
|
129
181
|
let total = 0;
|
|
130
182
|
for (let i = 0; i < points.length - 1; i++) total += segmentLength(points[i], points[i + 1]);
|
|
@@ -145,12 +197,24 @@ function routeBends(points) {
|
|
|
145
197
|
return bends;
|
|
146
198
|
}
|
|
147
199
|
function routeOrthogonal(sourceRect, targetRect, obstacles, bounds, lane = 0) {
|
|
148
|
-
const laneShift = lane
|
|
200
|
+
const laneShift = laneOffset(lane);
|
|
149
201
|
const sourceCenter = rectCenter(sourceRect);
|
|
150
202
|
const targetCenter = rectCenter(targetRect);
|
|
151
203
|
const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
|
|
152
|
-
const source = horizontalPrimary ? {
|
|
153
|
-
|
|
204
|
+
const source = horizontalPrimary ? {
|
|
205
|
+
x: targetCenter.x >= sourceCenter.x ? sourceRect.x2 : sourceRect.x1,
|
|
206
|
+
y: clamp(sourceCenter.y + laneShift, sourceRect.y1 + 12, sourceRect.y2 - 12)
|
|
207
|
+
} : {
|
|
208
|
+
x: clamp(sourceCenter.x + laneShift, sourceRect.x1 + 12, sourceRect.x2 - 12),
|
|
209
|
+
y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1
|
|
210
|
+
};
|
|
211
|
+
const target = horizontalPrimary ? {
|
|
212
|
+
x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2,
|
|
213
|
+
y: clamp(targetCenter.y + laneShift, targetRect.y1 + 12, targetRect.y2 - 12)
|
|
214
|
+
} : {
|
|
215
|
+
x: clamp(targetCenter.x + laneShift, targetRect.x1 + 12, targetRect.x2 - 12),
|
|
216
|
+
y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2
|
|
217
|
+
};
|
|
154
218
|
const infl = obstacles.map((o) => inflateRect(o, 8));
|
|
155
219
|
const candidates = [];
|
|
156
220
|
if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
|
|
@@ -203,6 +267,11 @@ var INITIAL_CAMERA_TRANSFORM = "translate(0px, 0px) scale(1)";
|
|
|
203
267
|
var DEFAULT_FRAME_ZOOM = 1.18;
|
|
204
268
|
var MAX_FRAME_ZOOM = 1.75;
|
|
205
269
|
var FRAME_PADDING = 88;
|
|
270
|
+
var edgeSceneCounter = 0;
|
|
271
|
+
function createEdgeSceneId() {
|
|
272
|
+
edgeSceneCounter += 1;
|
|
273
|
+
return `md-scene-${edgeSceneCounter}`;
|
|
274
|
+
}
|
|
206
275
|
function dedupePoints(points) {
|
|
207
276
|
const out = [];
|
|
208
277
|
for (const p of points) {
|
|
@@ -243,8 +312,18 @@ function ensureDefs(svg, theme, id) {
|
|
|
243
312
|
arrow.setAttribute("markerHeight", "7");
|
|
244
313
|
arrow.setAttribute("orient", "auto-start-reverse");
|
|
245
314
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
246
|
-
|
|
247
|
-
|
|
315
|
+
if (kind === "response") {
|
|
316
|
+
path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4");
|
|
317
|
+
path.setAttribute("fill", "none");
|
|
318
|
+
path.setAttribute("stroke", color);
|
|
319
|
+
path.setAttribute("stroke-width", "1.4");
|
|
320
|
+
} else if (kind === "event") {
|
|
321
|
+
path.setAttribute("d", "M 5 2 A 3 3 0 1 1 5 8 A 3 3 0 1 1 5 2");
|
|
322
|
+
path.setAttribute("fill", color);
|
|
323
|
+
} else {
|
|
324
|
+
path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
|
|
325
|
+
path.setAttribute("fill", color);
|
|
326
|
+
}
|
|
248
327
|
arrow.appendChild(path);
|
|
249
328
|
defs.appendChild(arrow);
|
|
250
329
|
}
|
|
@@ -254,7 +333,8 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
254
333
|
ensureDefs(svg, theme, sceneId);
|
|
255
334
|
const color = theme.edges[kind];
|
|
256
335
|
const style = EDGE_STYLES[kind];
|
|
257
|
-
const
|
|
336
|
+
const isSelfLoop = from.id === to.id;
|
|
337
|
+
const points = isSelfLoop ? dedupePoints(selfLoopPath(from)) : dedupePoints(routeOrthogonal(boxRect(from), boxRect(to), routeObstacles, bounds, lane));
|
|
258
338
|
const d = toPathD(points);
|
|
259
339
|
const len = polylineLength(points);
|
|
260
340
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
@@ -272,7 +352,7 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
272
352
|
path.setAttribute("marker-end", `url(#${sceneId}-arrow-${kind})`);
|
|
273
353
|
}
|
|
274
354
|
if (kind !== "dependency") {
|
|
275
|
-
path.style.filter = `drop-shadow(0 0 3px ${color
|
|
355
|
+
path.style.filter = `drop-shadow(0 0 3px ${translucentColor(color, "33")})`;
|
|
276
356
|
}
|
|
277
357
|
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
278
358
|
dot.setAttribute("r", "3.5");
|
|
@@ -314,7 +394,40 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObst
|
|
|
314
394
|
labelEl.__plate = plate;
|
|
315
395
|
}
|
|
316
396
|
svg.appendChild(group);
|
|
317
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
group,
|
|
399
|
+
path,
|
|
400
|
+
label: labelEl,
|
|
401
|
+
labelPlate: labelEl ? labelEl.__plate : void 0,
|
|
402
|
+
dot,
|
|
403
|
+
pathLen: len,
|
|
404
|
+
points,
|
|
405
|
+
labelRect,
|
|
406
|
+
kind,
|
|
407
|
+
color,
|
|
408
|
+
drawReveal: style.dash.length === 0
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function setEdgeVisible(runtime, visible) {
|
|
412
|
+
runtime.group.style.opacity = visible ? "1" : "0";
|
|
413
|
+
if (runtime.label) runtime.label.style.opacity = visible ? "1" : "0";
|
|
414
|
+
if (runtime.labelPlate) runtime.labelPlate.style.opacity = visible ? "1" : "0";
|
|
415
|
+
}
|
|
416
|
+
function translucentColor(color, alpha = "aa") {
|
|
417
|
+
const value = color.trim();
|
|
418
|
+
if (/^#[0-9a-f]{3}$/i.test(value)) {
|
|
419
|
+
return `#${value.slice(1).split("").map((char) => char + char).join("")}${alpha}`;
|
|
420
|
+
}
|
|
421
|
+
if (/^#[0-9a-f]{6}$/i.test(value)) return `${value}${alpha}`;
|
|
422
|
+
if (/^#[0-9a-f]{8}$/i.test(value)) return value;
|
|
423
|
+
return `color-mix(in srgb, ${value} 67%, transparent)`;
|
|
424
|
+
}
|
|
425
|
+
function nextEdgeLane(lanes, from, to) {
|
|
426
|
+
const pair = [from, to].sort().join("|");
|
|
427
|
+
const keys = [`pair:${pair}`, `out:${from}`, `in:${to}`];
|
|
428
|
+
const lane = Math.max(...keys.map((key) => lanes.get(key) ?? 0));
|
|
429
|
+
for (const key of keys) lanes.set(key, (lanes.get(key) ?? 0) + 1);
|
|
430
|
+
return lane;
|
|
318
431
|
}
|
|
319
432
|
function dotTravelKeyframes(points) {
|
|
320
433
|
const total = polylineLength(points);
|
|
@@ -337,21 +450,24 @@ function dotTravelKeyframes(points) {
|
|
|
337
450
|
function animateEdgeReveal(runtime, startMs, durMs) {
|
|
338
451
|
const anims = [];
|
|
339
452
|
const { path, dot, label, group, pathLen, points } = runtime;
|
|
340
|
-
|
|
341
|
-
|
|
453
|
+
if (runtime.drawReveal) {
|
|
454
|
+
path.style.strokeDasharray = String(pathLen);
|
|
455
|
+
path.style.strokeDashoffset = String(pathLen);
|
|
456
|
+
}
|
|
342
457
|
const drawMs = Math.min(220, durMs * 0.5);
|
|
343
|
-
anims.push(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
458
|
+
anims.push(group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 120, delay: startMs, fill: "forwards" }));
|
|
459
|
+
if (runtime.drawReveal) {
|
|
460
|
+
anims.push(
|
|
461
|
+
path.animate(
|
|
462
|
+
[{ strokeDashoffset: pathLen }, { strokeDashoffset: 0 }],
|
|
463
|
+
{ duration: drawMs, delay: startMs, fill: "forwards", easing: "ease-out" }
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
}
|
|
350
467
|
if (label) {
|
|
351
468
|
anims.push(label.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
anims.push(plate.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
469
|
+
if (runtime.labelPlate) {
|
|
470
|
+
anims.push(runtime.labelPlate.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 180, delay: startMs + 80, fill: "forwards" }));
|
|
355
471
|
}
|
|
356
472
|
}
|
|
357
473
|
if (points.length >= 2) {
|
|
@@ -366,6 +482,16 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
366
482
|
}
|
|
367
483
|
return anims;
|
|
368
484
|
}
|
|
485
|
+
function animateEdgeEmphasis(runtime, startMs, durMs, strength, color) {
|
|
486
|
+
const baseFilter = runtime.kind === "dependency" ? "none" : `drop-shadow(0 0 3px ${translucentColor(runtime.color, "33")})`;
|
|
487
|
+
const glowColor = color ?? runtime.color;
|
|
488
|
+
const radius = Math.max(4, Math.min(16, 5 + strength * 4));
|
|
489
|
+
const peakFilter = `drop-shadow(0 0 ${radius}px ${translucentColor(glowColor)}) brightness(${1 + Math.min(strength, 2) * 0.08})`;
|
|
490
|
+
return runtime.path.animate(
|
|
491
|
+
[{ filter: baseFilter }, { filter: peakFilter }, { filter: baseFilter }],
|
|
492
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
|
|
493
|
+
);
|
|
494
|
+
}
|
|
369
495
|
function computeFrameTransform(targetIds, nodes, bounds, requestedZoom = DEFAULT_FRAME_ZOOM) {
|
|
370
496
|
const targets = new Set(targetIds);
|
|
371
497
|
const selected = nodes.filter((node) => targets.has(node.id));
|
|
@@ -387,7 +513,7 @@ function computeFrameTransform(targetIds, nodes, bounds, requestedZoom = DEFAULT
|
|
|
387
513
|
const ty = Math.round((bounds.height / 2 - centerY * scale) * 10) / 10;
|
|
388
514
|
return `translate(${tx}px, ${ty}px) scale(${Math.round(scale * 1e3) / 1e3})`;
|
|
389
515
|
}
|
|
390
|
-
function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds) {
|
|
516
|
+
function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds, edges = [], edgeRuntimes = /* @__PURE__ */ new Map(), sceneId = createEdgeSceneId(), diagramType = "architecture") {
|
|
391
517
|
const anims = [];
|
|
392
518
|
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
393
519
|
const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
|
|
@@ -395,10 +521,37 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
395
521
|
const placedLabels = [];
|
|
396
522
|
const laneByPair = /* @__PURE__ */ new Map();
|
|
397
523
|
const svg = ensureEdgeLayer(scene);
|
|
398
|
-
const sceneId = `md-${Math.random().toString(36).slice(2, 8)}`;
|
|
399
524
|
let cameraTransform = scene.style.transform || INITIAL_CAMERA_TRANSFORM;
|
|
400
525
|
scene.style.transformOrigin = "0 0";
|
|
401
526
|
scene.style.transform = cameraTransform;
|
|
527
|
+
const edgeById = new Map(edges.map((edge) => [edge.id, edge]));
|
|
528
|
+
const edgeRects = [...allNodeRects];
|
|
529
|
+
const edgeRectById = new Map(nodes.map((node) => [node.id, boxRect(node)]));
|
|
530
|
+
const edgeLabels = [];
|
|
531
|
+
const edgeLanes = /* @__PURE__ */ new Map();
|
|
532
|
+
for (const edge of diagramType === "sequence" ? [] : edges) {
|
|
533
|
+
if (edge.structural || edgeRuntimes.has(edge.id)) continue;
|
|
534
|
+
const from = nodeById.get(edge.from);
|
|
535
|
+
const to = nodeById.get(edge.to);
|
|
536
|
+
if (!from || !to) continue;
|
|
537
|
+
const routeObstacles = [...edgeRectById.entries()].filter(([id]) => id !== from.id && id !== to.id).map(([, rect]) => rect);
|
|
538
|
+
const lane = nextEdgeLane(edgeLanes, edge.from, edge.to);
|
|
539
|
+
const runtime = createEdgeRuntime(
|
|
540
|
+
svg,
|
|
541
|
+
from,
|
|
542
|
+
to,
|
|
543
|
+
edge.kind,
|
|
544
|
+
edge.label,
|
|
545
|
+
theme,
|
|
546
|
+
sceneId,
|
|
547
|
+
routeObstacles,
|
|
548
|
+
[...edgeRects, ...edgeLabels],
|
|
549
|
+
bounds,
|
|
550
|
+
lane
|
|
551
|
+
);
|
|
552
|
+
if (runtime.labelRect) edgeLabels.push(runtime.labelRect);
|
|
553
|
+
edgeRuntimes.set(edge.id, runtime);
|
|
554
|
+
}
|
|
402
555
|
anims.push(
|
|
403
556
|
titleEl.animate(
|
|
404
557
|
[{ opacity: 0, transform: "translateY(-6px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
@@ -411,37 +564,55 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
411
564
|
if (cue.kind === "show") {
|
|
412
565
|
cue.targets.forEach((id, idx) => {
|
|
413
566
|
const el = nodeEls.get(id);
|
|
414
|
-
if (!el) return;
|
|
415
567
|
const delay = startMs + (typeof cue.params.stagger === "number" ? cue.params.stagger * 1e3 * idx : 0);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
568
|
+
if (el) {
|
|
569
|
+
anims.push(
|
|
570
|
+
el.animate(
|
|
571
|
+
[{ opacity: 0, transform: "translateY(8px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
572
|
+
{ duration: durMs, delay, fill: "forwards", easing: "ease-out" }
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const runtime = edgeRuntimes.get(id);
|
|
578
|
+
if (runtime) anims.push(runtime.group.animate([{ opacity: 0 }, { opacity: 1 }], { duration: durMs, delay, fill: "forwards", easing: "ease-out" }));
|
|
422
579
|
});
|
|
423
580
|
continue;
|
|
424
581
|
}
|
|
425
582
|
if (cue.kind === "hide") {
|
|
426
583
|
for (const id of cue.targets) {
|
|
427
584
|
const el = nodeEls.get(id);
|
|
428
|
-
if (
|
|
429
|
-
|
|
585
|
+
if (el) {
|
|
586
|
+
anims.push(el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const runtime = edgeRuntimes.get(id);
|
|
590
|
+
if (runtime) anims.push(runtime.group.animate([{ opacity: 1 }, { opacity: 0 }], { duration: durMs, delay: startMs, fill: "forwards" }));
|
|
430
591
|
}
|
|
431
592
|
continue;
|
|
432
593
|
}
|
|
433
594
|
if (cue.kind === "glow") {
|
|
595
|
+
const strength = typeof cue.params.strength === "number" ? cue.params.strength : 1;
|
|
596
|
+
const peak = 1 + 0.12 * strength;
|
|
434
597
|
for (const id of cue.targets) {
|
|
435
598
|
const el = nodeEls.get(id);
|
|
436
|
-
if (
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
599
|
+
if (el) {
|
|
600
|
+
if (cue.params.color) el.style.setProperty("--md-glow-color", String(cue.params.color));
|
|
601
|
+
const glowColor = translucentColor(String(cue.params.color ?? "var(--md-accent)"));
|
|
602
|
+
anims.push(
|
|
603
|
+
el.animate(
|
|
604
|
+
[
|
|
605
|
+
{ filter: "brightness(1)" },
|
|
606
|
+
{ filter: `drop-shadow(0 0 ${Math.max(4, 4 + strength * 5)}px ${glowColor}) brightness(${peak})` },
|
|
607
|
+
{ filter: "brightness(1)" }
|
|
608
|
+
],
|
|
609
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
|
|
610
|
+
)
|
|
611
|
+
);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
const runtime = edgeRuntimes.get(id);
|
|
615
|
+
if (runtime) anims.push(animateEdgeEmphasis(runtime, startMs, durMs, strength, typeof cue.params.color === "string" ? cue.params.color : void 0));
|
|
445
616
|
}
|
|
446
617
|
continue;
|
|
447
618
|
}
|
|
@@ -449,20 +620,31 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
449
620
|
const zoom = typeof cue.params.zoom === "number" && cue.params.zoom > 0 ? cue.params.zoom : 1.03;
|
|
450
621
|
for (const id of cue.targets) {
|
|
451
622
|
const el = nodeEls.get(id);
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
623
|
+
if (el) {
|
|
624
|
+
anims.push(
|
|
625
|
+
el.animate(
|
|
626
|
+
[
|
|
627
|
+
{ transform: "scale(1)", filter: "none" },
|
|
628
|
+
{ transform: `scale(${zoom})`, filter: "drop-shadow(0 0 7px var(--md-accent))" },
|
|
629
|
+
{ transform: "scale(1)", filter: "none" }
|
|
630
|
+
],
|
|
631
|
+
{ duration: durMs, delay: startMs, fill: "none", easing: "ease-in-out" }
|
|
632
|
+
)
|
|
633
|
+
);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const runtime = edgeRuntimes.get(id);
|
|
637
|
+
if (runtime) anims.push(animateEdgeEmphasis(runtime, startMs, durMs, zoom, void 0));
|
|
460
638
|
}
|
|
461
639
|
continue;
|
|
462
640
|
}
|
|
463
641
|
if (cue.kind === "frame") {
|
|
642
|
+
const frameTargets = cue.targets.flatMap((id) => {
|
|
643
|
+
const edge = edgeById.get(id);
|
|
644
|
+
return edge ? [edge.from, edge.to] : [id];
|
|
645
|
+
});
|
|
464
646
|
const nextTransform = computeFrameTransform(
|
|
465
|
-
|
|
647
|
+
frameTargets,
|
|
466
648
|
nodes,
|
|
467
649
|
bounds,
|
|
468
650
|
typeof cue.params.zoom === "number" ? cue.params.zoom : DEFAULT_FRAME_ZOOM
|
|
@@ -477,7 +659,7 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
477
659
|
cameraTransform = nextTransform;
|
|
478
660
|
continue;
|
|
479
661
|
}
|
|
480
|
-
if (cue.kind === "flow" && cue.segments?.[0]) {
|
|
662
|
+
if (cue.kind === "flow" && cue.segments?.[0] && diagramType !== "sequence") {
|
|
481
663
|
const seg = cue.segments[0];
|
|
482
664
|
const from = nodeById.get(seg.from);
|
|
483
665
|
const to = nodeById.get(seg.to);
|
|
@@ -486,26 +668,303 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
486
668
|
for (const [id, rect] of rectById) {
|
|
487
669
|
if (id !== seg.from && id !== seg.to) routeObstacles.push(rect);
|
|
488
670
|
}
|
|
489
|
-
const
|
|
490
|
-
const lane = laneByPair.get(pairKey) ?? 0;
|
|
491
|
-
laneByPair.set(pairKey, lane + 1);
|
|
671
|
+
const lane = nextEdgeLane(laneByPair, seg.from, seg.to);
|
|
492
672
|
const labelObstacles = [...allNodeRects, ...placedLabels];
|
|
493
|
-
const
|
|
494
|
-
|
|
673
|
+
const edgeId = cue.edgeId ?? edges.find(
|
|
674
|
+
(edge) => !edge.structural && edge.from === seg.from && edge.to === seg.to && edge.kind === seg.op && edge.label === seg.label
|
|
675
|
+
)?.id;
|
|
676
|
+
let runtime = edgeId ? edgeRuntimes.get(edgeId) : void 0;
|
|
677
|
+
if (!runtime) {
|
|
678
|
+
runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane);
|
|
679
|
+
if (runtime.labelRect) placedLabels.push(runtime.labelRect);
|
|
680
|
+
if (edgeId) edgeRuntimes.set(edgeId, runtime);
|
|
681
|
+
}
|
|
495
682
|
anims.push(...animateEdgeReveal(runtime, startMs, durMs));
|
|
496
683
|
}
|
|
497
684
|
}
|
|
498
685
|
for (const a of anims) a.pause();
|
|
499
686
|
return anims;
|
|
500
687
|
}
|
|
688
|
+
function buildStructuralEdgeAnimations(edges, nodes, theme, scene, bounds, edgeRuntimes = /* @__PURE__ */ new Map(), sceneId = createEdgeSceneId(), diagramType = "architecture") {
|
|
689
|
+
const structural = edges.filter(
|
|
690
|
+
(e) => e.structural && diagramType !== "sequence" && !(diagramType === "tree" && !e.label)
|
|
691
|
+
);
|
|
692
|
+
if (structural.length === 0) return [];
|
|
693
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
694
|
+
const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
|
|
695
|
+
const allNodeRects = [...rectById.values()];
|
|
696
|
+
const placedLabels = [];
|
|
697
|
+
const laneByPair = /* @__PURE__ */ new Map();
|
|
698
|
+
const svg = ensureEdgeLayer(scene);
|
|
699
|
+
for (const edge of structural) {
|
|
700
|
+
const from = nodeById.get(edge.from);
|
|
701
|
+
const to = nodeById.get(edge.to);
|
|
702
|
+
if (!from || !to) continue;
|
|
703
|
+
const routeObstacles = [];
|
|
704
|
+
for (const [id, rect] of rectById) {
|
|
705
|
+
if (id !== edge.from && id !== edge.to) routeObstacles.push(rect);
|
|
706
|
+
}
|
|
707
|
+
const lane = nextEdgeLane(laneByPair, edge.from, edge.to);
|
|
708
|
+
const labelObstacles = [...allNodeRects, ...placedLabels];
|
|
709
|
+
const runtime = createEdgeRuntime(
|
|
710
|
+
svg,
|
|
711
|
+
from,
|
|
712
|
+
to,
|
|
713
|
+
edge.kind,
|
|
714
|
+
edge.label,
|
|
715
|
+
theme,
|
|
716
|
+
sceneId,
|
|
717
|
+
routeObstacles,
|
|
718
|
+
labelObstacles,
|
|
719
|
+
bounds,
|
|
720
|
+
lane
|
|
721
|
+
);
|
|
722
|
+
if (runtime.labelRect) placedLabels.push(runtime.labelRect);
|
|
723
|
+
setEdgeVisible(runtime, true);
|
|
724
|
+
edgeRuntimes.set(edge.id, runtime);
|
|
725
|
+
}
|
|
726
|
+
return [];
|
|
727
|
+
}
|
|
501
728
|
|
|
502
|
-
// src/
|
|
503
|
-
var STYLE_ID = "markdy-
|
|
504
|
-
function
|
|
729
|
+
// src/annotations.ts
|
|
730
|
+
var STYLE_ID = "markdy-annotation-styles";
|
|
731
|
+
function ensureAnnotationStyles(doc) {
|
|
505
732
|
if (doc.getElementById(STYLE_ID)) return;
|
|
506
733
|
const style = doc.createElement("style");
|
|
507
734
|
style.id = STYLE_ID;
|
|
508
735
|
style.textContent = `
|
|
736
|
+
.markdy-annotation-layer {
|
|
737
|
+
position: absolute;
|
|
738
|
+
inset: 0;
|
|
739
|
+
pointer-events: none;
|
|
740
|
+
z-index: 150;
|
|
741
|
+
}
|
|
742
|
+
.markdy-annotation {
|
|
743
|
+
position: absolute;
|
|
744
|
+
max-width: 220px;
|
|
745
|
+
font-family: var(--md-font-title, Georgia, serif);
|
|
746
|
+
font-size: 14px;
|
|
747
|
+
font-style: italic;
|
|
748
|
+
color: var(--md-text);
|
|
749
|
+
line-height: 1.35;
|
|
750
|
+
}
|
|
751
|
+
.markdy-annotation__leader {
|
|
752
|
+
position: absolute;
|
|
753
|
+
pointer-events: none;
|
|
754
|
+
}
|
|
755
|
+
`;
|
|
756
|
+
doc.head.appendChild(style);
|
|
757
|
+
}
|
|
758
|
+
function positionForAnnotation(position, bounds, index) {
|
|
759
|
+
const pad = 24;
|
|
760
|
+
const p = (position ?? "").toLowerCase();
|
|
761
|
+
if (p.includes("top") && p.includes("right")) return { x: bounds.width - pad - 200, y: pad + index * 48 };
|
|
762
|
+
if (p.includes("top") && p.includes("left")) return { x: pad, y: pad + index * 48 };
|
|
763
|
+
if (p.includes("bottom") && p.includes("right")) return { x: bounds.width - pad - 200, y: bounds.height - pad - 40 };
|
|
764
|
+
if (p.includes("bottom") && p.includes("left")) return { x: pad, y: bounds.height - pad - 40 };
|
|
765
|
+
return { x: bounds.width - pad - 200, y: pad + index * 48 };
|
|
766
|
+
}
|
|
767
|
+
function mountAnnotations(layer, annotations, nodes, theme, bounds) {
|
|
768
|
+
if (annotations.length === 0) return;
|
|
769
|
+
const doc = layer.ownerDocument;
|
|
770
|
+
ensureAnnotationStyles(doc);
|
|
771
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
772
|
+
svg.classList.add("markdy-annotation__leader");
|
|
773
|
+
Object.assign(svg.style, { position: "absolute", inset: "0", width: "100%", height: "100%", overflow: "visible" });
|
|
774
|
+
layer.appendChild(svg);
|
|
775
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
776
|
+
annotations.slice(0, 2).forEach((ann, index) => {
|
|
777
|
+
const pos = positionForAnnotation(ann.position, bounds, index);
|
|
778
|
+
const textEl = doc.createElement("div");
|
|
779
|
+
textEl.className = "markdy-annotation";
|
|
780
|
+
textEl.textContent = ann.text;
|
|
781
|
+
textEl.style.left = `${pos.x}px`;
|
|
782
|
+
textEl.style.top = `${pos.y}px`;
|
|
783
|
+
layer.appendChild(textEl);
|
|
784
|
+
const target = ann.target ? nodeById.get(ann.target) : void 0;
|
|
785
|
+
if (!target) return;
|
|
786
|
+
const tx = target.x + target.width / 2;
|
|
787
|
+
const ty = target.y + target.height / 2;
|
|
788
|
+
const ax = pos.x + 8;
|
|
789
|
+
const ay = pos.y + 16;
|
|
790
|
+
const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
791
|
+
path.setAttribute("d", `M ${ax} ${ay} Q ${(ax + tx) / 2} ${(ay + ty) / 2 - 20} ${tx} ${ty}`);
|
|
792
|
+
path.setAttribute("fill", "none");
|
|
793
|
+
path.setAttribute("stroke", theme.textMuted);
|
|
794
|
+
path.setAttribute("stroke-width", "1");
|
|
795
|
+
path.setAttribute("stroke-dasharray", "4 3");
|
|
796
|
+
path.setAttribute("opacity", "0.55");
|
|
797
|
+
svg.appendChild(path);
|
|
798
|
+
const dot = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
799
|
+
dot.setAttribute("cx", String(tx));
|
|
800
|
+
dot.setAttribute("cy", String(ty));
|
|
801
|
+
dot.setAttribute("r", "2");
|
|
802
|
+
dot.setAttribute("fill", theme.text);
|
|
803
|
+
svg.appendChild(dot);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/constellation.ts
|
|
808
|
+
var constellationId = 0;
|
|
809
|
+
function nextId() {
|
|
810
|
+
constellationId += 1;
|
|
811
|
+
return `md-constellation-${constellationId}`;
|
|
812
|
+
}
|
|
813
|
+
function appendCircle(doc, parent, cx, cy, radius, stroke, opacity, dash) {
|
|
814
|
+
const circle = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
815
|
+
circle.setAttribute("cx", String(cx));
|
|
816
|
+
circle.setAttribute("cy", String(cy));
|
|
817
|
+
circle.setAttribute("r", String(radius));
|
|
818
|
+
circle.setAttribute("fill", "none");
|
|
819
|
+
circle.setAttribute("stroke", stroke);
|
|
820
|
+
circle.setAttribute("stroke-width", "1");
|
|
821
|
+
circle.setAttribute("opacity", opacity);
|
|
822
|
+
if (dash) circle.setAttribute("stroke-dasharray", dash);
|
|
823
|
+
parent.appendChild(circle);
|
|
824
|
+
}
|
|
825
|
+
function mountConstellationLayer(layer, nodes, theme, bounds) {
|
|
826
|
+
if (nodes.length === 0) return;
|
|
827
|
+
const doc = layer.ownerDocument;
|
|
828
|
+
Object.assign(layer.style, {
|
|
829
|
+
position: "absolute",
|
|
830
|
+
inset: "0",
|
|
831
|
+
zIndex: "38",
|
|
832
|
+
pointerEvents: "none"
|
|
833
|
+
});
|
|
834
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
835
|
+
svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
|
|
836
|
+
Object.assign(svg.style, {
|
|
837
|
+
position: "absolute",
|
|
838
|
+
inset: "0",
|
|
839
|
+
width: "100%",
|
|
840
|
+
height: "100%",
|
|
841
|
+
overflow: "visible"
|
|
842
|
+
});
|
|
843
|
+
layer.appendChild(svg);
|
|
844
|
+
const focal = nodes.find((node) => node.focal) ?? nodes[0];
|
|
845
|
+
const centerX = focal.x + focal.width / 2;
|
|
846
|
+
const centerY = focal.y + focal.height / 2;
|
|
847
|
+
const radius = Math.max(120, Math.min(bounds.width, bounds.height) * 0.28);
|
|
848
|
+
const gradientId = `${nextId()}-halo`;
|
|
849
|
+
const defs = doc.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
850
|
+
const gradient = doc.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
|
|
851
|
+
gradient.setAttribute("id", gradientId);
|
|
852
|
+
const stopA = doc.createElementNS("http://www.w3.org/2000/svg", "stop");
|
|
853
|
+
stopA.setAttribute("offset", "0%");
|
|
854
|
+
stopA.setAttribute("stop-color", theme.accent);
|
|
855
|
+
stopA.setAttribute("stop-opacity", "0.24");
|
|
856
|
+
const stopB = doc.createElementNS("http://www.w3.org/2000/svg", "stop");
|
|
857
|
+
stopB.setAttribute("offset", "100%");
|
|
858
|
+
stopB.setAttribute("stop-color", theme.accent);
|
|
859
|
+
stopB.setAttribute("stop-opacity", "0");
|
|
860
|
+
gradient.append(stopA, stopB);
|
|
861
|
+
defs.appendChild(gradient);
|
|
862
|
+
svg.appendChild(defs);
|
|
863
|
+
const halo = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
864
|
+
halo.setAttribute("cx", String(centerX));
|
|
865
|
+
halo.setAttribute("cy", String(centerY));
|
|
866
|
+
halo.setAttribute("r", String(radius * 0.7));
|
|
867
|
+
halo.setAttribute("fill", `url(#${gradientId})`);
|
|
868
|
+
svg.appendChild(halo);
|
|
869
|
+
appendCircle(doc, svg, centerX, centerY, radius * 0.55, theme.rule ?? theme.border, "0.55", "2 8");
|
|
870
|
+
appendCircle(doc, svg, centerX, centerY, radius * 0.82, theme.rule ?? theme.border, "0.38", "1 11");
|
|
871
|
+
appendCircle(doc, svg, centerX, centerY, radius, theme.soft ?? theme.border, "0.22", "1 15");
|
|
872
|
+
for (const node of nodes) {
|
|
873
|
+
if (node.id === focal.id) continue;
|
|
874
|
+
const nodeX = node.x + node.width / 2;
|
|
875
|
+
const nodeY = node.y + node.height / 2;
|
|
876
|
+
const link = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
877
|
+
link.classList.add("markdy-constellation-link");
|
|
878
|
+
link.setAttribute("x1", String(centerX));
|
|
879
|
+
link.setAttribute("y1", String(centerY));
|
|
880
|
+
link.setAttribute("x2", String(nodeX));
|
|
881
|
+
link.setAttribute("y2", String(nodeY));
|
|
882
|
+
link.setAttribute("stroke", theme.soft ?? theme.accent);
|
|
883
|
+
link.setAttribute("stroke-width", "1");
|
|
884
|
+
link.setAttribute("stroke-dasharray", "2 9");
|
|
885
|
+
link.setAttribute("opacity", "0.22");
|
|
886
|
+
svg.appendChild(link);
|
|
887
|
+
}
|
|
888
|
+
for (let index = 0; index < 28; index += 1) {
|
|
889
|
+
const angle = index * 2.399963;
|
|
890
|
+
const distance = 70 + index * 47 % 190;
|
|
891
|
+
const star = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
892
|
+
star.classList.add("markdy-constellation-star");
|
|
893
|
+
star.setAttribute("cx", String(centerX + Math.cos(angle) * distance));
|
|
894
|
+
star.setAttribute("cy", String(centerY + Math.sin(angle) * distance * 0.72));
|
|
895
|
+
star.setAttribute("r", String(1 + index % 3 * 0.45));
|
|
896
|
+
star.setAttribute("fill", index % 4 === 0 ? theme.soft ?? theme.accent : theme.textMuted);
|
|
897
|
+
star.setAttribute("opacity", String(0.28 + index % 5 * 0.1));
|
|
898
|
+
star.style.animationDelay = `${-(index % 7) * 0.45}s`;
|
|
899
|
+
svg.appendChild(star);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/groups.ts
|
|
904
|
+
var STYLE_ID2 = "markdy-group-boundary-styles";
|
|
905
|
+
function ensureGroupStyles(doc) {
|
|
906
|
+
if (doc.getElementById(STYLE_ID2)) return;
|
|
907
|
+
const style = doc.createElement("style");
|
|
908
|
+
style.id = STYLE_ID2;
|
|
909
|
+
style.textContent = `
|
|
910
|
+
.markdy-group-boundary {
|
|
911
|
+
position: absolute;
|
|
912
|
+
box-sizing: border-box;
|
|
913
|
+
border: 1px dashed var(--md-group-border, color-mix(in srgb, var(--md-border) 70%, transparent));
|
|
914
|
+
border-radius: var(--md-radius-md, 8px);
|
|
915
|
+
background: color-mix(in srgb, var(--md-surface-raised) 40%, transparent);
|
|
916
|
+
pointer-events: none;
|
|
917
|
+
z-index: 40;
|
|
918
|
+
}
|
|
919
|
+
.markdy-group-boundary__label {
|
|
920
|
+
position: absolute;
|
|
921
|
+
left: 12px;
|
|
922
|
+
top: -10px;
|
|
923
|
+
padding: 2px 8px;
|
|
924
|
+
font-size: 10px;
|
|
925
|
+
font-weight: 600;
|
|
926
|
+
letter-spacing: 0.08em;
|
|
927
|
+
text-transform: uppercase;
|
|
928
|
+
color: var(--md-text-muted);
|
|
929
|
+
background: var(--md-canvas);
|
|
930
|
+
border: 1px solid var(--md-group-border, color-mix(in srgb, var(--md-border) 60%, transparent));
|
|
931
|
+
border-radius: 4px;
|
|
932
|
+
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
933
|
+
}
|
|
934
|
+
`;
|
|
935
|
+
doc.head.appendChild(style);
|
|
936
|
+
}
|
|
937
|
+
function createGroupBoundaryEl(boundary, theme, doc = document) {
|
|
938
|
+
const el = doc.createElement("div");
|
|
939
|
+
el.className = "markdy-group-boundary";
|
|
940
|
+
el.dataset.group = boundary.id;
|
|
941
|
+
el.style.left = `${boundary.x}px`;
|
|
942
|
+
el.style.top = `${boundary.y}px`;
|
|
943
|
+
el.style.width = `${boundary.width}px`;
|
|
944
|
+
el.style.height = `${boundary.height}px`;
|
|
945
|
+
el.style.setProperty("--md-group-border", theme.hairline ?? theme.border);
|
|
946
|
+
if (boundary.label) {
|
|
947
|
+
const label = document.createElement("div");
|
|
948
|
+
label.className = "markdy-group-boundary__label";
|
|
949
|
+
label.textContent = boundary.label;
|
|
950
|
+
el.appendChild(label);
|
|
951
|
+
}
|
|
952
|
+
return el;
|
|
953
|
+
}
|
|
954
|
+
function mountGroupBoundaries(layer, boundaries, theme) {
|
|
955
|
+
ensureGroupStyles(layer.ownerDocument);
|
|
956
|
+
for (const boundary of boundaries) {
|
|
957
|
+
layer.appendChild(createGroupBoundaryEl(boundary, theme, layer.ownerDocument));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/nodes.ts
|
|
962
|
+
var STYLE_ID3 = "markdy-diagram-node-styles";
|
|
963
|
+
function ensureNodeStyles(doc) {
|
|
964
|
+
if (doc.getElementById(STYLE_ID3)) return;
|
|
965
|
+
const style = doc.createElement("style");
|
|
966
|
+
style.id = STYLE_ID3;
|
|
967
|
+
style.textContent = `
|
|
509
968
|
.markdy-node {
|
|
510
969
|
position: absolute;
|
|
511
970
|
box-sizing: border-box;
|
|
@@ -522,7 +981,7 @@ function ensureNodeStyles(doc) {
|
|
|
522
981
|
0 10px 22px -12px var(--md-shadow, rgba(2, 6, 23, 0.55)),
|
|
523
982
|
inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent)),
|
|
524
983
|
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
525
|
-
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
984
|
+
font-family: var(--md-font-node, Inter, ui-sans-serif, system-ui, sans-serif);
|
|
526
985
|
overflow: hidden;
|
|
527
986
|
opacity: 0;
|
|
528
987
|
transform: translateY(8px);
|
|
@@ -616,6 +1075,13 @@ function ensureNodeStyles(doc) {
|
|
|
616
1075
|
word-break: break-word;
|
|
617
1076
|
text-wrap: balance;
|
|
618
1077
|
}
|
|
1078
|
+
.markdy-node__value {
|
|
1079
|
+
flex: 0 0 auto;
|
|
1080
|
+
font-size: 18px;
|
|
1081
|
+
font-weight: 700;
|
|
1082
|
+
color: var(--md-ink, var(--md-text));
|
|
1083
|
+
font-variant-numeric: tabular-nums;
|
|
1084
|
+
}
|
|
619
1085
|
.markdy-node[data-role="client"] { border-radius: 15px 15px 9px 9px; }
|
|
620
1086
|
.markdy-node[data-role="data"] { border-radius: 12px 12px 20px 20px; }
|
|
621
1087
|
.markdy-scene-title {
|
|
@@ -630,12 +1096,71 @@ function ensureNodeStyles(doc) {
|
|
|
630
1096
|
color: var(--md-text);
|
|
631
1097
|
opacity: 0;
|
|
632
1098
|
transform: translateY(-6px);
|
|
633
|
-
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
1099
|
+
font-family: var(--md-font-title, Inter, ui-sans-serif, system-ui, sans-serif);
|
|
634
1100
|
}
|
|
635
1101
|
.markdy-scene-title[data-visible="1"] {
|
|
636
1102
|
opacity: 1;
|
|
637
1103
|
transform: translateY(0);
|
|
638
1104
|
}
|
|
1105
|
+
.markdy-node[data-shape="diamond"] {
|
|
1106
|
+
border-radius: 4px;
|
|
1107
|
+
transform: rotate(0deg);
|
|
1108
|
+
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
|
1109
|
+
}
|
|
1110
|
+
.markdy-node[data-shape="pill"] {
|
|
1111
|
+
border-radius: 999px;
|
|
1112
|
+
min-height: 56px;
|
|
1113
|
+
}
|
|
1114
|
+
.markdy-node[data-shape="circle"],
|
|
1115
|
+
.markdy-node[data-kind="dot"] {
|
|
1116
|
+
width: 64px;
|
|
1117
|
+
height: 64px;
|
|
1118
|
+
border-radius: 50%;
|
|
1119
|
+
}
|
|
1120
|
+
.markdy-node[data-kind="matrix"] {
|
|
1121
|
+
background-image:
|
|
1122
|
+
linear-gradient(var(--md-hairline) 1px, transparent 1px),
|
|
1123
|
+
linear-gradient(90deg, var(--md-hairline) 1px, transparent 1px);
|
|
1124
|
+
background-size: 12px 12px;
|
|
1125
|
+
}
|
|
1126
|
+
.markdy-node[data-kind="track"] {
|
|
1127
|
+
border-left: 4px solid var(--md-role-color, var(--md-accent));
|
|
1128
|
+
border-radius: var(--md-radius-md, 8px);
|
|
1129
|
+
}
|
|
1130
|
+
.markdy-node[data-kind="token_strip"] {
|
|
1131
|
+
border-radius: 999px;
|
|
1132
|
+
}
|
|
1133
|
+
.markdy-node[data-shape="rounded"] {
|
|
1134
|
+
border-radius: 16px;
|
|
1135
|
+
}
|
|
1136
|
+
.markdy-node[data-shape="terminal"] {
|
|
1137
|
+
border-radius: 6px;
|
|
1138
|
+
font-family: var(--md-font-mono, ui-monospace, monospace);
|
|
1139
|
+
box-shadow: none;
|
|
1140
|
+
background: var(--md-node-surface, var(--md-surface));
|
|
1141
|
+
}
|
|
1142
|
+
.markdy-node[data-focal="1"] {
|
|
1143
|
+
background: color-mix(in srgb, var(--md-accent-tint, var(--md-accent)) 100%, transparent);
|
|
1144
|
+
box-shadow:
|
|
1145
|
+
inset 0 0 0 1px color-mix(in srgb, var(--md-accent) 55%, transparent);
|
|
1146
|
+
}
|
|
1147
|
+
.markdy-scene-root[data-markdy-theme="nebula"] .markdy-node {
|
|
1148
|
+
border: 1px solid color-mix(in srgb, var(--md-role-color, var(--md-accent)) 34%, transparent);
|
|
1149
|
+
box-shadow:
|
|
1150
|
+
0 0 24px -14px color-mix(in srgb, var(--md-role-color, var(--md-accent)) 80%, transparent),
|
|
1151
|
+
inset 0 0 0 1px var(--md-hairline);
|
|
1152
|
+
}
|
|
1153
|
+
.markdy-scene-root[data-markdy-theme="nebula"] .markdy-node[data-focal="1"] {
|
|
1154
|
+
box-shadow:
|
|
1155
|
+
0 0 34px -8px color-mix(in srgb, var(--md-accent) 78%, transparent),
|
|
1156
|
+
inset 0 0 0 1px var(--md-accent);
|
|
1157
|
+
}
|
|
1158
|
+
.markdy-scene-root[data-flat="1"] .markdy-node {
|
|
1159
|
+
box-shadow: inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent));
|
|
1160
|
+
}
|
|
1161
|
+
.markdy-scene-root[data-flat="1"] .markdy-node[data-visible="1"] {
|
|
1162
|
+
box-shadow: inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent));
|
|
1163
|
+
}
|
|
639
1164
|
`;
|
|
640
1165
|
doc.head.appendChild(style);
|
|
641
1166
|
}
|
|
@@ -644,6 +1169,14 @@ var ICONS = {
|
|
|
644
1169
|
["rect", { x: "5", y: "5", width: "14", height: "14", rx: "3" }],
|
|
645
1170
|
["path", { d: "M9 9h6v6H9zM9 2.5v2.5M15 2.5v2.5M9 19v2.5M15 19v2.5M2.5 9h2.5M2.5 15h2.5M19 9h2.5M19 15h2.5" }]
|
|
646
1171
|
],
|
|
1172
|
+
laptop: [
|
|
1173
|
+
["path", { d: "M3 19l18 0" }],
|
|
1174
|
+
["path", { d: "M5 7a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1l0 -8" }]
|
|
1175
|
+
],
|
|
1176
|
+
phone: [
|
|
1177
|
+
["path", { d: "M6 5a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-14" }],
|
|
1178
|
+
["path", { d: "M10.5 18h3" }]
|
|
1179
|
+
],
|
|
647
1180
|
user: [
|
|
648
1181
|
["circle", { cx: "12", cy: "8", r: "3.4" }],
|
|
649
1182
|
["path", { d: "M5.5 20a6.5 6.5 0 0 1 13 0" }]
|
|
@@ -783,6 +1316,7 @@ var ICONS = {
|
|
|
783
1316
|
["path", { d: "M12 16h.01" }]
|
|
784
1317
|
]
|
|
785
1318
|
};
|
|
1319
|
+
var ICON_REGISTRY = Object.freeze(ICONS);
|
|
786
1320
|
function iconKeyForNode(node) {
|
|
787
1321
|
const override = typeof node.props?.icon === "string" ? node.props.icon.toLowerCase() : void 0;
|
|
788
1322
|
if (override && ICONS[override]) return override;
|
|
@@ -802,6 +1336,7 @@ function iconKeyForNode(node) {
|
|
|
802
1336
|
if (node.kind === "monitor" || node.kind === "metrics" || node.kind === "dashboard" || node.kind === "slo" || node.kind === "probe") return "metrics";
|
|
803
1337
|
if (node.kind === "registry" || node.kind === "artifact") return "registry";
|
|
804
1338
|
if (node.kind === "mobile") return "mobile";
|
|
1339
|
+
if (node.kind === "laptop" || node.kind === "desktop") return node.kind;
|
|
805
1340
|
if (node.kind === "api" || node.kind === "service" || node.kind === "microservice" || node.kind === "backend" || node.kind === "server" || node.kind === "handler" || node.kind === "controller") return "server";
|
|
806
1341
|
if (node.kind === "browser" || node.kind === "web" || node.kind === "frontend" || node.kind === "app") return "browser";
|
|
807
1342
|
if (node.kind === "user" || node.kind === "client") return "user";
|
|
@@ -878,12 +1413,18 @@ function createNodeEl(node, theme, assets) {
|
|
|
878
1413
|
el.style.top = `${node.y}px`;
|
|
879
1414
|
el.style.setProperty("--md-node-w", `${node.width}px`);
|
|
880
1415
|
el.style.setProperty("--md-node-h", `${node.height}px`);
|
|
1416
|
+
if (theme.flatCards) {
|
|
1417
|
+
el.style.setProperty("--md-shadow", "transparent");
|
|
1418
|
+
}
|
|
1419
|
+
if (theme.accentTint) el.style.setProperty("--md-accent-tint", theme.accentTint);
|
|
881
1420
|
const roleColor = theme.roles[node.role] ?? theme.accent;
|
|
882
1421
|
el.style.setProperty("--md-role-color", roleColor);
|
|
883
1422
|
applyDeclaredNodeStyle(el, node.style);
|
|
884
1423
|
const typeText = node.kind.replace(/_/g, " ");
|
|
885
1424
|
el.dataset.kind = node.kind;
|
|
886
1425
|
el.dataset.icon = iconKeyForNode(node);
|
|
1426
|
+
if (node.shape) el.dataset.shape = node.shape;
|
|
1427
|
+
if (node.focal) el.dataset.focal = "1";
|
|
887
1428
|
el.title = `${node.label} (${typeText})`;
|
|
888
1429
|
el.setAttribute("aria-label", el.title);
|
|
889
1430
|
const body = document.createElement("div");
|
|
@@ -893,6 +1434,13 @@ function createNodeEl(node, theme, assets) {
|
|
|
893
1434
|
label.className = "markdy-node__label";
|
|
894
1435
|
label.textContent = node.label;
|
|
895
1436
|
body.append(icon, label);
|
|
1437
|
+
const value = node.props?.value ?? node.props?.metric;
|
|
1438
|
+
if (value !== void 0 && value !== null) {
|
|
1439
|
+
const valueEl = document.createElement("div");
|
|
1440
|
+
valueEl.className = "markdy-node__value";
|
|
1441
|
+
valueEl.textContent = String(value);
|
|
1442
|
+
body.appendChild(valueEl);
|
|
1443
|
+
}
|
|
896
1444
|
el.append(body);
|
|
897
1445
|
return el;
|
|
898
1446
|
}
|
|
@@ -903,6 +1451,225 @@ function createTitleEl(title) {
|
|
|
903
1451
|
return el;
|
|
904
1452
|
}
|
|
905
1453
|
|
|
1454
|
+
// src/sequence.ts
|
|
1455
|
+
var sequenceLayerCounter = 0;
|
|
1456
|
+
function markerId(prefix) {
|
|
1457
|
+
sequenceLayerCounter += 1;
|
|
1458
|
+
return `md-sequence-${prefix}-${sequenceLayerCounter}`;
|
|
1459
|
+
}
|
|
1460
|
+
function appendMarker(doc, defs, id, kind, color) {
|
|
1461
|
+
const marker = doc.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
1462
|
+
marker.setAttribute("id", id);
|
|
1463
|
+
marker.setAttribute("viewBox", "0 0 10 10");
|
|
1464
|
+
marker.setAttribute("refX", "8.5");
|
|
1465
|
+
marker.setAttribute("refY", "5");
|
|
1466
|
+
marker.setAttribute("markerWidth", "7");
|
|
1467
|
+
marker.setAttribute("markerHeight", "7");
|
|
1468
|
+
marker.setAttribute("orient", "auto");
|
|
1469
|
+
const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1470
|
+
if (kind === "response") {
|
|
1471
|
+
path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4");
|
|
1472
|
+
path.setAttribute("fill", "none");
|
|
1473
|
+
path.setAttribute("stroke", color);
|
|
1474
|
+
path.setAttribute("stroke-width", "1.4");
|
|
1475
|
+
} else if (kind === "event") {
|
|
1476
|
+
path.setAttribute("d", "M 5 2 A 3 3 0 1 1 5 8 A 3 3 0 1 1 5 2");
|
|
1477
|
+
path.setAttribute("fill", color);
|
|
1478
|
+
} else {
|
|
1479
|
+
path.setAttribute("d", "M 1.5 1.6 L 9 5 L 1.5 8.4 L 3.4 5 Z");
|
|
1480
|
+
path.setAttribute("fill", color);
|
|
1481
|
+
}
|
|
1482
|
+
marker.appendChild(path);
|
|
1483
|
+
defs.appendChild(marker);
|
|
1484
|
+
}
|
|
1485
|
+
function createText(doc, x, y, text, theme) {
|
|
1486
|
+
const label = doc.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
1487
|
+
label.setAttribute("x", String(x));
|
|
1488
|
+
label.setAttribute("y", String(y));
|
|
1489
|
+
label.setAttribute("text-anchor", "middle");
|
|
1490
|
+
label.setAttribute("dominant-baseline", "middle");
|
|
1491
|
+
label.setAttribute("font-size", "11");
|
|
1492
|
+
label.setAttribute("font-family", theme.fonts?.mono ?? "ui-monospace, SFMono-Regular, Menlo, monospace");
|
|
1493
|
+
label.setAttribute("fill", theme.text);
|
|
1494
|
+
label.textContent = text;
|
|
1495
|
+
return label;
|
|
1496
|
+
}
|
|
1497
|
+
function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds) {
|
|
1498
|
+
Object.assign(layer.style, {
|
|
1499
|
+
position: "absolute",
|
|
1500
|
+
inset: "0",
|
|
1501
|
+
zIndex: "52",
|
|
1502
|
+
pointerEvents: "none"
|
|
1503
|
+
});
|
|
1504
|
+
const doc = layer.ownerDocument;
|
|
1505
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1506
|
+
svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
|
|
1507
|
+
Object.assign(svg.style, {
|
|
1508
|
+
position: "absolute",
|
|
1509
|
+
inset: "0",
|
|
1510
|
+
width: "100%",
|
|
1511
|
+
height: "100%",
|
|
1512
|
+
overflow: "visible"
|
|
1513
|
+
});
|
|
1514
|
+
layer.appendChild(svg);
|
|
1515
|
+
const defs = doc.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
1516
|
+
const markers = /* @__PURE__ */ new Map();
|
|
1517
|
+
for (const kind of ["request", "response", "event"]) {
|
|
1518
|
+
const id = markerId(kind);
|
|
1519
|
+
markers.set(kind, id);
|
|
1520
|
+
appendMarker(doc, defs, id, kind, theme.edges[kind]);
|
|
1521
|
+
}
|
|
1522
|
+
svg.appendChild(defs);
|
|
1523
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
1524
|
+
const centerX = (id) => {
|
|
1525
|
+
const node = nodeById.get(id);
|
|
1526
|
+
return node ? node.x + node.width / 2 : 0;
|
|
1527
|
+
};
|
|
1528
|
+
for (const node of nodes) {
|
|
1529
|
+
const x = centerX(node.id);
|
|
1530
|
+
const lifeline = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1531
|
+
lifeline.classList.add("markdy-sequence-lifeline");
|
|
1532
|
+
lifeline.setAttribute("x1", String(x));
|
|
1533
|
+
lifeline.setAttribute("x2", String(x));
|
|
1534
|
+
lifeline.setAttribute("y1", String(node.y + node.height + 12));
|
|
1535
|
+
lifeline.setAttribute("y2", String(bounds.height - 28));
|
|
1536
|
+
lifeline.setAttribute("stroke", theme.rule ?? theme.soft ?? theme.border);
|
|
1537
|
+
lifeline.setAttribute("stroke-width", "1");
|
|
1538
|
+
lifeline.setAttribute("stroke-dasharray", "4 5");
|
|
1539
|
+
lifeline.setAttribute("opacity", "0.75");
|
|
1540
|
+
svg.appendChild(lifeline);
|
|
1541
|
+
}
|
|
1542
|
+
for (const activation of activations) {
|
|
1543
|
+
const x = centerX(activation.participant);
|
|
1544
|
+
const bar = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
1545
|
+
bar.classList.add("markdy-sequence-activation");
|
|
1546
|
+
bar.setAttribute("x", String(x - 5));
|
|
1547
|
+
bar.setAttribute("y", String(activation.y));
|
|
1548
|
+
bar.setAttribute("width", "10");
|
|
1549
|
+
bar.setAttribute("height", String(activation.height));
|
|
1550
|
+
bar.setAttribute("rx", "3");
|
|
1551
|
+
bar.setAttribute("fill", theme.accent);
|
|
1552
|
+
bar.setAttribute("opacity", "0");
|
|
1553
|
+
svg.appendChild(bar);
|
|
1554
|
+
}
|
|
1555
|
+
const animations = [];
|
|
1556
|
+
for (const message of messages) {
|
|
1557
|
+
const fromX = centerX(message.from);
|
|
1558
|
+
const toX = centerX(message.to);
|
|
1559
|
+
const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1560
|
+
group.classList.add("markdy-sequence-message");
|
|
1561
|
+
group.setAttribute("data-message", message.id);
|
|
1562
|
+
group.style.opacity = "0";
|
|
1563
|
+
const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
1564
|
+
line.setAttribute("x1", String(fromX));
|
|
1565
|
+
line.setAttribute("x2", String(toX));
|
|
1566
|
+
line.setAttribute("y1", String(message.y));
|
|
1567
|
+
line.setAttribute("y2", String(message.y));
|
|
1568
|
+
line.setAttribute("stroke", theme.edges[message.kind]);
|
|
1569
|
+
line.setAttribute("stroke-width", "2");
|
|
1570
|
+
line.setAttribute("stroke-linecap", "round");
|
|
1571
|
+
if (message.kind === "response") line.setAttribute("stroke-dasharray", "6 4");
|
|
1572
|
+
if (message.kind === "event") line.setAttribute("stroke-dasharray", "2 6");
|
|
1573
|
+
if (message.kind !== "dependency") {
|
|
1574
|
+
const marker = markers.get(message.kind);
|
|
1575
|
+
if (marker) line.setAttribute("marker-end", `url(#${marker})`);
|
|
1576
|
+
}
|
|
1577
|
+
group.appendChild(line);
|
|
1578
|
+
if (message.label) {
|
|
1579
|
+
const midX = (fromX + toX) / 2;
|
|
1580
|
+
const plate = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
1581
|
+
const width = message.label.length * 6.6 + 16;
|
|
1582
|
+
plate.setAttribute("x", String(midX - width / 2));
|
|
1583
|
+
plate.setAttribute("y", String(message.y - 24));
|
|
1584
|
+
plate.setAttribute("width", String(width));
|
|
1585
|
+
plate.setAttribute("height", "18");
|
|
1586
|
+
plate.setAttribute("rx", "5");
|
|
1587
|
+
plate.setAttribute("fill", theme.labelPlate ?? theme.surface);
|
|
1588
|
+
plate.setAttribute("stroke", theme.hairline ?? theme.border);
|
|
1589
|
+
plate.setAttribute("stroke-width", "1");
|
|
1590
|
+
group.appendChild(plate);
|
|
1591
|
+
group.appendChild(createText(doc, midX, message.y - 15, message.label, theme));
|
|
1592
|
+
}
|
|
1593
|
+
svg.appendChild(group);
|
|
1594
|
+
animations.push(
|
|
1595
|
+
group.animate(
|
|
1596
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
1597
|
+
{
|
|
1598
|
+
duration: Math.max(160, message.duration * 1e3),
|
|
1599
|
+
delay: message.start * 1e3,
|
|
1600
|
+
fill: "forwards",
|
|
1601
|
+
easing: "ease-out"
|
|
1602
|
+
}
|
|
1603
|
+
)
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
const activationEls = Array.from(svg.querySelectorAll(".markdy-sequence-activation"));
|
|
1607
|
+
activationEls.forEach((bar, index) => {
|
|
1608
|
+
const activation = activations[index];
|
|
1609
|
+
if (!activation) return;
|
|
1610
|
+
animations.push(
|
|
1611
|
+
bar.animate(
|
|
1612
|
+
[{ opacity: 0 }, { opacity: 0.9, offset: 0.2 }, { opacity: 0 }],
|
|
1613
|
+
{
|
|
1614
|
+
duration: Math.max(120, activation.duration * 1e3),
|
|
1615
|
+
delay: activation.start * 1e3,
|
|
1616
|
+
fill: "none",
|
|
1617
|
+
easing: "ease-out"
|
|
1618
|
+
}
|
|
1619
|
+
)
|
|
1620
|
+
);
|
|
1621
|
+
});
|
|
1622
|
+
for (const animation of animations) animation.pause();
|
|
1623
|
+
return animations;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/tree.ts
|
|
1627
|
+
function mountTreeBuses(layer, buses, theme) {
|
|
1628
|
+
if (buses.length === 0) return;
|
|
1629
|
+
Object.assign(layer.style, {
|
|
1630
|
+
position: "absolute",
|
|
1631
|
+
inset: "0",
|
|
1632
|
+
zIndex: "42",
|
|
1633
|
+
pointerEvents: "none"
|
|
1634
|
+
});
|
|
1635
|
+
const doc = layer.ownerDocument;
|
|
1636
|
+
const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1637
|
+
Object.assign(svg.style, {
|
|
1638
|
+
position: "absolute",
|
|
1639
|
+
inset: "0",
|
|
1640
|
+
width: "100%",
|
|
1641
|
+
height: "100%",
|
|
1642
|
+
overflow: "visible"
|
|
1643
|
+
});
|
|
1644
|
+
layer.appendChild(svg);
|
|
1645
|
+
const stroke = theme.rule ?? theme.hairline ?? theme.border;
|
|
1646
|
+
for (const bus of buses) {
|
|
1647
|
+
const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
1648
|
+
group.setAttribute("data-tree-bus", bus.id);
|
|
1649
|
+
const parentLeg = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1650
|
+
parentLeg.setAttribute("d", `M ${bus.parentX} ${bus.parentY} L ${bus.parentX} ${bus.branchY}`);
|
|
1651
|
+
const childXs = [...bus.childXs].sort((a, b) => a - b);
|
|
1652
|
+
const branch = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1653
|
+
const branchStart = childXs[0] ?? bus.parentX;
|
|
1654
|
+
const branchEnd = childXs[childXs.length - 1] ?? bus.parentX;
|
|
1655
|
+
branch.setAttribute("d", `M ${branchStart} ${bus.branchY} L ${branchEnd} ${bus.branchY}`);
|
|
1656
|
+
group.append(parentLeg, branch);
|
|
1657
|
+
for (const childX of bus.childXs) {
|
|
1658
|
+
const leg = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1659
|
+
leg.setAttribute("d", `M ${childX} ${bus.branchY} L ${childX} ${bus.childY}`);
|
|
1660
|
+
group.appendChild(leg);
|
|
1661
|
+
}
|
|
1662
|
+
for (const path of Array.from(group.querySelectorAll("path"))) {
|
|
1663
|
+
path.setAttribute("fill", "none");
|
|
1664
|
+
path.setAttribute("stroke", stroke);
|
|
1665
|
+
path.setAttribute("stroke-width", "1.5");
|
|
1666
|
+
path.setAttribute("stroke-linecap", "round");
|
|
1667
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
1668
|
+
}
|
|
1669
|
+
svg.appendChild(group);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
906
1673
|
// src/theme.ts
|
|
907
1674
|
var SCENE_STYLE_ID = "markdy-scene-ambience-styles";
|
|
908
1675
|
function ensureSceneStyles(doc) {
|
|
@@ -938,6 +1705,30 @@ function ensureSceneStyles(doc) {
|
|
|
938
1705
|
opacity: 0.62;
|
|
939
1706
|
}
|
|
940
1707
|
.markdy-scene-content { z-index: 2; }
|
|
1708
|
+
.markdy-scene-root[data-markdy-theme="nebula"]::before {
|
|
1709
|
+
background:
|
|
1710
|
+
radial-gradient(circle at 18% 18%, color-mix(in srgb, var(--md-soft) 16%, transparent), transparent 32%),
|
|
1711
|
+
radial-gradient(circle at 84% 72%, color-mix(in srgb, var(--md-accent) 18%, transparent), transparent 36%),
|
|
1712
|
+
linear-gradient(var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px,
|
|
1713
|
+
linear-gradient(90deg, var(--md-grid-minor) 1px, transparent 1px) 0 0 / 32px 32px;
|
|
1714
|
+
mask-image: none;
|
|
1715
|
+
opacity: 0.9;
|
|
1716
|
+
}
|
|
1717
|
+
.markdy-scene-root[data-markdy-theme="nebula"]::after {
|
|
1718
|
+
background:
|
|
1719
|
+
radial-gradient(ellipse at 50% 42%, color-mix(in srgb, var(--md-accent) 12%, transparent), transparent 56%),
|
|
1720
|
+
linear-gradient(180deg, transparent 0%, var(--md-vignette) 100%);
|
|
1721
|
+
opacity: 0.8;
|
|
1722
|
+
}
|
|
1723
|
+
@keyframes markdy-star-twinkle {
|
|
1724
|
+
from { opacity: 0.24; transform: scale(0.85); }
|
|
1725
|
+
to { opacity: 0.9; transform: scale(1.15); }
|
|
1726
|
+
}
|
|
1727
|
+
.markdy-constellation-star {
|
|
1728
|
+
transform-box: fill-box;
|
|
1729
|
+
transform-origin: center;
|
|
1730
|
+
animation: markdy-star-twinkle 5s ease-in-out infinite alternate;
|
|
1731
|
+
}
|
|
941
1732
|
.markdy-camera-layer {
|
|
942
1733
|
position: absolute;
|
|
943
1734
|
inset: 0;
|
|
@@ -990,15 +1781,32 @@ function applyThemeToScene(scene, theme) {
|
|
|
990
1781
|
scene.style.setProperty("--md-border", theme.border);
|
|
991
1782
|
scene.style.setProperty("--md-text", theme.text);
|
|
992
1783
|
scene.style.setProperty("--md-text-muted", theme.textMuted);
|
|
1784
|
+
scene.style.setProperty("--md-paper", theme.paper ?? theme.canvas);
|
|
1785
|
+
scene.style.setProperty("--md-ink", theme.ink ?? theme.text);
|
|
1786
|
+
scene.style.setProperty("--md-muted", theme.muted ?? theme.textMuted);
|
|
1787
|
+
scene.style.setProperty("--md-rule", theme.rule ?? theme.border);
|
|
993
1788
|
scene.style.setProperty("--md-grid-minor", theme.gridMinor);
|
|
994
1789
|
scene.style.setProperty("--md-grid-major", theme.gridMajor);
|
|
995
1790
|
scene.style.setProperty("--md-vignette", theme.vignette);
|
|
996
1791
|
scene.style.setProperty("--md-accent", theme.accent);
|
|
1792
|
+
if (theme.link) scene.style.setProperty("--md-link", theme.link);
|
|
1793
|
+
if (theme.soft) scene.style.setProperty("--md-soft", theme.soft);
|
|
1794
|
+
if (theme.accentTint) scene.style.setProperty("--md-accent-tint", theme.accentTint);
|
|
997
1795
|
scene.style.setProperty("--md-node-surface", theme.nodeSurface ?? theme.surface);
|
|
998
1796
|
scene.style.setProperty("--md-node-surface-raised", theme.nodeSurfaceRaised ?? theme.surfaceRaised);
|
|
999
1797
|
scene.style.setProperty("--md-hairline", theme.hairline ?? `color-mix(in srgb, ${theme.border} 50%, transparent)`);
|
|
1000
1798
|
scene.style.setProperty("--md-shadow", theme.shadow ?? "rgba(2, 6, 23, 0.55)");
|
|
1799
|
+
if (theme.fonts?.title) scene.style.setProperty("--md-font-title", theme.fonts.title);
|
|
1800
|
+
if (theme.fonts?.nodeName) scene.style.setProperty("--md-font-node", theme.fonts.nodeName);
|
|
1801
|
+
if (theme.fonts?.mono) scene.style.setProperty("--md-font-mono", theme.fonts.mono);
|
|
1802
|
+
if (theme.radiusMd) scene.style.setProperty("--md-radius-md", `${theme.radiusMd}px`);
|
|
1803
|
+
if (theme.spacing) {
|
|
1804
|
+
for (const [key, value] of Object.entries(theme.spacing)) {
|
|
1805
|
+
scene.style.setProperty(`--md-space-${key}`, `${value}px`);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1001
1808
|
scene.dataset.markdyTheme = theme.name;
|
|
1809
|
+
if (theme.flatCards) scene.dataset.flat = "1";
|
|
1002
1810
|
}
|
|
1003
1811
|
|
|
1004
1812
|
// src/diagram.ts
|
|
@@ -1138,9 +1946,50 @@ function createDiagram(opts) {
|
|
|
1138
1946
|
const cameraLayer = document.createElement("div");
|
|
1139
1947
|
cameraLayer.className = "markdy-camera-layer";
|
|
1140
1948
|
sceneContent.appendChild(cameraLayer);
|
|
1949
|
+
const structuralEdgeHost = document.createElement("div");
|
|
1950
|
+
structuralEdgeHost.className = "markdy-structural-edge-host";
|
|
1951
|
+
Object.assign(structuralEdgeHost.style, {
|
|
1952
|
+
position: "absolute",
|
|
1953
|
+
inset: "0",
|
|
1954
|
+
zIndex: "45",
|
|
1955
|
+
pointerEvents: "none"
|
|
1956
|
+
});
|
|
1957
|
+
cameraLayer.appendChild(structuralEdgeHost);
|
|
1958
|
+
const treeLayer = document.createElement("div");
|
|
1959
|
+
treeLayer.className = "markdy-tree-layer";
|
|
1960
|
+
cameraLayer.appendChild(treeLayer);
|
|
1961
|
+
mountTreeBuses(treeLayer, plan.treeBuses, plan.theme);
|
|
1962
|
+
const constellationLayer = document.createElement("div");
|
|
1963
|
+
constellationLayer.className = "markdy-constellation-layer";
|
|
1964
|
+
cameraLayer.appendChild(constellationLayer);
|
|
1965
|
+
if (plan.diagramType === "constellation") {
|
|
1966
|
+
mountConstellationLayer(
|
|
1967
|
+
constellationLayer,
|
|
1968
|
+
plan.nodes,
|
|
1969
|
+
plan.theme,
|
|
1970
|
+
{ width: plan.meta.width, height: plan.meta.height }
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
const groupLayer = document.createElement("div");
|
|
1974
|
+
groupLayer.className = "markdy-group-layer";
|
|
1975
|
+
Object.assign(groupLayer.style, {
|
|
1976
|
+
position: "absolute",
|
|
1977
|
+
inset: "0",
|
|
1978
|
+
zIndex: "48",
|
|
1979
|
+
pointerEvents: "none"
|
|
1980
|
+
});
|
|
1981
|
+
cameraLayer.appendChild(groupLayer);
|
|
1982
|
+
mountGroupBoundaries(groupLayer, plan.groupBoundaries, plan.theme);
|
|
1983
|
+
const sequenceLayer = document.createElement("div");
|
|
1984
|
+
sequenceLayer.className = "markdy-sequence-layer";
|
|
1985
|
+
cameraLayer.appendChild(sequenceLayer);
|
|
1141
1986
|
const nodeLayer = document.createElement("div");
|
|
1142
1987
|
nodeLayer.className = "markdy-scene-node-layer";
|
|
1988
|
+
Object.assign(nodeLayer.style, { position: "absolute", inset: "0", zIndex: "60" });
|
|
1143
1989
|
cameraLayer.appendChild(nodeLayer);
|
|
1990
|
+
const annotationLayer = document.createElement("div");
|
|
1991
|
+
annotationLayer.className = "markdy-annotation-layer";
|
|
1992
|
+
cameraLayer.appendChild(annotationLayer);
|
|
1144
1993
|
const captionLayer = createBeatCaptionLayer(document, plan.beats);
|
|
1145
1994
|
sceneContent.appendChild(captionLayer);
|
|
1146
1995
|
const nodeEls = /* @__PURE__ */ new Map();
|
|
@@ -1156,13 +2005,38 @@ function createDiagram(opts) {
|
|
|
1156
2005
|
scaleScene();
|
|
1157
2006
|
const resizeObserver = new ResizeObserver(scaleScene);
|
|
1158
2007
|
resizeObserver.observe(viewport);
|
|
2008
|
+
const edgeRuntimes = /* @__PURE__ */ new Map();
|
|
2009
|
+
const edgeSceneId = createEdgeSceneId();
|
|
2010
|
+
const sequenceAnims = plan.diagramType === "sequence" ? mountSequenceLayer(
|
|
2011
|
+
sequenceLayer,
|
|
2012
|
+
plan.nodes,
|
|
2013
|
+
plan.sequenceMessages,
|
|
2014
|
+
plan.sequenceActivations,
|
|
2015
|
+
plan.theme,
|
|
2016
|
+
{ width: plan.meta.width, height: plan.meta.height }
|
|
2017
|
+
) : [];
|
|
1159
2018
|
const allAnims = [
|
|
2019
|
+
...sequenceAnims,
|
|
2020
|
+
...buildStructuralEdgeAnimations(
|
|
2021
|
+
plan.edges,
|
|
2022
|
+
plan.nodes,
|
|
2023
|
+
plan.theme,
|
|
2024
|
+
structuralEdgeHost,
|
|
2025
|
+
{ width: plan.meta.width, height: plan.meta.height },
|
|
2026
|
+
edgeRuntimes,
|
|
2027
|
+
edgeSceneId,
|
|
2028
|
+
plan.diagramType
|
|
2029
|
+
),
|
|
1160
2030
|
...buildCueAnimations(plan.cues, nodeEls, plan.nodes, plan.theme, cameraLayer, titleEl, {
|
|
1161
2031
|
width: plan.meta.width,
|
|
1162
2032
|
height: plan.meta.height
|
|
1163
|
-
}),
|
|
2033
|
+
}, plan.edges, edgeRuntimes, edgeSceneId, plan.diagramType),
|
|
1164
2034
|
...buildBeatCaptionAnimations(plan.beats, captionLayer)
|
|
1165
2035
|
];
|
|
2036
|
+
mountAnnotations(annotationLayer, plan.annotations, plan.nodes, plan.theme, {
|
|
2037
|
+
width: plan.meta.width,
|
|
2038
|
+
height: plan.meta.height
|
|
2039
|
+
});
|
|
1166
2040
|
for (const anim of allAnims) {
|
|
1167
2041
|
anim.pause();
|
|
1168
2042
|
anim.currentTime = 0;
|
|
@@ -1260,5 +2134,6 @@ function createDiagram(opts) {
|
|
|
1260
2134
|
return diagram;
|
|
1261
2135
|
}
|
|
1262
2136
|
export {
|
|
2137
|
+
ICON_REGISTRY,
|
|
1263
2138
|
createDiagram
|
|
1264
2139
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@markdy/core": "0.8.
|
|
47
|
+
"@markdy/core": "0.8.12"
|
|
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.12"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"build": "tsup",
|