@markdy/renderer-dom 0.8.0 → 0.8.1
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 +66 -18
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -62,25 +62,57 @@ function polylineLength(points) {
|
|
|
62
62
|
function segmentLength(a, b) {
|
|
63
63
|
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
var LABEL_BOX_HEIGHT = 16;
|
|
66
|
+
function rectsOverlap(a, b) {
|
|
67
|
+
return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
|
|
68
|
+
}
|
|
69
|
+
function overlapCount(rect, obstacles) {
|
|
70
|
+
let hits = 0;
|
|
71
|
+
for (const o of obstacles) if (rectsOverlap(rect, o)) hits++;
|
|
72
|
+
return hits;
|
|
73
|
+
}
|
|
74
|
+
function placeFlowLabel(points, textWidth, obstacles, bounds) {
|
|
67
75
|
let bestIndex = 0;
|
|
68
76
|
let bestLength = -1;
|
|
69
77
|
for (let i = 0; i < points.length - 1; i++) {
|
|
70
78
|
const len = segmentLength(points[i], points[i + 1]);
|
|
71
79
|
if (len > bestLength) {
|
|
72
|
-
bestIndex = i;
|
|
73
80
|
bestLength = len;
|
|
81
|
+
bestIndex = i;
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
|
-
const a = points[bestIndex];
|
|
77
|
-
const b = points[bestIndex + 1];
|
|
84
|
+
const a = points[bestIndex] ?? { x: 0, y: 0 };
|
|
85
|
+
const b = points[bestIndex + 1] ?? a;
|
|
78
86
|
const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const horizontal = Math.abs(a.x - b.x) >= Math.abs(a.y - b.y);
|
|
88
|
+
const half = textWidth / 2;
|
|
89
|
+
const halfH = LABEL_BOX_HEIGHT / 2;
|
|
90
|
+
const pad = 8;
|
|
91
|
+
const base = horizontal ? 14 : half + 12;
|
|
92
|
+
const step = horizontal ? LABEL_BOX_HEIGHT + 4 : textWidth + 12;
|
|
93
|
+
const order = horizontal ? [-1, 1] : [1, -1];
|
|
94
|
+
const offsets = [];
|
|
95
|
+
for (let k = 0; k < 8; k++) {
|
|
96
|
+
for (const sign of order) offsets.push(sign * (base + k * step));
|
|
97
|
+
}
|
|
98
|
+
let fallback = null;
|
|
99
|
+
let fallbackHits = Number.POSITIVE_INFINITY;
|
|
100
|
+
for (const off of offsets) {
|
|
101
|
+
const cx = clamp(horizontal ? mid.x : mid.x + off, pad + half, bounds.width - pad - half);
|
|
102
|
+
const cy = clamp(horizontal ? mid.y + off : mid.y, pad + halfH, bounds.height - pad - halfH);
|
|
103
|
+
const rect = { x1: cx - half, y1: cy - halfH, x2: cx + half, y2: cy + halfH };
|
|
104
|
+
const hits = overlapCount(rect, obstacles);
|
|
105
|
+
if (hits === 0) return { x: round1(cx), y: round1(cy), rect };
|
|
106
|
+
if (hits < fallbackHits) {
|
|
107
|
+
fallbackHits = hits;
|
|
108
|
+
fallback = { x: round1(cx), y: round1(cy), rect };
|
|
109
|
+
}
|
|
82
110
|
}
|
|
83
|
-
return
|
|
111
|
+
return fallback ?? {
|
|
112
|
+
x: round1(mid.x),
|
|
113
|
+
y: round1(mid.y),
|
|
114
|
+
rect: { x1: mid.x - half, y1: mid.y - halfH, x2: mid.x + half, y2: mid.y + halfH }
|
|
115
|
+
};
|
|
84
116
|
}
|
|
85
117
|
function clamp(n, min, max) {
|
|
86
118
|
if (max < min) return n;
|
|
@@ -214,11 +246,11 @@ function ensureDefs(svg, theme, id) {
|
|
|
214
246
|
}
|
|
215
247
|
svg.prepend(defs);
|
|
216
248
|
}
|
|
217
|
-
function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId,
|
|
249
|
+
function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane) {
|
|
218
250
|
ensureDefs(svg, theme, sceneId);
|
|
219
251
|
const color = theme.edges[kind];
|
|
220
252
|
const style = EDGE_STYLES[kind];
|
|
221
|
-
const points = dedupePoints(routeOrthogonal(boxRect(from), boxRect(to),
|
|
253
|
+
const points = dedupePoints(routeOrthogonal(boxRect(from), boxRect(to), routeObstacles, bounds, lane));
|
|
222
254
|
const d = toPathD(points);
|
|
223
255
|
const len = polylineLength(points);
|
|
224
256
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
@@ -243,21 +275,29 @@ function createEdgeRuntime(svg, from, to, kind, label, theme, sceneId, obstacles
|
|
|
243
275
|
dot.style.filter = `drop-shadow(0 0 6px ${color})`;
|
|
244
276
|
group.append(path, dot);
|
|
245
277
|
let labelEl;
|
|
278
|
+
let labelRect;
|
|
246
279
|
if (label) {
|
|
247
|
-
const
|
|
280
|
+
const textWidth = label.length * 6.6 + 10;
|
|
281
|
+
const placement = placeFlowLabel(points, textWidth, labelObstacles, bounds);
|
|
282
|
+
labelRect = placement.rect;
|
|
248
283
|
labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
249
|
-
labelEl.setAttribute("x", String(
|
|
250
|
-
labelEl.setAttribute("y", String(
|
|
284
|
+
labelEl.setAttribute("x", String(placement.x));
|
|
285
|
+
labelEl.setAttribute("y", String(placement.y));
|
|
251
286
|
labelEl.setAttribute("text-anchor", "middle");
|
|
287
|
+
labelEl.setAttribute("dominant-baseline", "middle");
|
|
252
288
|
labelEl.setAttribute("font-size", "11");
|
|
253
289
|
labelEl.setAttribute("font-family", "ui-monospace, SFMono-Regular, Menlo, monospace");
|
|
254
290
|
labelEl.setAttribute("fill", theme.textMuted);
|
|
291
|
+
labelEl.setAttribute("stroke", theme.canvas);
|
|
292
|
+
labelEl.setAttribute("stroke-width", "3");
|
|
293
|
+
labelEl.setAttribute("paint-order", "stroke");
|
|
294
|
+
labelEl.setAttribute("stroke-linejoin", "round");
|
|
255
295
|
labelEl.textContent = label;
|
|
256
296
|
labelEl.style.opacity = "0";
|
|
257
297
|
group.appendChild(labelEl);
|
|
258
298
|
}
|
|
259
299
|
svg.appendChild(group);
|
|
260
|
-
return { group, path, label: labelEl, dot, pathLen: len, points };
|
|
300
|
+
return { group, path, label: labelEl, dot, pathLen: len, points, labelRect };
|
|
261
301
|
}
|
|
262
302
|
function dotTravelKeyframes(points) {
|
|
263
303
|
const total = polylineLength(points);
|
|
@@ -309,6 +349,9 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
309
349
|
const anims = [];
|
|
310
350
|
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
311
351
|
const rectById = new Map(nodes.map((n) => [n.id, boxRect(n)]));
|
|
352
|
+
const allNodeRects = [...rectById.values()];
|
|
353
|
+
const placedLabels = [];
|
|
354
|
+
const laneByPair = /* @__PURE__ */ new Map();
|
|
312
355
|
const svg = ensureEdgeLayer(scene);
|
|
313
356
|
const sceneId = `md-${Math.random().toString(36).slice(2, 8)}`;
|
|
314
357
|
anims.push(
|
|
@@ -376,11 +419,16 @@ function buildCueAnimations(cues, nodeEls, nodes, theme, scene, titleEl, bounds)
|
|
|
376
419
|
const from = nodeById.get(seg.from);
|
|
377
420
|
const to = nodeById.get(seg.to);
|
|
378
421
|
if (!from || !to) continue;
|
|
379
|
-
const
|
|
422
|
+
const routeObstacles = [];
|
|
380
423
|
for (const [id, rect] of rectById) {
|
|
381
|
-
if (id !== seg.from && id !== seg.to)
|
|
424
|
+
if (id !== seg.from && id !== seg.to) routeObstacles.push(rect);
|
|
382
425
|
}
|
|
383
|
-
const
|
|
426
|
+
const pairKey = [seg.from, seg.to].sort().join("|");
|
|
427
|
+
const lane = laneByPair.get(pairKey) ?? 0;
|
|
428
|
+
laneByPair.set(pairKey, lane + 1);
|
|
429
|
+
const labelObstacles = [...allNodeRects, ...placedLabels];
|
|
430
|
+
const runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, routeObstacles, labelObstacles, bounds, lane);
|
|
431
|
+
if (runtime.labelRect) placedLabels.push(runtime.labelRect);
|
|
384
432
|
anims.push(...animateEdgeReveal(runtime, startMs, durMs));
|
|
385
433
|
}
|
|
386
434
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/renderer-dom",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"description": "Browser renderer for animated MarkdyScript architecture diagrams, built on the Web Animations API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"markdy",
|
|
23
|
-
"
|
|
23
|
+
"animated-diagram",
|
|
24
|
+
"architecture-diagram",
|
|
25
|
+
"diagram-as-code",
|
|
24
26
|
"web-animations-api",
|
|
25
27
|
"renderer",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"motion"
|
|
29
|
-
"framer-motion-alternative",
|
|
30
|
-
"gsap-alternative"
|
|
28
|
+
"mermaid-alternative",
|
|
29
|
+
"svg",
|
|
30
|
+
"motion"
|
|
31
31
|
],
|
|
32
32
|
"author": "Hoang Yell <hoangyell@gmail.com> (https://hoangyell.com)",
|
|
33
33
|
"homepage": "https://markdy.com",
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@markdy/core": "0.8.
|
|
46
|
+
"@markdy/core": "0.8.1"
|
|
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.1"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsup",
|