@markdy/renderer-dom 0.7.28 → 0.7.29

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 +83 -23
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1748,25 +1748,59 @@ function polylineLength(points) {
1748
1748
  function segmentLength(a, b) {
1749
1749
  return Math.hypot(b.x - a.x, b.y - a.y);
1750
1750
  }
1751
- function labelPointForPath(points, lane) {
1752
- if (points.length < 2) return points[0] ?? { x: 0, y: 0 };
1751
+ function rectsOverlap(a, b) {
1752
+ return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1;
1753
+ }
1754
+ function overlapCount(rect, obstacles) {
1755
+ let hits = 0;
1756
+ for (const o of obstacles) if (rectsOverlap(rect, o)) hits++;
1757
+ return hits;
1758
+ }
1759
+ var LABEL_BOX_HEIGHT = 18;
1760
+ function placeFlowLabel(points, textWidth, obstacles, ast) {
1753
1761
  let bestIndex = 0;
1754
1762
  let bestLength = -1;
1755
1763
  for (let i = 0; i < points.length - 1; i++) {
1756
1764
  const len = segmentLength(points[i], points[i + 1]);
1757
1765
  if (len > bestLength) {
1758
- bestIndex = i;
1759
1766
  bestLength = len;
1767
+ bestIndex = i;
1760
1768
  }
1761
1769
  }
1762
- const a = points[bestIndex];
1763
- const b = points[bestIndex + 1];
1770
+ const a = points[bestIndex] ?? { x: 0, y: 0 };
1771
+ const b = points[bestIndex + 1] ?? a;
1764
1772
  const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
1765
- const offset = lane * 8;
1766
- if (Math.abs(a.x - b.x) >= Math.abs(a.y - b.y)) {
1767
- return { x: mid.x, y: mid.y - 10 - offset };
1773
+ const horizontal = Math.abs(a.x - b.x) >= Math.abs(a.y - b.y);
1774
+ const half = textWidth / 2;
1775
+ const halfH = LABEL_BOX_HEIGHT / 2;
1776
+ const pad = 8;
1777
+ const base = horizontal ? 15 : half + 12;
1778
+ const step = horizontal ? LABEL_BOX_HEIGHT + 3 : textWidth + 12;
1779
+ const order = horizontal ? [-1, 1] : [1, -1];
1780
+ const offsets = [];
1781
+ for (let k = 0; k < 7; k++) {
1782
+ for (const sign of order) offsets.push(sign * (base + k * step));
1768
1783
  }
1769
- return { x: mid.x + 10 + offset, y: mid.y };
1784
+ let fallback = null;
1785
+ let fallbackHits = Number.POSITIVE_INFINITY;
1786
+ for (const off of offsets) {
1787
+ let cx = horizontal ? mid.x : mid.x + off;
1788
+ let cy = horizontal ? mid.y + off : mid.y;
1789
+ cx = clamp(cx, pad + half, ast.meta.width - pad - half);
1790
+ cy = clamp(cy, pad + halfH, ast.meta.height - pad - halfH);
1791
+ const rect = { x1: cx - half, y1: cy - halfH, x2: cx + half, y2: cy + halfH };
1792
+ const hits = overlapCount(rect, obstacles);
1793
+ if (hits === 0) return { x: round1(cx), y: round1(cy), rect };
1794
+ if (hits < fallbackHits) {
1795
+ fallbackHits = hits;
1796
+ fallback = { x: round1(cx), y: round1(cy), rect };
1797
+ }
1798
+ }
1799
+ return fallback ?? {
1800
+ x: round1(mid.x),
1801
+ y: round1(mid.y),
1802
+ rect: { x1: mid.x - half, y1: mid.y - halfH, x2: mid.x + half, y2: mid.y + halfH }
1803
+ };
1770
1804
  }
1771
1805
  function clamp(n, min, max) {
1772
1806
  if (max < min) return n;
@@ -1863,7 +1897,9 @@ var STROKE_BY_ACTION = {
1863
1897
  };
1864
1898
  var DEFAULT_STROKE = "#38bdf8";
1865
1899
  var LABEL_MAX_CHARS = 28;
1866
- var EDGE_FADE_MS = 140;
1900
+ var LABEL_CHAR_PX = 6.8;
1901
+ var EDGE_REVEAL_MS = 220;
1902
+ var LABEL_RECTS = /* @__PURE__ */ new WeakMap();
1867
1903
  function ensureEdgeLayer(scene) {
1868
1904
  const existing = scene.querySelector(`svg[${EDGE_LAYER_ATTR}='1']`);
1869
1905
  if (existing) return existing;
@@ -1920,6 +1956,12 @@ function isDashed(ctx) {
1920
1956
  const style = String(ctx.ev.params.style ?? "");
1921
1957
  return style === "dashed" || style === "fire_and_forget" || ctx.ev.action === "response";
1922
1958
  }
1959
+ function nodeObstacleRect(state, type, sceneWidth) {
1960
+ const rect = actorRect(state, type);
1961
+ if (type !== "caption") return rect;
1962
+ const halfH = (rect.y2 - rect.y1) / 2 + 4;
1963
+ return { x1: 0, y1: state.y - halfH, x2: sceneWidth, y2: state.y + halfH };
1964
+ }
1923
1965
  function buildEdge(ctx, targetName) {
1924
1966
  const { ev, state, states, ast, scene, baseOpts, anims } = ctx;
1925
1967
  const targetState = states.get(targetName);
@@ -1931,8 +1973,12 @@ function buildEdge(ctx, targetName) {
1931
1973
  const stroke = STROKE_BY_ACTION[ev.action] ?? DEFAULT_STROKE;
1932
1974
  const svg = ensureEdgeLayer(scene);
1933
1975
  ensureEdgeDefs(svg);
1976
+ const startMs = Number(baseOpts.delay ?? 0);
1977
+ const durationMs = Math.max(1, Number(baseOpts.duration ?? 500));
1978
+ const revealMs = Math.min(EDGE_REVEAL_MS, durationMs);
1934
1979
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
1935
1980
  group.setAttribute("data-markdy-flow-edge", "1");
1981
+ group.style.opacity = "0";
1936
1982
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
1937
1983
  path.setAttribute("d", pathD);
1938
1984
  path.setAttribute("fill", "none");
@@ -1956,37 +2002,51 @@ function buildEdge(ctx, targetName) {
1956
2002
  group.appendChild(marker);
1957
2003
  const labelText = String(ev.params.label ?? "");
1958
2004
  if (labelText) {
1959
- const midPoint = labelPointForPath(points, lane);
2005
+ const shown = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
2006
+ const textWidth = shown.length * LABEL_CHAR_PX + 12;
2007
+ const placed = LABEL_RECTS.get(svg) ?? (LABEL_RECTS.set(svg, []), LABEL_RECTS.get(svg));
2008
+ const obstacles = [...placed];
2009
+ for (const [name, nodeState] of states.entries()) {
2010
+ const type = ast.actors[name]?.type ?? "box";
2011
+ obstacles.push(inflateRect(nodeObstacleRect(nodeState, type, ast.meta.width), 2));
2012
+ }
2013
+ const spot = placeFlowLabel(points, textWidth, obstacles, ast);
2014
+ placed.push(spot.rect);
1960
2015
  const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
1961
- label.setAttribute("x", `${round1(midPoint.x)}`);
1962
- label.setAttribute("y", `${round1(midPoint.y - 8)}`);
2016
+ label.setAttribute("x", `${spot.x}`);
2017
+ label.setAttribute("y", `${spot.y}`);
1963
2018
  label.setAttribute("text-anchor", "middle");
2019
+ label.setAttribute("dominant-baseline", "middle");
1964
2020
  label.setAttribute("font-size", "12");
1965
2021
  label.setAttribute("font-weight", "700");
1966
2022
  label.setAttribute("paint-order", "stroke");
1967
- label.setAttribute("stroke", "rgba(2, 6, 23, 0.88)");
2023
+ label.setAttribute("stroke", "rgba(2, 6, 23, 0.9)");
1968
2024
  label.setAttribute("stroke-width", "5");
1969
2025
  label.setAttribute("stroke-linejoin", "round");
1970
- label.setAttribute("fill", "#cbd5e1");
1971
- label.setAttribute("filter", "url(#markdy-flow-glow)");
2026
+ label.setAttribute("fill", "#dbe4f0");
1972
2027
  label.setAttribute("data-full-label", labelText);
1973
- label.textContent = labelText.length > LABEL_MAX_CHARS ? `${labelText.slice(0, LABEL_MAX_CHARS - 1)}\u2026` : labelText;
2028
+ label.textContent = shown;
1974
2029
  group.appendChild(label);
1975
2030
  }
1976
2031
  svg.appendChild(group);
1977
2032
  anims.push(
1978
2033
  path.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], baseOpts),
2034
+ // Travel the dot along the edge, then fade it at arrival so the finished
2035
+ // edge is a clean line + arrowhead rather than a dot resting on the tip.
1979
2036
  marker.animate(
1980
2037
  [
1981
- { offsetDistance: "0%", opacity: 1 },
1982
- { offsetDistance: "100%", opacity: 1 }
2038
+ { offsetDistance: "0%", opacity: 1, offset: 0 },
2039
+ { offsetDistance: "100%", opacity: 1, offset: 0.82 },
2040
+ { offsetDistance: "100%", opacity: 0, offset: 1 }
1983
2041
  ],
1984
2042
  baseOpts
1985
2043
  ),
1986
- group.animate([{ opacity: 1 }, { opacity: 0 }], {
1987
- delay: Number(baseOpts.delay ?? 0) + Number(baseOpts.duration ?? 0),
1988
- duration: EDGE_FADE_MS,
1989
- fill: "forwards"
2044
+ // Reveal the edge at its scheduled time and keep it (persist). `fill: both`
2045
+ // holds opacity 0 during the delay, so nothing appears before its turn.
2046
+ group.animate([{ opacity: 0 }, { opacity: 1 }], {
2047
+ delay: startMs,
2048
+ duration: revealMs,
2049
+ fill: "both"
1990
2050
  })
1991
2051
  );
1992
2052
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.7.28",
3
+ "version": "0.7.29",
4
4
  "description": "Web Animations API renderer for MarkdyScript scenes.",
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.7.28"
46
+ "@markdy/core": "0.7.29"
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.7.28"
53
+ "@markdy/stdlib-systems": "0.7.29"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsup",