@markdy/renderer-dom 0.7.11 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.js +140 -16
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -645,6 +645,123 @@ function actorCenter(state, actorType) {
645
645
  y: state.y + height / 2
646
646
  };
647
647
  }
648
+ function actorRect(state, actorType) {
649
+ const { width, height } = actorSizeByType(actorType);
650
+ return {
651
+ x1: state.x,
652
+ y1: state.y,
653
+ x2: state.x + width,
654
+ y2: state.y + height
655
+ };
656
+ }
657
+ function inflateRect(rect, pad) {
658
+ return {
659
+ x1: rect.x1 - pad,
660
+ y1: rect.y1 - pad,
661
+ x2: rect.x2 + pad,
662
+ y2: rect.y2 + pad
663
+ };
664
+ }
665
+ function segmentIntersectsRect(a, b, rect) {
666
+ const minX = Math.min(a.x, b.x);
667
+ const maxX = Math.max(a.x, b.x);
668
+ const minY = Math.min(a.y, b.y);
669
+ const maxY = Math.max(a.y, b.y);
670
+ const horizontal = Math.abs(a.y - b.y) < 1e-3;
671
+ const vertical = Math.abs(a.x - b.x) < 1e-3;
672
+ if (horizontal) {
673
+ const yHit = a.y > rect.y1 && a.y < rect.y2;
674
+ const xOverlap = maxX > rect.x1 && minX < rect.x2;
675
+ return yHit && xOverlap;
676
+ }
677
+ if (vertical) {
678
+ const xHit = a.x > rect.x1 && a.x < rect.x2;
679
+ const yOverlap = maxY > rect.y1 && minY < rect.y2;
680
+ return xHit && yOverlap;
681
+ }
682
+ return false;
683
+ }
684
+ function countPathIntersections(points, obstacles) {
685
+ let hits = 0;
686
+ for (let i = 0; i < points.length - 1; i++) {
687
+ for (const obstacle of obstacles) {
688
+ if (segmentIntersectsRect(points[i], points[i + 1], obstacle)) hits++;
689
+ }
690
+ }
691
+ return hits;
692
+ }
693
+ function toPathD(points) {
694
+ return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round1(p.x)} ${round1(p.y)}`).join(" ");
695
+ }
696
+ function round1(n) {
697
+ return Math.round(n * 10) / 10;
698
+ }
699
+ function polylineLength(points) {
700
+ let total = 0;
701
+ for (let i = 0; i < points.length - 1; i++) {
702
+ total += Math.hypot(points[i + 1].x - points[i].x, points[i + 1].y - points[i].y);
703
+ }
704
+ return Math.max(1, total);
705
+ }
706
+ function pointAtDistance(points, dist) {
707
+ let remain = dist;
708
+ for (let i = 0; i < points.length - 1; i++) {
709
+ const a = points[i];
710
+ const b = points[i + 1];
711
+ const seg = Math.hypot(b.x - a.x, b.y - a.y);
712
+ if (remain <= seg || i === points.length - 2) {
713
+ const t = seg <= 0 ? 0 : remain / seg;
714
+ return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
715
+ }
716
+ remain -= seg;
717
+ }
718
+ return points[0];
719
+ }
720
+ function routeFlowPath(sourceName, targetName, sourceState, targetState, states, ast, lane) {
721
+ const sourceRect = actorRect(sourceState, ast.actors[sourceName]?.type ?? "box");
722
+ const targetRect = actorRect(targetState, ast.actors[targetName]?.type ?? "box");
723
+ const laneShift = lane * 18;
724
+ const sourceCenter = actorCenter(sourceState, ast.actors[sourceName]?.type ?? "box");
725
+ const targetCenter = actorCenter(targetState, ast.actors[targetName]?.type ?? "box");
726
+ const horizontalPrimary = Math.abs(targetCenter.x - sourceCenter.x) >= Math.abs(targetCenter.y - sourceCenter.y);
727
+ const source = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? sourceRect.x2 : sourceRect.x1, y: sourceCenter.y } : { x: sourceCenter.x, y: targetCenter.y >= sourceCenter.y ? sourceRect.y2 : sourceRect.y1 };
728
+ const target = horizontalPrimary ? { x: targetCenter.x >= sourceCenter.x ? targetRect.x1 : targetRect.x2, y: targetCenter.y } : { x: targetCenter.x, y: targetCenter.y >= sourceCenter.y ? targetRect.y1 : targetRect.y2 };
729
+ const obstacles = [];
730
+ for (const [name, state] of states.entries()) {
731
+ if (name === sourceName || name === targetName) continue;
732
+ const type = ast.actors[name]?.type ?? "box";
733
+ obstacles.push(inflateRect(actorRect(state, type), 8));
734
+ }
735
+ const candidates = [];
736
+ if (Math.abs(source.y - target.y) < 1e-3 || Math.abs(source.x - target.x) < 1e-3) {
737
+ candidates.push([source, target]);
738
+ }
739
+ const midX = round1((source.x + target.x) / 2 + laneShift);
740
+ const midY = round1((source.y + target.y) / 2 + laneShift);
741
+ candidates.push(
742
+ [source, { x: midX, y: source.y }, { x: midX, y: target.y }, target],
743
+ [source, { x: source.x, y: midY }, { x: target.x, y: midY }, target]
744
+ );
745
+ const minObstacleY = obstacles.length ? Math.min(...obstacles.map((o) => o.y1)) : Math.min(source.y, target.y);
746
+ const maxObstacleY = obstacles.length ? Math.max(...obstacles.map((o) => o.y2)) : Math.max(source.y, target.y);
747
+ const topLane = Math.max(16, minObstacleY - 24 - lane * 12);
748
+ const bottomLane = Math.min(ast.meta.height - 16, maxObstacleY + 24 + lane * 12);
749
+ candidates.push(
750
+ [source, { x: source.x, y: topLane }, { x: target.x, y: topLane }, target],
751
+ [source, { x: source.x, y: bottomLane }, { x: target.x, y: bottomLane }, target]
752
+ );
753
+ let best = candidates[0];
754
+ let bestHits = Number.POSITIVE_INFINITY;
755
+ for (const candidate of candidates) {
756
+ const hits = countPathIntersections(candidate, obstacles);
757
+ if (hits < bestHits) {
758
+ best = candidate;
759
+ bestHits = hits;
760
+ if (hits === 0) break;
761
+ }
762
+ }
763
+ return best;
764
+ }
648
765
  function ensureEdgeLayer(scene) {
649
766
  const existing = scene.querySelector("svg[data-markdy-edge-layer='1']");
650
767
  if (existing) return existing;
@@ -662,18 +779,22 @@ function ensureEdgeLayer(scene) {
662
779
  scene.appendChild(svg);
663
780
  return svg;
664
781
  }
665
- function renderFlowEdge(ev, sourceState, targetState, sourceType, targetType, scene, baseOpts, anims) {
782
+ function renderFlowEdge(ev, sourceName, targetName, sourceState, targetState, states, ast, lane, scene, baseOpts, anims) {
666
783
  const styleToken = String(ev.params.style ?? "");
667
784
  const isDashed = styleToken === "dashed" || styleToken === "fire_and_forget" || ev.action === "response";
668
785
  const stroke = FLOW_STROKE_BY_ACTION[ev.action] ?? "#38bdf8";
669
- const source = actorCenter(sourceState, sourceType);
670
- const target = actorCenter(targetState, targetType);
671
- const dx = target.x - source.x;
672
- const dy = target.y - source.y;
673
- const length = Math.max(1, Math.hypot(dx, dy));
674
- const mx = source.x + dx / 2;
675
- const my = source.y + dy / 2;
676
- const pathD = `M ${source.x} ${source.y} L ${target.x} ${target.y}`;
786
+ const points = routeFlowPath(
787
+ sourceName,
788
+ targetName,
789
+ sourceState,
790
+ targetState,
791
+ states,
792
+ ast,
793
+ lane
794
+ );
795
+ const length = polylineLength(points);
796
+ const midPoint = pointAtDistance(points, length * 0.5);
797
+ const pathD = toPathD(points);
677
798
  const svg = ensureEdgeLayer(scene);
678
799
  const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
679
800
  group.setAttribute("data-markdy-flow-edge", "1");
@@ -682,6 +803,7 @@ function renderFlowEdge(ev, sourceState, targetState, sourceType, targetType, sc
682
803
  path.setAttribute("fill", "none");
683
804
  path.setAttribute("stroke", stroke);
684
805
  path.setAttribute("stroke-width", "2.5");
806
+ path.setAttribute("data-markdy-flow-action", ev.action);
685
807
  path.style.strokeDasharray = isDashed ? "8 6" : `${length}`;
686
808
  path.style.strokeDashoffset = `${length}`;
687
809
  group.appendChild(path);
@@ -696,8 +818,8 @@ function renderFlowEdge(ev, sourceState, targetState, sourceType, targetType, sc
696
818
  if (labelRaw) {
697
819
  const label = labelRaw.length > 28 ? `${labelRaw.slice(0, 27)}\u2026` : labelRaw;
698
820
  const labelEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
699
- labelEl.setAttribute("x", `${mx}`);
700
- labelEl.setAttribute("y", `${my - 8}`);
821
+ labelEl.setAttribute("x", `${round1(midPoint.x)}`);
822
+ labelEl.setAttribute("y", `${round1(midPoint.y - 8)}`);
701
823
  labelEl.setAttribute("text-anchor", "middle");
702
824
  labelEl.setAttribute("font-size", "12");
703
825
  labelEl.setAttribute("fill", "#cbd5e1");
@@ -722,7 +844,7 @@ function renderFlowEdge(ev, sourceState, targetState, sourceType, targetType, sc
722
844
  })
723
845
  );
724
846
  }
725
- function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, actorEls, scene, assetOverrides, faceSwaps, anims, txFn) {
847
+ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, _actorEls, scene, assetOverrides, faceSwaps, anims, txFn) {
726
848
  switch (ev.action) {
727
849
  case "request":
728
850
  case "response":
@@ -731,14 +853,16 @@ function buildAction(ev, el, s, baseOpts, delayMs, durMs, ast, states, actorEls,
731
853
  if (!targetActorName) break;
732
854
  const targetState = states.get(targetActorName);
733
855
  if (!targetState) break;
734
- const sourceType = ast.actors[ev.actor]?.type ?? "box";
735
- const targetType = ast.actors[targetActorName]?.type ?? "box";
856
+ const lane = ev.line % 5 - 2;
736
857
  renderFlowEdge(
737
858
  ev,
859
+ ev.actor,
860
+ targetActorName,
738
861
  s,
739
862
  targetState,
740
- sourceType,
741
- targetType,
863
+ states,
864
+ ast,
865
+ lane,
742
866
  scene,
743
867
  baseOpts,
744
868
  anims
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "0.7.11",
3
+ "version": "0.7.12",
4
4
  "description": "Web Animations API renderer for MarkdyScript scenes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@markdy/core": "0.7.11"
46
+ "@markdy/core": "0.7.12"
47
47
  },
48
48
  "devDependencies": {
49
49
  "jsdom": "^29.1.1",