@markdy/renderer-dom 0.8.4 → 0.8.6
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/dist/index.js +149 -8
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -199,6 +199,10 @@ var EDGE_STYLES = {
|
|
|
199
199
|
event: { dash: "2 6", marker: "dot" },
|
|
200
200
|
dependency: { dash: "4 4", marker: "none" }
|
|
201
201
|
};
|
|
202
|
+
var INITIAL_CAMERA_TRANSFORM = "translate(0px, 0px) scale(1)";
|
|
203
|
+
var DEFAULT_FRAME_ZOOM = 1.18;
|
|
204
|
+
var MAX_FRAME_ZOOM = 1.75;
|
|
205
|
+
var FRAME_PADDING = 88;
|
|
202
206
|
function dedupePoints(points) {
|
|
203
207
|
const out = [];
|
|
204
208
|
for (const p of points) {
|
|
@@ -362,6 +366,27 @@ function animateEdgeReveal(runtime, startMs, durMs) {
|
|
|
362
366
|
}
|
|
363
367
|
return anims;
|
|
364
368
|
}
|
|
369
|
+
function computeFrameTransform(targetIds, nodes, bounds, requestedZoom = DEFAULT_FRAME_ZOOM) {
|
|
370
|
+
const targets = new Set(targetIds);
|
|
371
|
+
const selected = nodes.filter((node) => targets.has(node.id));
|
|
372
|
+
if (selected.length === 0) return void 0;
|
|
373
|
+
const framesEveryNode = selected.length === nodes.length && nodes.every((node) => targets.has(node.id));
|
|
374
|
+
if (framesEveryNode) return INITIAL_CAMERA_TRANSFORM;
|
|
375
|
+
const zoom = Number.isFinite(requestedZoom) && requestedZoom > 0 ? requestedZoom : DEFAULT_FRAME_ZOOM;
|
|
376
|
+
const minX = Math.min(...selected.map((node) => node.x));
|
|
377
|
+
const minY = Math.min(...selected.map((node) => node.y));
|
|
378
|
+
const maxX = Math.max(...selected.map((node) => node.x + node.width));
|
|
379
|
+
const maxY = Math.max(...selected.map((node) => node.y + node.height));
|
|
380
|
+
const frameWidth = Math.max(1, maxX - minX + FRAME_PADDING * 2);
|
|
381
|
+
const frameHeight = Math.max(1, maxY - minY + FRAME_PADDING * 2);
|
|
382
|
+
const fitScale = Math.min(bounds.width / frameWidth, bounds.height / frameHeight);
|
|
383
|
+
const scale = Math.max(1, Math.min(zoom, fitScale, MAX_FRAME_ZOOM));
|
|
384
|
+
const centerX = (minX + maxX) / 2;
|
|
385
|
+
const centerY = (minY + maxY) / 2;
|
|
386
|
+
const tx = Math.round((bounds.width / 2 - centerX * scale) * 10) / 10;
|
|
387
|
+
const ty = Math.round((bounds.height / 2 - centerY * scale) * 10) / 10;
|
|
388
|
+
return `translate(${tx}px, ${ty}px) scale(${Math.round(scale * 1e3) / 1e3})`;
|
|
389
|
+
}
|
|
365
390
|
function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds) {
|
|
366
391
|
const anims = [];
|
|
367
392
|
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
@@ -371,6 +396,9 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
371
396
|
const laneByPair = /* @__PURE__ */ new Map();
|
|
372
397
|
const svg = ensureEdgeLayer(scene);
|
|
373
398
|
const sceneId = `md-${Math.random().toString(36).slice(2, 8)}`;
|
|
399
|
+
let cameraTransform = scene.style.transform || INITIAL_CAMERA_TRANSFORM;
|
|
400
|
+
scene.style.transformOrigin = "0 0";
|
|
401
|
+
scene.style.transform = cameraTransform;
|
|
374
402
|
anims.push(
|
|
375
403
|
titleEl.animate(
|
|
376
404
|
[{ opacity: 0, transform: "translateY(-6px)" }, { opacity: 1, transform: "translateY(0)" }],
|
|
@@ -418,19 +446,37 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
418
446
|
continue;
|
|
419
447
|
}
|
|
420
448
|
if (cue.kind === "focus") {
|
|
449
|
+
const zoom = typeof cue.params.zoom === "number" && cue.params.zoom > 0 ? cue.params.zoom : 1.03;
|
|
421
450
|
for (const id of cue.targets) {
|
|
422
451
|
const el = nodeEls.get(id);
|
|
423
452
|
if (!el) continue;
|
|
424
453
|
el.dataset.focused = "1";
|
|
425
454
|
anims.push(
|
|
426
455
|
el.animate(
|
|
427
|
-
[{ transform: "scale(1)" }, { transform:
|
|
456
|
+
[{ transform: "scale(1)" }, { transform: `scale(${zoom})` }, { transform: "scale(1)" }],
|
|
428
457
|
{ duration: durMs, delay: startMs, fill: "forwards" }
|
|
429
458
|
)
|
|
430
459
|
);
|
|
431
460
|
}
|
|
432
461
|
continue;
|
|
433
462
|
}
|
|
463
|
+
if (cue.kind === "frame") {
|
|
464
|
+
const nextTransform = computeFrameTransform(
|
|
465
|
+
cue.targets,
|
|
466
|
+
nodes,
|
|
467
|
+
bounds,
|
|
468
|
+
typeof cue.params.zoom === "number" ? cue.params.zoom : DEFAULT_FRAME_ZOOM
|
|
469
|
+
);
|
|
470
|
+
if (!nextTransform) continue;
|
|
471
|
+
anims.push(
|
|
472
|
+
scene.animate(
|
|
473
|
+
[{ transform: cameraTransform }, { transform: nextTransform }],
|
|
474
|
+
{ duration: durMs, delay: startMs, fill: "forwards", easing: "ease-in-out" }
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
cameraTransform = nextTransform;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
434
480
|
if (cue.kind === "flow" && cue.segments?.[0]) {
|
|
435
481
|
const seg = cue.segments[0];
|
|
436
482
|
const from = nodeById.get(seg.from);
|
|
@@ -577,6 +623,7 @@ function ensureNodeStyles(doc) {
|
|
|
577
623
|
left: 44px;
|
|
578
624
|
top: 26px;
|
|
579
625
|
right: 44px;
|
|
626
|
+
z-index: 130;
|
|
580
627
|
font-size: 32px;
|
|
581
628
|
font-weight: 700;
|
|
582
629
|
line-height: 1.2;
|
|
@@ -808,6 +855,20 @@ function createNodeMediaEl(doc, node, assets) {
|
|
|
808
855
|
appendGlyph(doc, wrap, ICONS[iconKeyForNode(node)] ?? ICONS.service);
|
|
809
856
|
return wrap;
|
|
810
857
|
}
|
|
858
|
+
function applyDeclaredNodeStyle(el, style) {
|
|
859
|
+
if (!style) return;
|
|
860
|
+
const fill = typeof style.fill === "string" ? style.fill : void 0;
|
|
861
|
+
const surface = typeof style.surface === "string" ? style.surface : fill;
|
|
862
|
+
const surfaceRaised = typeof style.surfaceRaised === "string" ? style.surfaceRaised : surface ? `color-mix(in srgb, ${surface} 90%, #ffffff 10%)` : void 0;
|
|
863
|
+
const stroke = typeof style.stroke === "string" ? style.stroke : void 0;
|
|
864
|
+
const text = typeof style.text === "string" ? style.text : void 0;
|
|
865
|
+
const accent = typeof style.accent === "string" ? style.accent : void 0;
|
|
866
|
+
if (surface) el.style.setProperty("--md-node-surface", surface);
|
|
867
|
+
if (surfaceRaised) el.style.setProperty("--md-node-surface-raised", surfaceRaised);
|
|
868
|
+
if (stroke) el.style.setProperty("--md-hairline", stroke);
|
|
869
|
+
if (text) el.style.color = text;
|
|
870
|
+
if (accent) el.style.setProperty("--md-role-color", accent);
|
|
871
|
+
}
|
|
811
872
|
function createNodeEl(node, theme, assets) {
|
|
812
873
|
const el = document.createElement("div");
|
|
813
874
|
el.className = "markdy-node markdy-scene-node";
|
|
@@ -819,6 +880,7 @@ function createNodeEl(node, theme, assets) {
|
|
|
819
880
|
el.style.setProperty("--md-node-h", `${node.height}px`);
|
|
820
881
|
const roleColor = theme.roles[node.role] ?? theme.accent;
|
|
821
882
|
el.style.setProperty("--md-role-color", roleColor);
|
|
883
|
+
applyDeclaredNodeStyle(el, node.style);
|
|
822
884
|
const typeText = node.kind.replace(/_/g, " ");
|
|
823
885
|
el.dataset.kind = node.kind;
|
|
824
886
|
el.dataset.icon = iconKeyForNode(node);
|
|
@@ -876,11 +938,46 @@ function ensureSceneStyles(doc) {
|
|
|
876
938
|
opacity: 0.62;
|
|
877
939
|
}
|
|
878
940
|
.markdy-scene-content { z-index: 2; }
|
|
941
|
+
.markdy-camera-layer {
|
|
942
|
+
position: absolute;
|
|
943
|
+
inset: 0;
|
|
944
|
+
overflow: visible;
|
|
945
|
+
transform-origin: 0 0;
|
|
946
|
+
transform: translate(0px, 0px) scale(1);
|
|
947
|
+
}
|
|
879
948
|
.markdy-scene-node-layer {
|
|
880
949
|
position: absolute;
|
|
881
950
|
inset: 0;
|
|
882
951
|
overflow: visible;
|
|
883
952
|
}
|
|
953
|
+
.markdy-beat-caption-layer {
|
|
954
|
+
position: absolute;
|
|
955
|
+
left: 44px;
|
|
956
|
+
right: 44px;
|
|
957
|
+
bottom: 30px;
|
|
958
|
+
z-index: 140;
|
|
959
|
+
display: grid;
|
|
960
|
+
justify-items: center;
|
|
961
|
+
align-items: end;
|
|
962
|
+
pointer-events: none;
|
|
963
|
+
}
|
|
964
|
+
.markdy-beat-caption {
|
|
965
|
+
grid-area: 1 / 1;
|
|
966
|
+
max-width: min(720px, 100%);
|
|
967
|
+
padding: 10px 16px;
|
|
968
|
+
border-radius: 999px;
|
|
969
|
+
color: var(--md-text);
|
|
970
|
+
background: color-mix(in srgb, var(--md-surface-raised) 88%, transparent);
|
|
971
|
+
backdrop-filter: blur(6px);
|
|
972
|
+
box-shadow:
|
|
973
|
+
0 14px 32px -20px var(--md-shadow, rgba(2, 6, 23, 0.55)),
|
|
974
|
+
inset 0 0 0 1px var(--md-hairline, color-mix(in srgb, var(--md-border) 50%, transparent));
|
|
975
|
+
font: 600 14px/1.3 Inter, ui-sans-serif, system-ui, sans-serif;
|
|
976
|
+
text-align: center;
|
|
977
|
+
opacity: 0;
|
|
978
|
+
transform: translateY(8px);
|
|
979
|
+
will-change: opacity, transform;
|
|
980
|
+
}
|
|
884
981
|
`;
|
|
885
982
|
doc.head.appendChild(style);
|
|
886
983
|
}
|
|
@@ -905,6 +1002,42 @@ function applyThemeToScene(scene, theme) {
|
|
|
905
1002
|
}
|
|
906
1003
|
|
|
907
1004
|
// src/diagram.ts
|
|
1005
|
+
function createBeatCaptionLayer(doc, beats) {
|
|
1006
|
+
const layer = doc.createElement("div");
|
|
1007
|
+
layer.className = "markdy-beat-caption-layer";
|
|
1008
|
+
for (const beat of beats) {
|
|
1009
|
+
if (!beat.label) continue;
|
|
1010
|
+
const caption = doc.createElement("div");
|
|
1011
|
+
caption.className = "markdy-beat-caption";
|
|
1012
|
+
caption.textContent = beat.label;
|
|
1013
|
+
caption.dataset.beat = beat.name;
|
|
1014
|
+
layer.appendChild(caption);
|
|
1015
|
+
}
|
|
1016
|
+
return layer;
|
|
1017
|
+
}
|
|
1018
|
+
function buildBeatCaptionAnimations(beats, layer) {
|
|
1019
|
+
const anims = [];
|
|
1020
|
+
const captions = Array.from(layer.querySelectorAll(".markdy-beat-caption"));
|
|
1021
|
+
for (const beat of beats) {
|
|
1022
|
+
if (!beat.label) continue;
|
|
1023
|
+
const caption = captions.find((item) => item.dataset.beat === beat.name);
|
|
1024
|
+
if (!caption) continue;
|
|
1025
|
+
const startMs = beat.start * 1e3;
|
|
1026
|
+
const durMs = Math.max((beat.end - beat.start) * 1e3, 650);
|
|
1027
|
+
anims.push(
|
|
1028
|
+
caption.animate(
|
|
1029
|
+
[
|
|
1030
|
+
{ opacity: 0, transform: "translateY(8px)" },
|
|
1031
|
+
{ opacity: 1, transform: "translateY(0)", offset: 0.16 },
|
|
1032
|
+
{ opacity: 1, transform: "translateY(0)", offset: 0.82 },
|
|
1033
|
+
{ opacity: 0, transform: "translateY(-4px)" }
|
|
1034
|
+
],
|
|
1035
|
+
{ duration: durMs, delay: startMs, fill: "forwards", easing: "ease-out" }
|
|
1036
|
+
)
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
return anims;
|
|
1040
|
+
}
|
|
908
1041
|
function createDiagram(opts) {
|
|
909
1042
|
const {
|
|
910
1043
|
container,
|
|
@@ -1000,11 +1133,16 @@ function createDiagram(opts) {
|
|
|
1000
1133
|
sceneContent.className = "markdy-scene-content";
|
|
1001
1134
|
Object.assign(sceneContent.style, { position: "absolute", inset: "0" });
|
|
1002
1135
|
scene.appendChild(sceneContent);
|
|
1136
|
+
const titleEl = createTitleEl(plan.title);
|
|
1137
|
+
sceneContent.appendChild(titleEl);
|
|
1138
|
+
const cameraLayer = document.createElement("div");
|
|
1139
|
+
cameraLayer.className = "markdy-camera-layer";
|
|
1140
|
+
sceneContent.appendChild(cameraLayer);
|
|
1003
1141
|
const nodeLayer = document.createElement("div");
|
|
1004
1142
|
nodeLayer.className = "markdy-scene-node-layer";
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1143
|
+
cameraLayer.appendChild(nodeLayer);
|
|
1144
|
+
const captionLayer = createBeatCaptionLayer(document, plan.beats);
|
|
1145
|
+
sceneContent.appendChild(captionLayer);
|
|
1008
1146
|
const nodeEls = /* @__PURE__ */ new Map();
|
|
1009
1147
|
for (const node of plan.nodes) {
|
|
1010
1148
|
const el = createNodeEl(node, plan.theme, assets);
|
|
@@ -1018,10 +1156,13 @@ function createDiagram(opts) {
|
|
|
1018
1156
|
scaleScene();
|
|
1019
1157
|
const resizeObserver = new ResizeObserver(scaleScene);
|
|
1020
1158
|
resizeObserver.observe(viewport);
|
|
1021
|
-
const allAnims =
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1159
|
+
const allAnims = [
|
|
1160
|
+
...buildCueAnimations(plan.cues, nodeEls, plan.nodes, plan.theme, cameraLayer, titleEl, {
|
|
1161
|
+
width: plan.meta.width,
|
|
1162
|
+
height: plan.meta.height
|
|
1163
|
+
}),
|
|
1164
|
+
...buildBeatCaptionAnimations(plan.beats, captionLayer)
|
|
1165
|
+
];
|
|
1025
1166
|
for (const anim of allAnims) {
|
|
1026
1167
|
anim.pause();
|
|
1027
1168
|
anim.currentTime = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
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,14 +43,14 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@markdy/core": "0.8.
|
|
46
|
+
"@markdy/core": "0.8.6"
|
|
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.6"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsup",
|