@markdy/renderer-dom 1.0.29 → 1.0.30

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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BeatRange, ThemeTokens, Diagnostic, RenderPlan } from '@markdy/core';
1
+ import { BeatRange, ThemeTokens, PlayerControlsConfig, Diagnostic, RenderPlan } from '@markdy/core';
2
2
 
3
3
  /**
4
4
  * Markdy diagram runtime — renders a RenderPlan via WAAPI.
@@ -30,8 +30,10 @@ interface DiagramOptions {
30
30
  interactiveViewport?: boolean;
31
31
  /** Base URL for the Share control. Defaults to the Markdy playground. */
32
32
  shareUrl?: string;
33
- /** Host controls override. `true` enables the legacy default set; `false` suppresses script controls. */
34
- controls?: boolean;
33
+ /** Host controls override. `true` enables the legacy default set; `false` suppresses script controls; or pass a granular controls configuration object. */
34
+ controls?: boolean | (PlayerControlsConfig & {
35
+ playback?: boolean;
36
+ });
35
37
  onWarning?: (warning: Diagnostic) => void;
36
38
  onTimeUpdate?: (seconds: number, durationSeconds: number) => void;
37
39
  onPlayStateChange?: (playing: boolean) => void;
package/dist/index.js CHANGED
@@ -1,9 +1,3 @@
1
- import {
2
- exportDiagramAsVectorSvg,
3
- getDiagramSceneElement,
4
- prepareHtmlSceneForExport
5
- } from "./chunk-VIZHA7GI.js";
6
-
7
1
  // src/diagram.ts
8
2
  import {
9
3
  compressMarkdyToUrlHash,
@@ -1491,6 +1485,159 @@ function mountConstellationLayer(layer, nodes, theme, bounds) {
1491
1485
  }
1492
1486
  }
1493
1487
 
1488
+ // src/quadrant.ts
1489
+ function mountQuadrantLayer(layer, nodes, theme, bounds) {
1490
+ if (nodes.length === 0) return;
1491
+ const doc = layer.ownerDocument;
1492
+ Object.assign(layer.style, {
1493
+ position: "absolute",
1494
+ inset: "0",
1495
+ zIndex: "38",
1496
+ pointerEvents: "none"
1497
+ });
1498
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1499
+ svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
1500
+ Object.assign(svg.style, {
1501
+ position: "absolute",
1502
+ inset: "0",
1503
+ width: "100%",
1504
+ height: "100%",
1505
+ overflow: "visible"
1506
+ });
1507
+ layer.appendChild(svg);
1508
+ const SAFE = 40;
1509
+ const TITLE_BAND = 64;
1510
+ const contentW = bounds.width - SAFE * 2;
1511
+ const contentH = bounds.height - SAFE - TITLE_BAND - SAFE;
1512
+ const centerX = SAFE + contentW / 2;
1513
+ const centerY = TITLE_BAND + contentH / 2;
1514
+ const stroke = theme?.edges?.dependency ?? theme?.rule ?? theme?.border ?? "#64748b";
1515
+ const vAxis = doc.createElementNS("http://www.w3.org/2000/svg", "line");
1516
+ vAxis.classList.add("markdy-quadrant-vaxis");
1517
+ vAxis.setAttribute("x1", String(centerX));
1518
+ vAxis.setAttribute("y1", String(TITLE_BAND + 16));
1519
+ vAxis.setAttribute("x2", String(centerX));
1520
+ vAxis.setAttribute("y2", String(bounds.height - SAFE - 16));
1521
+ vAxis.setAttribute("stroke", stroke);
1522
+ vAxis.setAttribute("stroke-width", "1.5");
1523
+ vAxis.setAttribute("stroke-dasharray", "5 5");
1524
+ vAxis.setAttribute("opacity", "0.65");
1525
+ svg.appendChild(vAxis);
1526
+ const hAxis = doc.createElementNS("http://www.w3.org/2000/svg", "line");
1527
+ hAxis.classList.add("markdy-quadrant-haxis");
1528
+ hAxis.setAttribute("x1", String(SAFE + 16));
1529
+ hAxis.setAttribute("y1", String(centerY));
1530
+ hAxis.setAttribute("x2", String(bounds.width - SAFE - 16));
1531
+ hAxis.setAttribute("y2", String(centerY));
1532
+ hAxis.setAttribute("stroke", stroke);
1533
+ hAxis.setAttribute("stroke-width", "1.5");
1534
+ hAxis.setAttribute("stroke-dasharray", "5 5");
1535
+ hAxis.setAttribute("opacity", "0.65");
1536
+ svg.appendChild(hAxis);
1537
+ const hub = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
1538
+ hub.classList.add("markdy-quadrant-hub");
1539
+ hub.setAttribute("cx", String(centerX));
1540
+ hub.setAttribute("cy", String(centerY));
1541
+ hub.setAttribute("r", "4");
1542
+ hub.setAttribute("fill", stroke);
1543
+ hub.setAttribute("opacity", "0.75");
1544
+ svg.appendChild(hub);
1545
+ }
1546
+
1547
+ // src/swimlane.ts
1548
+ function mountSwimlaneLayer(layer, nodes, theme, bounds) {
1549
+ if (nodes.length === 0) return;
1550
+ const doc = layer.ownerDocument;
1551
+ Object.assign(layer.style, {
1552
+ position: "absolute",
1553
+ inset: "0",
1554
+ zIndex: "38",
1555
+ pointerEvents: "none"
1556
+ });
1557
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1558
+ svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
1559
+ Object.assign(svg.style, {
1560
+ position: "absolute",
1561
+ inset: "0",
1562
+ width: "100%",
1563
+ height: "100%",
1564
+ overflow: "visible"
1565
+ });
1566
+ layer.appendChild(svg);
1567
+ const SAFE = 40;
1568
+ const TITLE_BAND = 64;
1569
+ const contentH = bounds.height - SAFE - TITLE_BAND - SAFE;
1570
+ const nodeYs = [...new Set(nodes.map((n) => Math.round(n.y)))].sort((a, b) => a - b);
1571
+ const laneCount = Math.max(nodeYs.length, 1);
1572
+ const laneHeight = contentH / laneCount;
1573
+ const stroke = theme?.edges?.dependency ?? theme?.rule ?? theme?.border ?? "#64748b";
1574
+ for (let idx = 0; idx <= laneCount; idx++) {
1575
+ const y = TITLE_BAND + idx * laneHeight;
1576
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
1577
+ line.classList.add("markdy-swimlane-divider");
1578
+ line.setAttribute("x1", String(SAFE));
1579
+ line.setAttribute("y1", String(y));
1580
+ line.setAttribute("x2", String(bounds.width - SAFE));
1581
+ line.setAttribute("y2", String(y));
1582
+ line.setAttribute("stroke", stroke);
1583
+ line.setAttribute("stroke-width", "1.2");
1584
+ line.setAttribute("stroke-dasharray", idx === 0 || idx === laneCount ? "none" : "5 5");
1585
+ line.setAttribute("opacity", idx === 0 || idx === laneCount ? "0.6" : "0.4");
1586
+ svg.appendChild(line);
1587
+ }
1588
+ }
1589
+
1590
+ // src/gantt.ts
1591
+ function mountGanttLayer(layer, nodes, theme, bounds) {
1592
+ if (nodes.length === 0) return;
1593
+ const doc = layer.ownerDocument;
1594
+ Object.assign(layer.style, {
1595
+ position: "absolute",
1596
+ inset: "0",
1597
+ zIndex: "38",
1598
+ pointerEvents: "none"
1599
+ });
1600
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1601
+ svg.setAttribute("viewBox", `0 0 ${bounds.width} ${bounds.height}`);
1602
+ Object.assign(svg.style, {
1603
+ position: "absolute",
1604
+ inset: "0",
1605
+ width: "100%",
1606
+ height: "100%",
1607
+ overflow: "visible"
1608
+ });
1609
+ layer.appendChild(svg);
1610
+ const SAFE = 40;
1611
+ const TITLE_BAND = 64;
1612
+ const contentW = bounds.width - SAFE * 2;
1613
+ const stroke = theme?.edges?.dependency ?? theme?.rule ?? theme?.border ?? "#64748b";
1614
+ const totalPhases = Math.max(
1615
+ ...nodes.map((n) => {
1616
+ const p = typeof n.props?.phase === "number" ? n.props.phase : 0;
1617
+ const s = typeof n.props?.span === "number" ? n.props.span : 1;
1618
+ return p + s;
1619
+ }),
1620
+ 4
1621
+ );
1622
+ const minY = Math.min(...nodes.map((n) => n.y)) - 16;
1623
+ const maxY = Math.max(...nodes.map((n) => n.y + n.height)) + 16;
1624
+ const unitW = contentW / totalPhases;
1625
+ for (let phase = 0; phase <= totalPhases; phase++) {
1626
+ const x = SAFE + phase * unitW;
1627
+ const colLine = doc.createElementNS("http://www.w3.org/2000/svg", "line");
1628
+ colLine.classList.add("markdy-gantt-column");
1629
+ colLine.setAttribute("x1", String(x));
1630
+ colLine.setAttribute("y1", String(Math.max(TITLE_BAND, minY)));
1631
+ colLine.setAttribute("x2", String(x));
1632
+ colLine.setAttribute("y2", String(Math.min(bounds.height - SAFE, maxY)));
1633
+ colLine.setAttribute("stroke", stroke);
1634
+ colLine.setAttribute("stroke-width", "1");
1635
+ colLine.setAttribute("stroke-dasharray", "4 4");
1636
+ colLine.setAttribute("opacity", phase === 0 || phase === totalPhases ? "0.6" : "0.35");
1637
+ svg.appendChild(colLine);
1638
+ }
1639
+ }
1640
+
1494
1641
  // src/radar.ts
1495
1642
  function mountRadarLayer(layer, nodes, theme, bounds) {
1496
1643
  if (nodes.length < 3) return;
@@ -1511,14 +1658,18 @@ function mountRadarLayer(layer, nodes, theme, bounds) {
1511
1658
  overflow: "visible"
1512
1659
  });
1513
1660
  layer.appendChild(svg);
1514
- const centerX = bounds.width / 2;
1515
- const centerY = (bounds.height + 40) / 2;
1661
+ const SAFE = 40;
1662
+ const TITLE_BAND = 64;
1663
+ const contentW = bounds.width - SAFE * 2;
1664
+ const contentH = bounds.height - SAFE - TITLE_BAND - SAFE;
1665
+ const centerX = SAFE + contentW / 2;
1666
+ const centerY = TITLE_BAND + contentH / 2;
1516
1667
  const nodeCenters = nodes.map((n) => ({
1517
1668
  x: n.x + n.width / 2,
1518
1669
  y: n.y + n.height / 2
1519
1670
  }));
1520
- const strokeBorder = theme?.border ?? "#cbd5e1";
1521
- const strokeHairline = theme?.hairline ?? theme?.border ?? "#e2e8f0";
1671
+ const strokeAxis = theme?.edges?.dependency ?? theme?.rule ?? theme?.border ?? "#64748b";
1672
+ const strokeRing = theme?.rule ?? theme?.edges?.dependency ?? theme?.border ?? "#64748b";
1522
1673
  const accentColor = theme?.accent ?? "#38bdf8";
1523
1674
  for (const nc of nodeCenters) {
1524
1675
  const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
@@ -1526,10 +1677,10 @@ function mountRadarLayer(layer, nodes, theme, bounds) {
1526
1677
  line.setAttribute("y1", String(centerY));
1527
1678
  line.setAttribute("x2", String(nc.x));
1528
1679
  line.setAttribute("y2", String(nc.y));
1529
- line.setAttribute("stroke", strokeBorder);
1530
- line.setAttribute("stroke-width", "1");
1680
+ line.setAttribute("stroke", strokeAxis);
1681
+ line.setAttribute("stroke-width", "1.2");
1531
1682
  line.setAttribute("stroke-dasharray", "4 4");
1532
- line.setAttribute("opacity", "0.45");
1683
+ line.setAttribute("opacity", "0.6");
1533
1684
  svg.appendChild(line);
1534
1685
  }
1535
1686
  const fractions = [0.33, 0.66, 1];
@@ -1542,12 +1693,19 @@ function mountRadarLayer(layer, nodes, theme, bounds) {
1542
1693
  const poly = doc.createElementNS("http://www.w3.org/2000/svg", "polygon");
1543
1694
  poly.setAttribute("points", points);
1544
1695
  poly.setAttribute("fill", "none");
1545
- poly.setAttribute("stroke", strokeHairline);
1546
- poly.setAttribute("stroke-width", "1");
1547
- poly.setAttribute("stroke-dasharray", f === 1 ? "none" : "3 3");
1548
- poly.setAttribute("opacity", String(0.3 + f * 0.25));
1696
+ poly.setAttribute("stroke", strokeRing);
1697
+ poly.setAttribute("stroke-width", f === 1 ? "1.5" : "1.2");
1698
+ poly.setAttribute("stroke-dasharray", f === 1 ? "none" : "4 4");
1699
+ poly.setAttribute("opacity", String(0.4 + f * 0.3));
1549
1700
  svg.appendChild(poly);
1550
1701
  }
1702
+ const centerDot = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
1703
+ centerDot.setAttribute("cx", String(centerX));
1704
+ centerDot.setAttribute("cy", String(centerY));
1705
+ centerDot.setAttribute("r", "3");
1706
+ centerDot.setAttribute("fill", strokeAxis);
1707
+ centerDot.setAttribute("opacity", "0.8");
1708
+ svg.appendChild(centerDot);
1551
1709
  const areaPoints = nodeCenters.map((nc, idx) => {
1552
1710
  const f = 0.75 + idx % 3 * 0.12;
1553
1711
  const px = centerX + (nc.x - centerX) * f;
@@ -1561,7 +1719,7 @@ function mountRadarLayer(layer, nodes, theme, bounds) {
1561
1719
  area.setAttribute("stroke", accentColor);
1562
1720
  area.setAttribute("stroke-width", "1.5");
1563
1721
  area.setAttribute("stroke-dasharray", "4 4");
1564
- area.setAttribute("opacity", "0.6");
1722
+ area.setAttribute("opacity", "0.7");
1565
1723
  svg.appendChild(area);
1566
1724
  }
1567
1725
 
@@ -1585,11 +1743,14 @@ function mountTimelineLayer(layer, nodes, theme, bounds) {
1585
1743
  overflow: "visible"
1586
1744
  });
1587
1745
  layer.appendChild(svg);
1588
- const baselineY = (bounds.height + 40) / 2;
1746
+ const SAFE = 40;
1747
+ const TITLE_BAND = 64;
1748
+ const contentH = bounds.height - SAFE - TITLE_BAND - SAFE;
1749
+ const baselineY = TITLE_BAND + contentH / 2;
1589
1750
  const minX = Math.min(...nodes.map((n) => n.x)) - 20;
1590
1751
  const maxX = Math.max(...nodes.map((n) => n.x + n.width)) + 20;
1591
- const strokeAxis = theme?.hairline ?? theme?.border ?? "#cbd5e1";
1592
- const strokeBorder = theme?.border ?? "#cbd5e1";
1752
+ const strokeAxis = theme?.edges?.dependency ?? theme?.rule ?? theme?.border ?? "#64748b";
1753
+ const strokeBorder = theme?.edges?.dependency ?? theme?.border ?? "#64748b";
1593
1754
  const accentColor = theme?.accent ?? "#38bdf8";
1594
1755
  const surfaceFill = theme?.surfaceRaised ?? theme?.surface ?? "#ffffff";
1595
1756
  const axis = doc.createElementNS("http://www.w3.org/2000/svg", "line");
@@ -1599,7 +1760,8 @@ function mountTimelineLayer(layer, nodes, theme, bounds) {
1599
1760
  axis.setAttribute("y2", String(baselineY));
1600
1761
  axis.setAttribute("stroke", strokeAxis);
1601
1762
  axis.setAttribute("stroke-width", "2");
1602
- axis.setAttribute("opacity", "0.6");
1763
+ axis.setAttribute("stroke-linecap", "round");
1764
+ axis.setAttribute("opacity", "0.75");
1603
1765
  svg.appendChild(axis);
1604
1766
  for (const node of nodes) {
1605
1767
  const nodeCenterX = node.x + node.width / 2;
@@ -1611,17 +1773,17 @@ function mountTimelineLayer(layer, nodes, theme, bounds) {
1611
1773
  stem.setAttribute("x2", String(nodeCenterX));
1612
1774
  stem.setAttribute("y2", String(targetY));
1613
1775
  stem.setAttribute("stroke", node.focal ? accentColor : strokeBorder);
1614
- stem.setAttribute("stroke-width", node.focal ? "1.5" : "1");
1615
- stem.setAttribute("stroke-dasharray", node.focal ? "none" : "3 3");
1616
- stem.setAttribute("opacity", node.focal ? "0.9" : "0.5");
1776
+ stem.setAttribute("stroke-width", node.focal ? "2" : "1.5");
1777
+ stem.setAttribute("stroke-dasharray", node.focal ? "none" : "4 4");
1778
+ stem.setAttribute("opacity", node.focal ? "0.95" : "0.75");
1617
1779
  svg.appendChild(stem);
1618
1780
  const pip = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
1619
1781
  pip.setAttribute("cx", String(nodeCenterX));
1620
1782
  pip.setAttribute("cy", String(baselineY));
1621
- pip.setAttribute("r", node.focal ? "5" : "3.5");
1783
+ pip.setAttribute("r", node.focal ? "5.5" : "4");
1622
1784
  pip.setAttribute("fill", node.focal ? accentColor : surfaceFill);
1623
1785
  pip.setAttribute("stroke", node.focal ? accentColor : strokeBorder);
1624
- pip.setAttribute("stroke-width", "1.5");
1786
+ pip.setAttribute("stroke-width", "2");
1625
1787
  svg.appendChild(pip);
1626
1788
  }
1627
1789
  }
@@ -1676,7 +1838,7 @@ function createGroupBoundaryEl(boundary, theme, doc = document) {
1676
1838
  el.style.top = `${boundary.y}px`;
1677
1839
  el.style.width = `${boundary.width}px`;
1678
1840
  el.style.height = `${boundary.height}px`;
1679
- el.style.setProperty("--md-group-border", theme.hairline ?? theme.border);
1841
+ el.style.setProperty("--md-group-border", theme.border ?? theme.rule ?? "#cbd5e1");
1680
1842
  const displayLabel = boundary.label || boundary.id;
1681
1843
  if (displayLabel) {
1682
1844
  const label = doc.createElement("div");
@@ -2439,6 +2601,9 @@ function createText(doc, x, y, text, theme) {
2439
2601
  label.textContent = text;
2440
2602
  return label;
2441
2603
  }
2604
+ function resolveLifelineStroke(theme) {
2605
+ return theme.edges?.dependency ?? theme.rule ?? theme.border ?? "#64748b";
2606
+ }
2442
2607
  function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds) {
2443
2608
  Object.assign(layer.style, {
2444
2609
  position: "absolute",
@@ -2470,19 +2635,28 @@ function mountSequenceLayer(layer, nodes, messages, activations, theme, bounds)
2470
2635
  const node = nodeById.get(id);
2471
2636
  return node ? node.x + node.width / 2 : 0;
2472
2637
  };
2638
+ const lifelineStroke = resolveLifelineStroke(theme);
2473
2639
  for (const node of nodes) {
2474
2640
  const x = centerX(node.id);
2475
2641
  const lifeline = doc.createElementNS("http://www.w3.org/2000/svg", "line");
2476
2642
  lifeline.classList.add("markdy-sequence-lifeline");
2477
2643
  lifeline.setAttribute("x1", String(x));
2478
2644
  lifeline.setAttribute("x2", String(x));
2479
- lifeline.setAttribute("y1", String(node.y + node.height + 8));
2480
- lifeline.setAttribute("y2", String(bounds.height - 32));
2481
- lifeline.setAttribute("stroke", theme.hairline ?? theme.border);
2482
- lifeline.setAttribute("stroke-width", "1.2");
2483
- lifeline.setAttribute("stroke-dasharray", "4 6");
2645
+ lifeline.setAttribute("y1", String(node.y + node.height));
2646
+ lifeline.setAttribute("y2", String(bounds.height - 36));
2647
+ lifeline.setAttribute("stroke", lifelineStroke);
2648
+ lifeline.setAttribute("stroke-width", "1.5");
2649
+ lifeline.setAttribute("stroke-dasharray", "5 5");
2484
2650
  lifeline.setAttribute("opacity", "0.85");
2485
2651
  svg.appendChild(lifeline);
2652
+ const cap = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
2653
+ cap.classList.add("markdy-sequence-lifeline-cap");
2654
+ cap.setAttribute("cx", String(x));
2655
+ cap.setAttribute("cy", String(bounds.height - 36));
2656
+ cap.setAttribute("r", "2.5");
2657
+ cap.setAttribute("fill", lifelineStroke);
2658
+ cap.setAttribute("opacity", "0.85");
2659
+ svg.appendChild(cap);
2486
2660
  }
2487
2661
  for (const activation of activations) {
2488
2662
  const x = centerX(activation.participant);
@@ -2589,8 +2763,12 @@ function updateSequenceLayerTheme(layer, theme) {
2589
2763
  }
2590
2764
  });
2591
2765
  }
2766
+ const lifelineStroke = resolveLifelineStroke(theme);
2592
2767
  Array.from(svg.querySelectorAll(".markdy-sequence-lifeline")).forEach((line) => {
2593
- line.setAttribute("stroke", theme.hairline ?? theme.border);
2768
+ line.setAttribute("stroke", lifelineStroke);
2769
+ });
2770
+ Array.from(svg.querySelectorAll(".markdy-sequence-lifeline-cap")).forEach((cap) => {
2771
+ cap.setAttribute("fill", lifelineStroke);
2594
2772
  });
2595
2773
  Array.from(svg.querySelectorAll(".markdy-sequence-activation")).forEach((bar) => {
2596
2774
  bar.setAttribute("fill", theme.accent);
@@ -2636,7 +2814,7 @@ function mountTreeBuses(layer, buses, theme) {
2636
2814
  overflow: "visible"
2637
2815
  });
2638
2816
  layer.appendChild(svg);
2639
- const stroke = theme.rule ?? theme.hairline ?? theme.border;
2817
+ const stroke = theme.edges?.dependency ?? theme.rule ?? theme.border ?? "#64748b";
2640
2818
  for (const bus of buses) {
2641
2819
  const group = doc.createElementNS("http://www.w3.org/2000/svg", "g");
2642
2820
  group.setAttribute("data-tree-bus", bus.id);
@@ -2816,6 +2994,14 @@ function ensureSceneStyles(doc) {
2816
2994
  box-sizing: border-box;
2817
2995
  width: 100%;
2818
2996
  }
2997
+ .markdy-viewport {
2998
+ position: relative;
2999
+ width: 100%;
3000
+ flex: 1 1 auto;
3001
+ min-height: 0;
3002
+ overflow: hidden;
3003
+ box-sizing: border-box;
3004
+ }
2819
3005
  .markdy-footer {
2820
3006
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
2821
3007
  position: relative;
@@ -2825,13 +3011,15 @@ function ensureSceneStyles(doc) {
2825
3011
  align-items: center;
2826
3012
  justify-content: space-between;
2827
3013
  gap: 8px;
2828
- padding: 8px 12px 4px;
3014
+ padding: 8px 12px 6px;
2829
3015
  color: var(--md-text, #f8fafc);
2830
3016
  background: transparent;
2831
3017
  backdrop-filter: none;
2832
3018
  -webkit-backdrop-filter: none;
2833
3019
  border-top: none;
2834
3020
  z-index: 100;
3021
+ pointer-events: auto;
3022
+ flex-shrink: 0;
2835
3023
  }
2836
3024
  .markdy-controls {
2837
3025
  display: flex;
@@ -2843,9 +3031,15 @@ function ensureSceneStyles(doc) {
2843
3031
  gap: 8px;
2844
3032
  max-width: 100%;
2845
3033
  overflow-x: auto;
3034
+ overflow-y: visible;
2846
3035
  scrollbar-width: none;
2847
- padding: 4px 2px;
2848
- margin: -4px -2px;
3036
+ -ms-overflow-style: none;
3037
+ -webkit-overflow-scrolling: touch;
3038
+ padding: 4px 4px 6px;
3039
+ margin: 0;
3040
+ position: relative;
3041
+ z-index: 10;
3042
+ pointer-events: auto;
2849
3043
  }
2850
3044
  .markdy-controls::-webkit-scrollbar {
2851
3045
  display: none;
@@ -2855,6 +3049,9 @@ function ensureSceneStyles(doc) {
2855
3049
  align-items: center;
2856
3050
  gap: 4px;
2857
3051
  flex-shrink: 0;
3052
+ position: relative;
3053
+ z-index: 5;
3054
+ pointer-events: auto;
2858
3055
  }
2859
3056
  .markdy-controls-playback {
2860
3057
  display: inline-flex;
@@ -2875,6 +3072,9 @@ function ensureSceneStyles(doc) {
2875
3072
  align-items: center;
2876
3073
  gap: 4px;
2877
3074
  flex-shrink: 0;
3075
+ position: relative;
3076
+ z-index: 5;
3077
+ pointer-events: auto;
2878
3078
  }
2879
3079
  .markdy-control-divider {
2880
3080
  width: 1px;
@@ -2905,19 +3105,27 @@ function ensureSceneStyles(doc) {
2905
3105
  white-space: nowrap;
2906
3106
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
2907
3107
  transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
3108
+ position: relative;
3109
+ pointer-events: auto;
3110
+ isolation: isolate;
2908
3111
  }
2909
3112
  .markdy-controls button:hover:not([aria-pressed="true"]) {
2910
3113
  position: relative;
2911
- z-index: 2;
3114
+ z-index: 20;
2912
3115
  background: var(--md-control-hover-bg, rgba(255, 255, 255, 0.1));
2913
3116
  color: var(--md-control-hover-text, #f8fafc);
2914
3117
  border-color: var(--md-control-hover-border, rgba(148, 163, 184, 0.35));
2915
3118
  transform: translateY(-1px);
2916
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.12);
3119
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.14);
3120
+ }
3121
+ .markdy-controls-tools button:hover:not([aria-pressed="true"]) {
3122
+ z-index: 25;
3123
+ border-color: var(--md-control-hover-border, rgba(148, 163, 184, 0.45));
3124
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.16);
2917
3125
  }
2918
3126
  .markdy-controls button[aria-pressed="true"] {
2919
3127
  position: relative;
2920
- z-index: 2;
3128
+ z-index: 20;
2921
3129
  background: var(--accent, #10b981) !important;
2922
3130
  color: #ffffff !important;
2923
3131
  border-color: var(--accent, #10b981) !important;
@@ -2925,12 +3133,12 @@ function ensureSceneStyles(doc) {
2925
3133
  }
2926
3134
  .markdy-controls button:focus-visible {
2927
3135
  position: relative;
2928
- z-index: 3;
3136
+ z-index: 30;
2929
3137
  outline: 2px solid var(--accent, #10b981);
2930
- outline-offset: 1px;
3138
+ outline-offset: 2px;
2931
3139
  }
2932
3140
  .markdy-controls button:active {
2933
- transform: scale(0.95);
3141
+ transform: translateY(0) scale(0.96);
2934
3142
  }
2935
3143
  .markdy-icon {
2936
3144
  display: inline-block;
@@ -3264,6 +3472,7 @@ function ensureSceneStyles(doc) {
3264
3472
  background: var(--md-control-hover-bg, rgba(51, 65, 85, 0.95));
3265
3473
  color: var(--md-control-hover-text, #f8fafc);
3266
3474
  border-color: var(--md-control-hover-border, #94a3b8);
3475
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.25);
3267
3476
  }
3268
3477
  [data-markdy-theme="midnight"] .markdy-controls button[aria-pressed="true"],
3269
3478
  [data-markdy-theme="blueprint"] .markdy-controls button[aria-pressed="true"],
@@ -3336,6 +3545,24 @@ function ensureSceneStyles(doc) {
3336
3545
  margin: 0 auto !important;
3337
3546
  padding: 8px 12px 4px !important;
3338
3547
  }
3548
+ @media (hover: none) and (pointer: coarse) {
3549
+ .markdy-controls {
3550
+ gap: 6px;
3551
+ padding: 6px 4px 8px;
3552
+ }
3553
+ .markdy-controls button {
3554
+ height: 32px;
3555
+ min-width: 32px;
3556
+ padding: 0 8px;
3557
+ font-size: 12px;
3558
+ }
3559
+ .markdy-speed-group .markdy-control-rate {
3560
+ height: 26px;
3561
+ min-width: 28px;
3562
+ font-size: 11px;
3563
+ padding: 0 6px;
3564
+ }
3565
+ }
3339
3566
  @media (prefers-reduced-motion: reduce) {
3340
3567
  .markdy-node,
3341
3568
  .markdy-beat-caption,
@@ -3411,6 +3638,100 @@ function applyThemeToScene(scene, theme) {
3411
3638
  scene.style.color = theme.text;
3412
3639
  }
3413
3640
 
3641
+ // src/export/svg-exporter.ts
3642
+ function copyRenderedStyles(source, clone) {
3643
+ if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
3644
+ const sourceElements = [source, ...Array.from(source.querySelectorAll("*"))];
3645
+ const cloneElements = [clone, ...Array.from(clone.querySelectorAll("*"))];
3646
+ for (let index = 0; index < Math.min(sourceElements.length, cloneElements.length); index++) {
3647
+ const computed = window.getComputedStyle(sourceElements[index]);
3648
+ const target = cloneElements[index].style;
3649
+ for (let propertyIndex = 0; propertyIndex < computed.length; propertyIndex++) {
3650
+ const property = computed.item(propertyIndex);
3651
+ target.setProperty(property, computed.getPropertyValue(property), computed.getPropertyPriority(property));
3652
+ }
3653
+ }
3654
+ }
3655
+ function normalizeExportViewport(scene) {
3656
+ scene.querySelectorAll(".markdy-viewport-transform").forEach((viewportTransform) => {
3657
+ viewportTransform.style.transform = "translate(0px, 0px) scale(1)";
3658
+ viewportTransform.style.transformOrigin = "0 0";
3659
+ viewportTransform.style.willChange = "auto";
3660
+ });
3661
+ }
3662
+ function getDiagramSceneElement(containerEl) {
3663
+ const sceneEl = containerEl.classList?.contains("markdy-scene-root") ? containerEl : containerEl.querySelector(".markdy-scene-root") || containerEl.querySelector("svg") || (containerEl.tagName?.toLowerCase() === "svg" ? containerEl : null);
3664
+ if (!sceneEl) throw new Error("No Markdy scene element found in container");
3665
+ return sceneEl;
3666
+ }
3667
+ function prepareHtmlSceneForExport(sceneEl, options = {}) {
3668
+ const clonedScene = sceneEl.cloneNode(true);
3669
+ copyRenderedStyles(sceneEl, clonedScene);
3670
+ normalizeExportViewport(clonedScene);
3671
+ const widthStr = clonedScene.style.width || String(sceneEl.clientWidth || 800);
3672
+ const heightStr = clonedScene.style.height || String(sceneEl.clientHeight || 400);
3673
+ let width = parseFloat(widthStr);
3674
+ let height = parseFloat(heightStr);
3675
+ if (isNaN(width)) width = 800;
3676
+ if (isNaN(height)) height = 400;
3677
+ const scale = options.scale || 1;
3678
+ const scaledWidth = width * scale;
3679
+ const scaledHeight = height * scale;
3680
+ clonedScene.style.transform = `scale(${scale})`;
3681
+ clonedScene.style.transformOrigin = "0 0";
3682
+ clonedScene.style.position = "relative";
3683
+ clonedScene.style.left = "0px";
3684
+ clonedScene.style.top = "0px";
3685
+ clonedScene.style.margin = "0";
3686
+ clonedScene.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3687
+ if (options.transparentBackground) {
3688
+ clonedScene.style.background = "transparent";
3689
+ }
3690
+ return { sceneEl, clonedScene, width, height, scaledWidth, scaledHeight };
3691
+ }
3692
+ function exportDiagramAsVectorSvg(containerEl, options = {}) {
3693
+ const sceneEl = getDiagramSceneElement(containerEl);
3694
+ if (sceneEl.tagName?.toLowerCase() === "svg") {
3695
+ const clonedSvg = sceneEl.cloneNode(true);
3696
+ if (!clonedSvg.getAttribute("xmlns")) {
3697
+ clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
3698
+ }
3699
+ const serializer2 = new XMLSerializer();
3700
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3701
+ ${serializer2.serializeToString(clonedSvg)}`;
3702
+ }
3703
+ const { clonedScene, scaledWidth, scaledHeight } = prepareHtmlSceneForExport(sceneEl, options);
3704
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3705
+ svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
3706
+ svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
3707
+ svg.setAttribute("width", String(scaledWidth));
3708
+ svg.setAttribute("height", String(scaledHeight));
3709
+ svg.setAttribute("viewBox", `0 0 ${scaledWidth} ${scaledHeight}`);
3710
+ if (options.includeThemeStyles !== false) {
3711
+ const styleEl = document.createElementNS("http://www.w3.org/2000/svg", "style");
3712
+ let combinedStyles = `
3713
+ foreignObject { width: 100%; height: 100%; }
3714
+ .markdy-node { transition: opacity 0.3s ease; }
3715
+ `;
3716
+ if (typeof document !== "undefined") {
3717
+ const styles = document.querySelectorAll("style[id^='markdy-']");
3718
+ for (let i = 0; i < styles.length; i++) {
3719
+ combinedStyles += styles[i].textContent + "\n";
3720
+ }
3721
+ }
3722
+ styleEl.textContent = combinedStyles;
3723
+ svg.appendChild(styleEl);
3724
+ }
3725
+ const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
3726
+ foreignObject.setAttribute("width", "100%");
3727
+ foreignObject.setAttribute("height", "100%");
3728
+ foreignObject.appendChild(clonedScene);
3729
+ svg.appendChild(foreignObject);
3730
+ const serializer = new XMLSerializer();
3731
+ return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3732
+ ` + serializer.serializeToString(svg);
3733
+ }
3734
+
3414
3735
  // src/diagram.ts
3415
3736
  var NORMAL_PLAYBACK_RATE = 4 / 5;
3416
3737
  var MIN_VIEWPORT_ZOOM = 0.5;
@@ -3531,6 +3852,7 @@ function createDiagram(opts) {
3531
3852
  const totalDurationMs = plan.duration * 1e3;
3532
3853
  const durationSeconds = plan.duration;
3533
3854
  container.classList.add("markdy-diagram-root");
3855
+ applyThemeVariables(container, plan.theme);
3534
3856
  Object.assign(container.style, {
3535
3857
  position: "relative",
3536
3858
  display: "flex",
@@ -3545,12 +3867,13 @@ function createDiagram(opts) {
3545
3867
  Object.assign(viewport.style, {
3546
3868
  position: "relative",
3547
3869
  width: "100%",
3548
- height: "100%",
3549
3870
  maxWidth: "100%",
3550
3871
  maxHeight: "100%",
3551
3872
  aspectRatio: `${plan.meta.width} / ${plan.meta.height}`,
3552
3873
  overflow: "hidden",
3553
- boxSizing: "border-box"
3874
+ boxSizing: "border-box",
3875
+ flex: "1 1 auto",
3876
+ minHeight: "0"
3554
3877
  });
3555
3878
  container.appendChild(viewport);
3556
3879
  let progressEl = null;
@@ -3601,9 +3924,26 @@ function createDiagram(opts) {
3601
3924
  gap: "6px",
3602
3925
  width: "100%",
3603
3926
  boxSizing: "border-box",
3604
- padding: "6px 8px 2px",
3605
- background: "transparent"
3927
+ padding: "6px 8px 4px",
3928
+ background: "transparent",
3929
+ pointerEvents: "auto",
3930
+ flexShrink: "0"
3606
3931
  });
3932
+ for (const eventName of [
3933
+ "click",
3934
+ "dblclick",
3935
+ "pointerdown",
3936
+ "pointermove",
3937
+ "pointerup",
3938
+ "pointercancel",
3939
+ "wheel",
3940
+ "mousedown",
3941
+ "mouseup",
3942
+ "touchstart",
3943
+ "touchend"
3944
+ ]) {
3945
+ footer.addEventListener(eventName, (event) => event.stopPropagation());
3946
+ }
3607
3947
  container.appendChild(footer);
3608
3948
  return footer;
3609
3949
  }
@@ -3693,6 +4033,27 @@ function createDiagram(opts) {
3693
4033
  plan.theme,
3694
4034
  { width: plan.meta.width, height: plan.meta.height }
3695
4035
  );
4036
+ } else if (plan.diagramType === "quadrant") {
4037
+ mountQuadrantLayer(
4038
+ constellationLayer,
4039
+ plan.nodes,
4040
+ plan.theme,
4041
+ { width: plan.meta.width, height: plan.meta.height }
4042
+ );
4043
+ } else if (plan.diagramType === "swimlane") {
4044
+ mountSwimlaneLayer(
4045
+ constellationLayer,
4046
+ plan.nodes,
4047
+ plan.theme,
4048
+ { width: plan.meta.width, height: plan.meta.height }
4049
+ );
4050
+ } else if (plan.diagramType === "gantt") {
4051
+ mountGanttLayer(
4052
+ constellationLayer,
4053
+ plan.nodes,
4054
+ plan.theme,
4055
+ { width: plan.meta.width, height: plan.meta.height }
4056
+ );
3696
4057
  }
3697
4058
  const groupLayer = document.createElement("div");
3698
4059
  groupLayer.className = "markdy-group-layer";
@@ -4024,6 +4385,7 @@ function createDiagram(opts) {
4024
4385
  sceneMs = totalDurationMs > 0 ? Math.min(Math.max(seconds * 1e3, 0), totalDurationMs) : Math.max(seconds * 1e3, 0);
4025
4386
  applyCurrentTime();
4026
4387
  if (totalDurationMs > 0) updateProgressBar(sceneMs / totalDurationMs);
4388
+ syncControls();
4027
4389
  },
4028
4390
  setPlaybackRate(rate) {
4029
4391
  if (!Number.isFinite(rate) || rate <= 0) return;
@@ -4064,6 +4426,7 @@ function createDiagram(opts) {
4064
4426
  setTheme(theme) {
4065
4427
  const newTheme = typeof theme === "string" ? resolveTheme(theme) : theme;
4066
4428
  plan.theme = newTheme;
4429
+ applyThemeVariables(container, newTheme);
4067
4430
  applyThemeToScene(scene, newTheme);
4068
4431
  if (footer) {
4069
4432
  applyThemeVariables(footer, newTheme);
@@ -4098,7 +4461,7 @@ function createDiagram(opts) {
4098
4461
  treeLayer.replaceChildren();
4099
4462
  mountTreeBuses(treeLayer, plan.treeBuses, newTheme);
4100
4463
  }
4101
- if (plan.diagramType === "constellation" || plan.diagramType === "radar" || plan.diagramType === "timeline") {
4464
+ if (plan.diagramType === "constellation" || plan.diagramType === "radar" || plan.diagramType === "timeline" || plan.diagramType === "quadrant" || plan.diagramType === "swimlane" || plan.diagramType === "gantt") {
4102
4465
  constellationLayer.replaceChildren();
4103
4466
  if (plan.diagramType === "constellation") {
4104
4467
  mountConstellationLayer(
@@ -4121,6 +4484,27 @@ function createDiagram(opts) {
4121
4484
  newTheme,
4122
4485
  { width: plan.meta.width, height: plan.meta.height }
4123
4486
  );
4487
+ } else if (plan.diagramType === "quadrant") {
4488
+ mountQuadrantLayer(
4489
+ constellationLayer,
4490
+ plan.nodes,
4491
+ newTheme,
4492
+ { width: plan.meta.width, height: plan.meta.height }
4493
+ );
4494
+ } else if (plan.diagramType === "swimlane") {
4495
+ mountSwimlaneLayer(
4496
+ constellationLayer,
4497
+ plan.nodes,
4498
+ newTheme,
4499
+ { width: plan.meta.width, height: plan.meta.height }
4500
+ );
4501
+ } else if (plan.diagramType === "gantt") {
4502
+ mountGanttLayer(
4503
+ constellationLayer,
4504
+ plan.nodes,
4505
+ newTheme,
4506
+ { width: plan.meta.width, height: plan.meta.height }
4507
+ );
4124
4508
  }
4125
4509
  }
4126
4510
  if (plan.diagramType === "sequence") {
@@ -4233,12 +4617,14 @@ function createDiagram(opts) {
4233
4617
  });
4234
4618
  }
4235
4619
  function flashControlLabel(button, message) {
4236
- const originalHtml = button.innerHTML;
4620
+ const originalHtml = button.dataset.originalHtml ?? button.innerHTML;
4621
+ button.dataset.originalHtml = originalHtml;
4237
4622
  const isSuccess = !message.toLowerCase().includes("fail");
4238
4623
  button.innerHTML = isSuccess ? `${ICONS2.check}${message}` : message;
4239
4624
  button.classList.add("markdy-btn-flashed");
4240
4625
  setTimeout(() => {
4241
- button.innerHTML = originalHtml;
4626
+ button.innerHTML = button.dataset.originalHtml ?? originalHtml;
4627
+ delete button.dataset.originalHtml;
4242
4628
  button.classList.remove("markdy-btn-flashed");
4243
4629
  }, 1500);
4244
4630
  }
@@ -4258,14 +4644,13 @@ function createDiagram(opts) {
4258
4644
  if (!svgButton) return;
4259
4645
  const button = makeControlButton("SVG", "Export diagram as SVG", ICONS2.svg);
4260
4646
  button.className = "markdy-control-svg";
4261
- button.addEventListener("click", async () => {
4647
+ button.addEventListener("click", () => {
4262
4648
  const resumeAt = sceneMs;
4263
4649
  const wasPlaying = isPlaying;
4264
4650
  try {
4265
4651
  diagram.pause();
4266
4652
  diagram.seek(durationSeconds);
4267
- const { exportDiagramAsVectorSvg: exportDiagramAsVectorSvg2 } = await import("./svg-exporter-7FW6RIP6.js");
4268
- const svg = exportDiagramAsVectorSvg2(container);
4653
+ const svg = exportDiagramAsVectorSvg(container);
4269
4654
  const name = (plan.title || "markdy-diagram").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4270
4655
  downloadFile(`${name || "markdy-diagram"}.svg`, svg, "image/svg+xml");
4271
4656
  flashControlLabel(button, "Saved");
@@ -4355,8 +4740,11 @@ function createDiagram(opts) {
4355
4740
  const button = makeControlButton("Code", "View MarkdyScript source");
4356
4741
  button.className = "markdy-control-code";
4357
4742
  button.setAttribute("aria-haspopup", "dialog");
4743
+ button.setAttribute("aria-expanded", "false");
4358
4744
  button.addEventListener("click", () => {
4359
4745
  closeCodePanel?.();
4746
+ const previousActiveElement = document.activeElement;
4747
+ button.setAttribute("aria-expanded", "true");
4360
4748
  const overlay = document.createElement("div");
4361
4749
  overlay.className = "markdy-code-panel-overlay";
4362
4750
  overlay.setAttribute("role", "dialog");
@@ -4414,19 +4802,36 @@ function createDiagram(opts) {
4414
4802
  overlay.appendChild(panel);
4415
4803
  const mountHost = document.fullscreenElement ?? document.body;
4416
4804
  mountHost.appendChild(overlay);
4805
+ const prevOverflow = typeof document !== "undefined" ? document.body.style.overflow : "";
4806
+ if (typeof document !== "undefined" && !document.fullscreenElement) {
4807
+ document.body.style.overflow = "hidden";
4808
+ }
4417
4809
  closeBtn.focus();
4418
4810
  let closing = false;
4419
4811
  function removePanel() {
4420
4812
  document.removeEventListener("keydown", handleEscape);
4813
+ if (typeof document !== "undefined" && !document.fullscreenElement) {
4814
+ document.body.style.overflow = prevOverflow;
4815
+ }
4816
+ button.setAttribute("aria-expanded", "false");
4421
4817
  overlay.remove();
4818
+ previousActiveElement?.focus?.();
4422
4819
  if (closeCodePanel === removePanel) closeCodePanel = null;
4423
4820
  }
4424
4821
  function closePanel() {
4425
4822
  if (closing) return;
4426
4823
  closing = true;
4824
+ button.setAttribute("aria-expanded", "false");
4427
4825
  overlay.dataset.closing = "1";
4428
4826
  document.removeEventListener("keydown", handleEscape);
4429
- overlay.addEventListener("animationend", removePanel, { once: true });
4827
+ let cleaned = false;
4828
+ const doClean = () => {
4829
+ if (cleaned) return;
4830
+ cleaned = true;
4831
+ removePanel();
4832
+ };
4833
+ overlay.addEventListener("animationend", doClean, { once: true });
4834
+ setTimeout(doClean, 200);
4430
4835
  }
4431
4836
  closeBtn.addEventListener("click", closePanel);
4432
4837
  overlay.addEventListener("click", (e) => {
@@ -4584,21 +4989,33 @@ function createDiagram(opts) {
4584
4989
  toolbar.setAttribute("role", "toolbar");
4585
4990
  toolbar.setAttribute("aria-label", "Diagram controls");
4586
4991
  Object.assign(toolbar.style, {
4587
- position: "static",
4992
+ position: "relative",
4588
4993
  display: "flex",
4589
4994
  alignItems: "center",
4590
4995
  justifyContent: "flex-start",
4591
4996
  maxWidth: "100%",
4592
- padding: "0",
4593
4997
  border: "0",
4594
4998
  borderRadius: "0",
4595
4999
  background: "transparent",
4596
5000
  boxShadow: "none",
4597
5001
  backdropFilter: "none",
4598
5002
  transform: "none",
4599
- pointerEvents: "auto"
5003
+ pointerEvents: "auto",
5004
+ zIndex: "10"
4600
5005
  });
4601
- for (const eventName of ["click", "dblclick", "pointerdown", "pointermove", "pointerup", "wheel"]) {
5006
+ for (const eventName of [
5007
+ "click",
5008
+ "dblclick",
5009
+ "pointerdown",
5010
+ "pointermove",
5011
+ "pointerup",
5012
+ "pointercancel",
5013
+ "wheel",
5014
+ "mousedown",
5015
+ "mouseup",
5016
+ "touchstart",
5017
+ "touchend"
5018
+ ]) {
4602
5019
  toolbar.addEventListener(eventName, (event) => event.stopPropagation());
4603
5020
  }
4604
5021
  const playbackGroup = document.createElement("div");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/renderer-dom",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Browser renderer for diagram-native animated MarkdyScript architecture diagrams, built on the Web Animations API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "html2canvas": "^1.4.1",
49
- "@markdy/core": "1.0.29"
49
+ "@markdy/core": "1.0.30"
50
50
  },
51
51
  "devDependencies": {
52
52
  "jsdom": "^29.1.1",
53
53
  "tsup": "^8.5.1",
54
54
  "typescript": "^5.9.3",
55
55
  "vitest": "^4.1.7",
56
- "@markdy/stdlib-systems": "1.0.29"
56
+ "@markdy/stdlib-systems": "1.0.30"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsup",
@@ -1,99 +0,0 @@
1
- // src/export/svg-exporter.ts
2
- function copyRenderedStyles(source, clone) {
3
- if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return;
4
- const sourceElements = [source, ...Array.from(source.querySelectorAll("*"))];
5
- const cloneElements = [clone, ...Array.from(clone.querySelectorAll("*"))];
6
- for (let index = 0; index < Math.min(sourceElements.length, cloneElements.length); index++) {
7
- const computed = window.getComputedStyle(sourceElements[index]);
8
- const target = cloneElements[index].style;
9
- for (let propertyIndex = 0; propertyIndex < computed.length; propertyIndex++) {
10
- const property = computed.item(propertyIndex);
11
- target.setProperty(property, computed.getPropertyValue(property), computed.getPropertyPriority(property));
12
- }
13
- }
14
- }
15
- function normalizeExportViewport(scene) {
16
- scene.querySelectorAll(".markdy-viewport-transform").forEach((viewportTransform) => {
17
- viewportTransform.style.transform = "translate(0px, 0px) scale(1)";
18
- viewportTransform.style.transformOrigin = "0 0";
19
- viewportTransform.style.willChange = "auto";
20
- });
21
- }
22
- function getDiagramSceneElement(containerEl) {
23
- const sceneEl = containerEl.classList?.contains("markdy-scene-root") ? containerEl : containerEl.querySelector(".markdy-scene-root") || containerEl.querySelector("svg") || (containerEl.tagName?.toLowerCase() === "svg" ? containerEl : null);
24
- if (!sceneEl) throw new Error("No Markdy scene element found in container");
25
- return sceneEl;
26
- }
27
- function prepareHtmlSceneForExport(sceneEl, options = {}) {
28
- const clonedScene = sceneEl.cloneNode(true);
29
- copyRenderedStyles(sceneEl, clonedScene);
30
- normalizeExportViewport(clonedScene);
31
- const widthStr = clonedScene.style.width || String(sceneEl.clientWidth || 800);
32
- const heightStr = clonedScene.style.height || String(sceneEl.clientHeight || 400);
33
- let width = parseFloat(widthStr);
34
- let height = parseFloat(heightStr);
35
- if (isNaN(width)) width = 800;
36
- if (isNaN(height)) height = 400;
37
- const scale = options.scale || 1;
38
- const scaledWidth = width * scale;
39
- const scaledHeight = height * scale;
40
- clonedScene.style.transform = `scale(${scale})`;
41
- clonedScene.style.transformOrigin = "0 0";
42
- clonedScene.style.position = "relative";
43
- clonedScene.style.left = "0px";
44
- clonedScene.style.top = "0px";
45
- clonedScene.style.margin = "0";
46
- clonedScene.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
47
- if (options.transparentBackground) {
48
- clonedScene.style.background = "transparent";
49
- }
50
- return { sceneEl, clonedScene, width, height, scaledWidth, scaledHeight };
51
- }
52
- function exportDiagramAsVectorSvg(containerEl, options = {}) {
53
- const sceneEl = getDiagramSceneElement(containerEl);
54
- if (sceneEl.tagName?.toLowerCase() === "svg") {
55
- const clonedSvg = sceneEl.cloneNode(true);
56
- if (!clonedSvg.getAttribute("xmlns")) {
57
- clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
58
- }
59
- const serializer2 = new XMLSerializer();
60
- return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
61
- ${serializer2.serializeToString(clonedSvg)}`;
62
- }
63
- const { clonedScene, scaledWidth, scaledHeight } = prepareHtmlSceneForExport(sceneEl, options);
64
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
65
- svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
66
- svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
67
- svg.setAttribute("width", String(scaledWidth));
68
- svg.setAttribute("height", String(scaledHeight));
69
- svg.setAttribute("viewBox", `0 0 ${scaledWidth} ${scaledHeight}`);
70
- if (options.includeThemeStyles !== false) {
71
- const styleEl = document.createElementNS("http://www.w3.org/2000/svg", "style");
72
- let combinedStyles = `
73
- foreignObject { width: 100%; height: 100%; }
74
- .markdy-node { transition: opacity 0.3s ease; }
75
- `;
76
- if (typeof document !== "undefined") {
77
- const styles = document.querySelectorAll("style[id^='markdy-']");
78
- for (let i = 0; i < styles.length; i++) {
79
- combinedStyles += styles[i].textContent + "\n";
80
- }
81
- }
82
- styleEl.textContent = combinedStyles;
83
- svg.appendChild(styleEl);
84
- }
85
- const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
86
- foreignObject.setAttribute("width", "100%");
87
- foreignObject.setAttribute("height", "100%");
88
- foreignObject.appendChild(clonedScene);
89
- svg.appendChild(foreignObject);
90
- const serializer = new XMLSerializer();
91
- return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
92
- ` + serializer.serializeToString(svg);
93
- }
94
-
95
- export {
96
- getDiagramSceneElement,
97
- prepareHtmlSceneForExport,
98
- exportDiagramAsVectorSvg
99
- };
@@ -1,10 +0,0 @@
1
- import {
2
- exportDiagramAsVectorSvg,
3
- getDiagramSceneElement,
4
- prepareHtmlSceneForExport
5
- } from "./chunk-VIZHA7GI.js";
6
- export {
7
- exportDiagramAsVectorSvg,
8
- getDiagramSceneElement,
9
- prepareHtmlSceneForExport
10
- };