@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.
Files changed (2) hide show
  1. package/dist/index.js +66 -18
  2. 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
- function labelPointForPath(points, lane) {
66
- if (points.length < 2) return points[0] ?? { x: 0, y: 0 };
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 offset = lane * 8;
80
- if (Math.abs(a.x - b.x) >= Math.abs(a.y - b.y)) {
81
- return { x: round1(mid.x), y: round1(mid.y - 10 - offset) };
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 { x: round1(mid.x + 10 + offset), y: round1(mid.y) };
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, obstacles, bounds) {
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), obstacles, bounds));
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 mid = labelPointForPath(points, 0);
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(mid.x));
250
- labelEl.setAttribute("y", String(mid.y));
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 obstacles = [];
422
+ const routeObstacles = [];
380
423
  for (const [id, rect] of rectById) {
381
- if (id !== seg.from && id !== seg.to) obstacles.push(rect);
424
+ if (id !== seg.from && id !== seg.to) routeObstacles.push(rect);
382
425
  }
383
- const runtime = createEdgeRuntime(svg, from, to, seg.op, seg.label, theme, sceneId, obstacles, bounds);
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.0",
4
- "description": "Web Animations API renderer for MarkdyScript scenes.",
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
- "animation",
23
+ "animated-diagram",
24
+ "architecture-diagram",
25
+ "diagram-as-code",
24
26
  "web-animations-api",
25
27
  "renderer",
26
- "dom",
27
- "declarative-animation",
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.0"
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.0"
53
+ "@markdy/stdlib-systems": "0.8.1"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsup",