@glyphjs/components 0.3.0 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { accordionSchema, annotateSchema, architectureSchema, calloutSchema, cardSchema, chartSchema, codediffSchema, comparisonSchema, equationSchema, filetreeSchema, flowchartSchema, formSchema, graphSchema, infographicSchema, kanbanSchema, kpiSchema, matrixSchema, mindmapSchema, pollSchema, quizSchema, rankerSchema, ratingSchema, relationSchema, sequenceSchema, sliderSchema, stepsSchema, tableSchema, tabsSchema, timelineSchema } from '@glyphjs/schemas';
2
2
  import { RichText } from '@glyphjs/runtime';
3
- import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
5
5
  import * as d32 from 'd3';
6
6
  import { scaleTime, scaleOrdinal } from 'd3';
@@ -50,7 +50,7 @@ function Callout({ data }) {
50
50
  return /* @__PURE__ */ jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle11, children: [
51
51
  /* @__PURE__ */ jsx("span", { style: iconStyle, "aria-hidden": "true", children: CALLOUT_ICONS[type] }),
52
52
  /* @__PURE__ */ jsxs("div", { style: bodyStyle3, children: [
53
- title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: title }),
53
+ title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: /* @__PURE__ */ jsx(RichText, { content: title }) }),
54
54
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(RichText, { content }) })
55
55
  ] })
56
56
  ] });
@@ -62,6 +62,34 @@ var calloutDefinition = {
62
62
  schema: calloutSchema,
63
63
  render: Callout
64
64
  };
65
+
66
+ // src/utils/inlineToText.ts
67
+ function inlineToText(content) {
68
+ if (typeof content === "string") {
69
+ return content;
70
+ }
71
+ return content.map((node) => {
72
+ switch (node.type) {
73
+ case "text":
74
+ return node.value;
75
+ case "strong":
76
+ case "emphasis":
77
+ case "delete":
78
+ case "link":
79
+ return inlineToText(node.children);
80
+ case "inlineCode":
81
+ return node.value;
82
+ case "image":
83
+ return node.alt ?? "";
84
+ case "break":
85
+ return "\n";
86
+ default:
87
+ return "";
88
+ }
89
+ }).join("");
90
+ }
91
+
92
+ // src/chart/render.ts
65
93
  var DEFAULT_WIDTH = 600;
66
94
  var DEFAULT_HEIGHT = 400;
67
95
  var MARGIN = { top: 20, right: 30, bottom: 50, left: 60 };
@@ -109,7 +137,7 @@ function renderAxes(g, xScale, yScale, xAxisConfig, yAxisConfig, innerWidth, inn
109
137
  }
110
138
  xAxisG.selectAll("text, line, path").attr("fill", "var(--glyph-text, #1a2035)").attr("stroke", "var(--glyph-grid, #1a2035)");
111
139
  if (xAxisConfig?.label) {
112
- g.append("text").attr("class", "x-label").attr("x", innerWidth / 2).attr("y", innerHeight + MARGIN.bottom - 6).attr("text-anchor", "middle").attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", "12px").text(xAxisConfig.label);
140
+ g.append("text").attr("class", "x-label").attr("x", innerWidth / 2).attr("y", innerHeight + MARGIN.bottom - 6).attr("text-anchor", "middle").attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", "12px").text(inlineToText(xAxisConfig.label));
113
141
  }
114
142
  const yAxisG = g.append("g").attr("class", "y-axis");
115
143
  yAxisG.call(
@@ -117,7 +145,7 @@ function renderAxes(g, xScale, yScale, xAxisConfig, yAxisConfig, innerWidth, inn
117
145
  );
118
146
  yAxisG.selectAll("text, line, path").attr("fill", "var(--glyph-text, #1a2035)").attr("stroke", "var(--glyph-grid, #1a2035)");
119
147
  if (yAxisConfig?.label) {
120
- g.append("text").attr("class", "y-label").attr("transform", "rotate(-90)").attr("x", -innerHeight / 2).attr("y", -MARGIN.left + 14).attr("text-anchor", "middle").attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", "12px").text(yAxisConfig.label);
148
+ g.append("text").attr("class", "y-label").attr("transform", "rotate(-90)").attr("x", -innerHeight / 2).attr("y", -MARGIN.left + 14).attr("text-anchor", "middle").attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", "12px").text(inlineToText(yAxisConfig.label));
121
149
  }
122
150
  }
123
151
  function renderGridLines(g, yScale, innerWidth) {
@@ -173,7 +201,7 @@ function renderLegend(sel, series, marginLeft, marginTop, fontSize = "12px") {
173
201
  const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "var(--glyph-text, #1a2035)";
174
202
  const row = legendG.append("g").attr("transform", `translate(0,${String(i * 20)})`);
175
203
  row.append("rect").attr("width", 14).attr("height", 14).attr("fill", color3).attr("rx", 2);
176
- row.append("text").attr("x", 20).attr("y", 11).attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", fontSize).text(s.name);
204
+ row.append("text").attr("x", 20).attr("y", 11).attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", fontSize).text(inlineToText(s.name));
177
205
  });
178
206
  }
179
207
  function ChartAccessibleTable({
@@ -204,10 +232,10 @@ function ChartAccessibleTable({
204
232
  " chart data"
205
233
  ] }),
206
234
  series.map((s, si) => /* @__PURE__ */ jsxs("tbody", { children: [
207
- /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("th", { colSpan: 2, children: s.name }) }),
235
+ /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("th", { colSpan: 2, children: inlineToText(s.name) }) }),
208
236
  /* @__PURE__ */ jsxs("tr", { children: [
209
- /* @__PURE__ */ jsx("th", { children: xLabel ?? xKey }),
210
- /* @__PURE__ */ jsx("th", { children: yLabel ?? yKey })
237
+ /* @__PURE__ */ jsx("th", { children: xLabel ? inlineToText(xLabel) : xKey }),
238
+ /* @__PURE__ */ jsx("th", { children: yLabel ? inlineToText(yLabel) : yKey })
211
239
  ] }),
212
240
  s.data.map((d, di) => /* @__PURE__ */ jsxs("tr", { children: [
213
241
  /* @__PURE__ */ jsx("td", { children: String(d[xKey] ?? "") }),
@@ -258,6 +286,7 @@ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideT
258
286
  const { xScale, xScalePoint, yScale, innerHeight } = scales;
259
287
  series.forEach((s, i) => {
260
288
  const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "#333";
289
+ const seriesName = inlineToText(s.name);
261
290
  switch (type) {
262
291
  case "line":
263
292
  renderLineSeries(
@@ -269,7 +298,7 @@ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideT
269
298
  xKey,
270
299
  color3,
271
300
  i,
272
- s.name,
301
+ seriesName,
273
302
  showTooltip,
274
303
  hideTooltip
275
304
  );
@@ -285,7 +314,7 @@ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideT
285
314
  innerHeight,
286
315
  color3,
287
316
  i,
288
- s.name,
317
+ seriesName,
289
318
  showTooltip,
290
319
  hideTooltip
291
320
  );
@@ -302,7 +331,7 @@ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideT
302
331
  i,
303
332
  series.length,
304
333
  innerHeight,
305
- s.name,
334
+ seriesName,
306
335
  showTooltip,
307
336
  hideTooltip
308
337
  );
@@ -315,7 +344,7 @@ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideT
315
344
  xScalePoint,
316
345
  yScale,
317
346
  scales.innerWidth,
318
- s.name,
347
+ seriesName,
319
348
  showTooltip,
320
349
  hideTooltip
321
350
  );
@@ -695,7 +724,7 @@ function TableHead({
695
724
  whiteSpace: "nowrap"
696
725
  },
697
726
  children: [
698
- col.label,
727
+ /* @__PURE__ */ jsx(RichText, { content: col.label }),
699
728
  col.sortable ? sortIndicator(direction) : ""
700
729
  ]
701
730
  },
@@ -1021,7 +1050,7 @@ function Tabs({ data, block, onInteraction }) {
1021
1050
  outline: "revert",
1022
1051
  outlineOffset: "2px"
1023
1052
  },
1024
- children: tab.label
1053
+ children: /* @__PURE__ */ jsx(RichText, { content: tab.label })
1025
1054
  },
1026
1055
  tabId
1027
1056
  );
@@ -1047,7 +1076,7 @@ function Tabs({ data, block, onInteraction }) {
1047
1076
  color: "var(--glyph-heading, #0a0e1a)",
1048
1077
  lineHeight: 1.6
1049
1078
  },
1050
- children: tab.content
1079
+ children: /* @__PURE__ */ jsx(RichText, { content: tab.content })
1051
1080
  },
1052
1081
  panelId
1053
1082
  );
@@ -1298,6 +1327,92 @@ var timelineDefinition = {
1298
1327
  schema: timelineSchema,
1299
1328
  render: Timeline
1300
1329
  };
1330
+
1331
+ // src/utils/measureText.ts
1332
+ var measurementCache = /* @__PURE__ */ new WeakMap();
1333
+ function measurePlainText(text, style) {
1334
+ const canvas = document.createElement("canvas");
1335
+ const ctx = canvas.getContext("2d");
1336
+ if (!ctx) {
1337
+ const avgCharWidth = parseInt(style.fontSize) * 0.6;
1338
+ return {
1339
+ width: text.length * avgCharWidth,
1340
+ height: parseInt(style.fontSize) * 1.2
1341
+ };
1342
+ }
1343
+ const fontWeight = style.fontWeight ?? "normal";
1344
+ ctx.font = `${fontWeight} ${style.fontSize} ${style.fontFamily}`;
1345
+ const metrics = ctx.measureText(text);
1346
+ const width = metrics.width;
1347
+ const height = parseInt(style.fontSize) * 1.2;
1348
+ return { width, height };
1349
+ }
1350
+ function measureHtmlText(content, style) {
1351
+ const cached = measurementCache.get(content);
1352
+ if (cached) {
1353
+ return cached;
1354
+ }
1355
+ const container = document.createElement("div");
1356
+ container.style.position = "absolute";
1357
+ container.style.visibility = "hidden";
1358
+ container.style.left = "-9999px";
1359
+ container.style.top = "-9999px";
1360
+ container.style.fontSize = style.fontSize;
1361
+ container.style.fontFamily = style.fontFamily;
1362
+ container.style.fontWeight = style.fontWeight ?? "normal";
1363
+ container.style.whiteSpace = "pre-wrap";
1364
+ container.style.wordBreak = "break-word";
1365
+ if (style.maxWidth) {
1366
+ container.style.maxWidth = `${style.maxWidth}px`;
1367
+ }
1368
+ container.innerHTML = inlineNodesToHtml(content);
1369
+ document.body.appendChild(container);
1370
+ const rect = container.getBoundingClientRect();
1371
+ const dimensions = {
1372
+ width: Math.ceil(rect.width),
1373
+ height: Math.ceil(rect.height)
1374
+ };
1375
+ document.body.removeChild(container);
1376
+ measurementCache.set(content, dimensions);
1377
+ return dimensions;
1378
+ }
1379
+ function inlineNodesToHtml(nodes) {
1380
+ return nodes.map((node) => {
1381
+ switch (node.type) {
1382
+ case "text":
1383
+ return escapeHtml(node.value);
1384
+ case "strong":
1385
+ return `<strong>${inlineNodesToHtml(node.children)}</strong>`;
1386
+ case "emphasis":
1387
+ return `<em>${inlineNodesToHtml(node.children)}</em>`;
1388
+ case "delete":
1389
+ return `<del>${inlineNodesToHtml(node.children)}</del>`;
1390
+ case "inlineCode":
1391
+ return `<code>${escapeHtml(node.value)}</code>`;
1392
+ case "link":
1393
+ return `<a href="${escapeHtml(node.url)}">${inlineNodesToHtml(node.children)}</a>`;
1394
+ case "image":
1395
+ return `<img src="${escapeHtml(node.src)}" alt="${escapeHtml(node.alt ?? "")}" />`;
1396
+ case "break":
1397
+ return "<br />";
1398
+ default:
1399
+ return "";
1400
+ }
1401
+ }).join("");
1402
+ }
1403
+ function escapeHtml(str) {
1404
+ const div = document.createElement("div");
1405
+ div.textContent = str;
1406
+ return div.innerHTML;
1407
+ }
1408
+ function measureText(content, style) {
1409
+ if (typeof content === "string") {
1410
+ return measurePlainText(content, style);
1411
+ }
1412
+ return measureHtmlText(content, style);
1413
+ }
1414
+
1415
+ // src/graph/layout.ts
1301
1416
  var RANKDIR_MAP = {
1302
1417
  "top-down": "TB",
1303
1418
  "left-right": "LR",
@@ -1320,10 +1435,17 @@ function computeDagreLayout(nodes, edges, direction = "top-down") {
1320
1435
  });
1321
1436
  g.setDefaultEdgeLabel(() => ({}));
1322
1437
  for (const node of nodes) {
1438
+ const labelDimensions = measureText(node.label, {
1439
+ fontSize: "13px",
1440
+ fontFamily: "Inter, system-ui, sans-serif",
1441
+ maxWidth: 200
1442
+ });
1443
+ const nodeWidth = Math.max(120, Math.min(250, labelDimensions.width + 40));
1444
+ const nodeHeight = Math.max(40, labelDimensions.height + 20);
1323
1445
  g.setNode(node.id, {
1324
1446
  label: node.label,
1325
- width: DEFAULT_NODE_WIDTH,
1326
- height: DEFAULT_NODE_HEIGHT
1447
+ width: nodeWidth,
1448
+ height: nodeHeight
1327
1449
  });
1328
1450
  }
1329
1451
  for (const edge of edges) {
@@ -1430,6 +1552,326 @@ function computeForceLayout(nodes, edges) {
1430
1552
  height: maxY + LAYOUT_PADDING
1431
1553
  };
1432
1554
  }
1555
+ function useZoomInteraction({
1556
+ svgRef,
1557
+ rootRef,
1558
+ interactionMode
1559
+ }) {
1560
+ const [isActive, setIsActive] = useState(interactionMode === "always");
1561
+ const [hasAttemptedScroll, setHasAttemptedScroll] = useState(false);
1562
+ const containerRef = useRef(null);
1563
+ useEffect(() => {
1564
+ setIsActive(interactionMode === "always");
1565
+ setHasAttemptedScroll(false);
1566
+ }, [interactionMode]);
1567
+ const filterFunction = useCallback(
1568
+ (event) => {
1569
+ if (interactionMode === "always") {
1570
+ return true;
1571
+ }
1572
+ if (interactionMode === "modifier-key") {
1573
+ if (event.type === "mousedown") return true;
1574
+ if (event.type === "wheel") {
1575
+ const wheelEvent = event;
1576
+ const hasModifier = wheelEvent.altKey;
1577
+ if (!hasModifier && !hasAttemptedScroll) {
1578
+ setHasAttemptedScroll(true);
1579
+ setTimeout(() => setHasAttemptedScroll(false), 3e3);
1580
+ }
1581
+ return hasModifier;
1582
+ }
1583
+ return true;
1584
+ }
1585
+ if (interactionMode === "click-to-activate") {
1586
+ return isActive;
1587
+ }
1588
+ return true;
1589
+ },
1590
+ [interactionMode, isActive, hasAttemptedScroll]
1591
+ );
1592
+ useEffect(() => {
1593
+ if (interactionMode !== "modifier-key" || !svgRef.current) return;
1594
+ const svg = svgRef.current;
1595
+ const container = svg.parentElement;
1596
+ if (!container) return;
1597
+ const handleWheel = (event) => {
1598
+ const target = event.target;
1599
+ if (event.altKey && svg.contains(target)) {
1600
+ event.preventDefault();
1601
+ event.stopPropagation();
1602
+ }
1603
+ };
1604
+ container.addEventListener("wheel", handleWheel, { passive: false, capture: true });
1605
+ return () => {
1606
+ container.removeEventListener("wheel", handleWheel, { capture: true });
1607
+ };
1608
+ }, [interactionMode, svgRef]);
1609
+ const zoomBehavior = useMemo(() => {
1610
+ const zoom3 = d32.zoom().scaleExtent([0.1, 4]);
1611
+ if (typeof zoom3.filter === "function") {
1612
+ zoom3.filter(filterFunction);
1613
+ }
1614
+ zoom3.on("zoom", (event) => {
1615
+ if (rootRef.current) {
1616
+ d32.select(rootRef.current).attr("transform", event.transform.toString());
1617
+ }
1618
+ });
1619
+ return zoom3;
1620
+ }, [filterFunction, rootRef]);
1621
+ const handleActivate = useCallback(() => {
1622
+ if (interactionMode === "click-to-activate") {
1623
+ setIsActive(true);
1624
+ }
1625
+ }, [interactionMode]);
1626
+ useEffect(() => {
1627
+ if (interactionMode !== "click-to-activate" || !isActive) return;
1628
+ const handleKeyDown = (e) => {
1629
+ if (e.key === "Escape") {
1630
+ setIsActive(false);
1631
+ }
1632
+ };
1633
+ document.addEventListener("keydown", handleKeyDown);
1634
+ return () => document.removeEventListener("keydown", handleKeyDown);
1635
+ }, [interactionMode, isActive]);
1636
+ useEffect(() => {
1637
+ if (interactionMode !== "click-to-activate" || !isActive) return;
1638
+ const handleClickOutside = (e) => {
1639
+ const container = svgRef.current?.parentElement;
1640
+ if (container && !container.contains(e.target)) {
1641
+ setIsActive(false);
1642
+ }
1643
+ };
1644
+ document.addEventListener("click", handleClickOutside, true);
1645
+ return () => document.removeEventListener("click", handleClickOutside, true);
1646
+ }, [interactionMode, isActive, svgRef]);
1647
+ useEffect(() => {
1648
+ if (svgRef.current) {
1649
+ containerRef.current = svgRef.current.parentElement;
1650
+ }
1651
+ }, [svgRef]);
1652
+ const overlayProps = useMemo(() => {
1653
+ if (interactionMode === "always") return null;
1654
+ if (interactionMode === "modifier-key" && !hasAttemptedScroll) return null;
1655
+ if (interactionMode === "click-to-activate" && isActive) return null;
1656
+ return {
1657
+ mode: interactionMode,
1658
+ isActive,
1659
+ onActivate: handleActivate,
1660
+ width: "100%",
1661
+ height: "100%"
1662
+ };
1663
+ }, [interactionMode, isActive, hasAttemptedScroll, handleActivate]);
1664
+ const zoomIn = useCallback(() => {
1665
+ if (!svgRef.current) return;
1666
+ d32.select(svgRef.current).transition().duration(300).call(zoomBehavior.scaleBy, 1.3);
1667
+ }, [svgRef, zoomBehavior]);
1668
+ const zoomOut = useCallback(() => {
1669
+ if (!svgRef.current) return;
1670
+ d32.select(svgRef.current).transition().duration(300).call(zoomBehavior.scaleBy, 1 / 1.3);
1671
+ }, [svgRef, zoomBehavior]);
1672
+ const resetZoom = useCallback(() => {
1673
+ if (!svgRef.current) return;
1674
+ d32.select(svgRef.current).transition().duration(300).call(zoomBehavior.transform, d32.zoomIdentity);
1675
+ }, [svgRef, zoomBehavior]);
1676
+ return {
1677
+ isActive,
1678
+ overlayProps,
1679
+ zoomBehavior,
1680
+ zoomIn,
1681
+ zoomOut,
1682
+ resetZoom
1683
+ };
1684
+ }
1685
+ function InteractionOverlay({
1686
+ mode,
1687
+ isActive,
1688
+ onActivate,
1689
+ width,
1690
+ height
1691
+ }) {
1692
+ if (mode === "modifier-key") {
1693
+ return /* @__PURE__ */ jsx(
1694
+ "div",
1695
+ {
1696
+ className: "glyph-interaction-overlay",
1697
+ style: {
1698
+ ...OVERLAY_BASE_STYLE,
1699
+ width,
1700
+ height,
1701
+ pointerEvents: "none"
1702
+ },
1703
+ "aria-hidden": "true",
1704
+ children: /* @__PURE__ */ jsx("div", { style: TOOLTIP_STYLE, children: /* @__PURE__ */ jsx("span", { style: TOOLTIP_TEXT_STYLE, children: "Alt + scroll to zoom" }) })
1705
+ }
1706
+ );
1707
+ }
1708
+ if (mode === "click-to-activate" && !isActive) {
1709
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1710
+ /* @__PURE__ */ jsx(
1711
+ "div",
1712
+ {
1713
+ className: "glyph-interaction-overlay",
1714
+ style: {
1715
+ ...OVERLAY_BASE_STYLE,
1716
+ ...ACTIVATION_OVERLAY_STYLE,
1717
+ width,
1718
+ height
1719
+ },
1720
+ onClick: onActivate,
1721
+ role: "button",
1722
+ tabIndex: 0,
1723
+ "aria-label": "Click to activate graph interaction",
1724
+ onKeyDown: (e) => {
1725
+ if (e.key === "Enter" || e.key === " ") {
1726
+ e.preventDefault();
1727
+ onActivate();
1728
+ }
1729
+ },
1730
+ children: /* @__PURE__ */ jsx("div", { style: ACTIVATION_TEXT_STYLE, children: "Click to interact" })
1731
+ }
1732
+ ),
1733
+ /* @__PURE__ */ jsx("div", { style: SR_ONLY_STYLE, role: "status", "aria-live": "polite", "aria-atomic": "true", children: "Graph interaction inactive. Click to activate." })
1734
+ ] });
1735
+ }
1736
+ if (mode === "click-to-activate" && isActive) {
1737
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1738
+ /* @__PURE__ */ jsx(
1739
+ "div",
1740
+ {
1741
+ style: {
1742
+ ...OVERLAY_BASE_STYLE,
1743
+ width,
1744
+ height,
1745
+ pointerEvents: "none",
1746
+ border: "2px solid var(--glyph-interaction-active-border, #0a9d7c)",
1747
+ borderRadius: "4px"
1748
+ },
1749
+ "aria-hidden": "true"
1750
+ }
1751
+ ),
1752
+ /* @__PURE__ */ jsx("div", { style: SR_ONLY_STYLE, role: "status", "aria-live": "polite", "aria-atomic": "true", children: "Graph interaction active. Press Escape to deactivate." })
1753
+ ] });
1754
+ }
1755
+ return /* @__PURE__ */ jsx(Fragment, {});
1756
+ }
1757
+ var OVERLAY_BASE_STYLE = {
1758
+ position: "absolute",
1759
+ top: 0,
1760
+ left: 0,
1761
+ display: "flex",
1762
+ alignItems: "center",
1763
+ justifyContent: "center",
1764
+ zIndex: 10
1765
+ };
1766
+ var TOOLTIP_STYLE = {
1767
+ position: "absolute",
1768
+ bottom: "12px",
1769
+ right: "12px",
1770
+ padding: "6px 10px",
1771
+ backgroundColor: "var(--glyph-interaction-tooltip-bg, rgba(26, 32, 53, 0.9))",
1772
+ color: "var(--glyph-interaction-tooltip-text, #f4f6fa)",
1773
+ borderRadius: "4px",
1774
+ fontSize: "12px",
1775
+ fontFamily: "Inter, system-ui, sans-serif",
1776
+ fontWeight: 500,
1777
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
1778
+ pointerEvents: "none"
1779
+ };
1780
+ var TOOLTIP_TEXT_STYLE = {
1781
+ display: "flex",
1782
+ alignItems: "center",
1783
+ gap: "4px"
1784
+ };
1785
+ var ACTIVATION_OVERLAY_STYLE = {
1786
+ backgroundColor: "var(--glyph-interaction-overlay-bg, rgba(244, 246, 250, 0.8))",
1787
+ cursor: "pointer",
1788
+ transition: "background-color 0.2s ease"
1789
+ };
1790
+ var ACTIVATION_TEXT_STYLE = {
1791
+ padding: "12px 20px",
1792
+ backgroundColor: "var(--glyph-interaction-tooltip-bg, rgba(26, 32, 53, 0.9))",
1793
+ color: "var(--glyph-interaction-tooltip-text, #f4f6fa)",
1794
+ borderRadius: "6px",
1795
+ fontSize: "14px",
1796
+ fontFamily: "Inter, system-ui, sans-serif",
1797
+ fontWeight: 500,
1798
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.2)"
1799
+ };
1800
+ var SR_ONLY_STYLE = {
1801
+ position: "absolute",
1802
+ width: "1px",
1803
+ height: "1px",
1804
+ padding: 0,
1805
+ margin: "-1px",
1806
+ overflow: "hidden",
1807
+ clip: "rect(0, 0, 0, 0)",
1808
+ whiteSpace: "nowrap",
1809
+ border: 0
1810
+ };
1811
+ function ZoomControls({ onZoomIn, onZoomOut, onReset }) {
1812
+ return /* @__PURE__ */ jsxs("div", { style: CONTROLS_CONTAINER_STYLE, children: [
1813
+ /* @__PURE__ */ jsx(
1814
+ "button",
1815
+ {
1816
+ onClick: onZoomIn,
1817
+ style: BUTTON_STYLE,
1818
+ "aria-label": "Zoom in",
1819
+ title: "Zoom in",
1820
+ type: "button",
1821
+ children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [
1822
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "4", x2: "8", y2: "12", strokeWidth: "2" }),
1823
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "8", x2: "12", y2: "8", strokeWidth: "2" })
1824
+ ] })
1825
+ }
1826
+ ),
1827
+ /* @__PURE__ */ jsx(
1828
+ "button",
1829
+ {
1830
+ onClick: onZoomOut,
1831
+ style: BUTTON_STYLE,
1832
+ "aria-label": "Zoom out",
1833
+ title: "Zoom out",
1834
+ type: "button",
1835
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: /* @__PURE__ */ jsx("line", { x1: "4", y1: "8", x2: "12", y2: "8", strokeWidth: "2" }) })
1836
+ }
1837
+ ),
1838
+ /* @__PURE__ */ jsx(
1839
+ "button",
1840
+ {
1841
+ onClick: onReset,
1842
+ style: BUTTON_STYLE,
1843
+ "aria-label": "Reset zoom",
1844
+ title: "Reset zoom",
1845
+ type: "button",
1846
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "10", height: "10", strokeWidth: "2", rx: "1" }) })
1847
+ }
1848
+ )
1849
+ ] });
1850
+ }
1851
+ var CONTROLS_CONTAINER_STYLE = {
1852
+ position: "absolute",
1853
+ top: "12px",
1854
+ right: "12px",
1855
+ display: "flex",
1856
+ flexDirection: "column",
1857
+ gap: "4px",
1858
+ zIndex: 10
1859
+ };
1860
+ var BUTTON_STYLE = {
1861
+ width: "32px",
1862
+ height: "32px",
1863
+ padding: "0",
1864
+ display: "flex",
1865
+ alignItems: "center",
1866
+ justifyContent: "center",
1867
+ backgroundColor: "var(--glyph-surface-raised, #f4f6fa)",
1868
+ border: "1px solid var(--glyph-border, #d0d8e4)",
1869
+ borderRadius: "4px",
1870
+ color: "var(--glyph-text, #1a2035)",
1871
+ cursor: "pointer",
1872
+ transition: "all 0.2s ease",
1873
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)"
1874
+ };
1433
1875
  var GROUP_PALETTE = [
1434
1876
  "#00d4aa",
1435
1877
  // cyan-green
@@ -1476,7 +1918,7 @@ function getThemeVar(container, varName, fallback) {
1476
1918
  return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
1477
1919
  }
1478
1920
  var ARROW_MARKER_ID = "glyph-graph-arrowhead";
1479
- function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, onNodeClick) {
1921
+ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, zoomBehavior, onNodeClick) {
1480
1922
  const svg = d32.select(svgElement);
1481
1923
  svg.selectAll("*").remove();
1482
1924
  const width = Math.max(layout.width, 200);
@@ -1489,9 +1931,6 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, o
1489
1931
  const nodeStrokeWidth = getThemeVar(container, "--glyph-node-stroke-width", "1.5");
1490
1932
  const nodeFillOpacity = getThemeVar(container, "--glyph-node-fill-opacity", "0.85");
1491
1933
  const root = svg.append("g").attr("class", "glyph-graph-root");
1492
- const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
1493
- root.attr("transform", event.transform.toString());
1494
- });
1495
1934
  svg.call(zoomBehavior);
1496
1935
  const navigableNodes = /* @__PURE__ */ new Set();
1497
1936
  const refByAnchor = /* @__PURE__ */ new Map();
@@ -1509,7 +1948,7 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, o
1509
1948
  if (edge.label) {
1510
1949
  const mid = edge.points[Math.floor(edge.points.length / 2)];
1511
1950
  if (mid) {
1512
- edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("fill", "var(--glyph-edge-color, #6b7a94)").text(edge.label);
1951
+ edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("fill", "var(--glyph-edge-color, #6b7a94)").text(inlineToText(edge.label));
1513
1952
  }
1514
1953
  }
1515
1954
  }
@@ -1526,7 +1965,7 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, o
1526
1965
  } else {
1527
1966
  nodeG.append("rect").attr("x", nodeX).attr("y", nodeY).attr("width", node.width).attr("height", node.height).attr("rx", nodeRadius).attr("ry", nodeRadius).attr("fill", node.style?.["fill"] ?? color3).attr("stroke", node.style?.["stroke"] ?? defaultStroke).attr("stroke-width", node.style?.["stroke-width"] ?? nodeStrokeWidth).attr("opacity", nodeFillOpacity);
1528
1967
  }
1529
- nodeG.append("text").attr("x", node.x).attr("y", node.y).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(node.label);
1968
+ nodeG.append("text").attr("x", node.x).attr("y", node.y).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(inlineToText(node.label));
1530
1969
  if (isNavigable || onNodeClick) {
1531
1970
  nodeG.attr("cursor", "pointer");
1532
1971
  nodeG.on("click", () => {
@@ -1534,7 +1973,7 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, o
1534
1973
  const ref = refByAnchor.get(node.id);
1535
1974
  if (ref) onNavigate(ref);
1536
1975
  }
1537
- onNodeClick?.(node.id, node.label);
1976
+ onNodeClick?.(node.id, inlineToText(node.label));
1538
1977
  });
1539
1978
  }
1540
1979
  }
@@ -1548,6 +1987,7 @@ function Graph({
1548
1987
  container
1549
1988
  }) {
1550
1989
  const svgRef = useRef(null);
1990
+ const rootRef = useRef(null);
1551
1991
  const groupIndex = useRef(/* @__PURE__ */ new Map());
1552
1992
  const layoutResult = useMemo(() => {
1553
1993
  const direction = resolveLayout(data);
@@ -1556,6 +1996,12 @@ function Graph({
1556
1996
  }
1557
1997
  return computeDagreLayout(data.nodes, data.edges, direction);
1558
1998
  }, [data]);
1999
+ const { overlayProps, zoomBehavior, zoomIn, zoomOut, resetZoom } = useZoomInteraction({
2000
+ svgRef,
2001
+ rootRef,
2002
+ interactionMode: data.interactionMode ?? "modifier-key",
2003
+ blockId: block.id
2004
+ });
1559
2005
  const handleNodeClick = useMemo(() => {
1560
2006
  if (!onInteraction) return void 0;
1561
2007
  return (nodeId, nodeLabel) => {
@@ -1576,27 +2022,36 @@ function Graph({
1576
2022
  groupIndex.current,
1577
2023
  outgoingRefs,
1578
2024
  onNavigate,
2025
+ zoomBehavior,
1579
2026
  handleNodeClick
1580
2027
  );
1581
- }, [layoutResult, outgoingRefs, onNavigate, handleNodeClick]);
2028
+ const rootElement = svgRef.current.querySelector(".glyph-graph-root");
2029
+ if (rootElement) {
2030
+ rootRef.current = rootElement;
2031
+ }
2032
+ }, [layoutResult, outgoingRefs, onNavigate, zoomBehavior, handleNodeClick]);
1582
2033
  const ariaLabel = `${data.type} graph with ${data.nodes.length} nodes and ${data.edges.length} edges`;
1583
2034
  return /* @__PURE__ */ jsxs("div", { className: "glyph-graph-container", children: [
1584
- /* @__PURE__ */ jsx(
1585
- "svg",
1586
- {
1587
- ref: svgRef,
1588
- role: "img",
1589
- "aria-label": ariaLabel,
1590
- width: "100%",
1591
- height: "100%",
1592
- style: {
1593
- minHeight: container.tier === "compact" ? 200 : 300,
1594
- maxHeight: container.tier === "compact" ? 500 : 700,
1595
- display: "block"
2035
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
2036
+ /* @__PURE__ */ jsx(
2037
+ "svg",
2038
+ {
2039
+ ref: svgRef,
2040
+ role: "img",
2041
+ "aria-label": ariaLabel,
2042
+ width: "100%",
2043
+ height: "100%",
2044
+ style: {
2045
+ minHeight: container.tier === "compact" ? 200 : 300,
2046
+ maxHeight: container.tier === "compact" ? 500 : 700,
2047
+ display: "block"
2048
+ }
1596
2049
  }
1597
- }
1598
- ),
1599
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Graph data", style: SR_ONLY_STYLE, children: [
2050
+ ),
2051
+ overlayProps && /* @__PURE__ */ jsx(InteractionOverlay, { ...overlayProps }),
2052
+ /* @__PURE__ */ jsx(ZoomControls, { onZoomIn: zoomIn, onZoomOut: zoomOut, onReset: resetZoom })
2053
+ ] }),
2054
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Graph data", style: SR_ONLY_STYLE2, children: [
1600
2055
  /* @__PURE__ */ jsx("caption", { children: "Graph nodes and connections" }),
1601
2056
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1602
2057
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
@@ -1610,7 +2065,7 @@ function Graph({
1610
2065
  return `${dir} ${target}${e.label ? ` (${e.label})` : ""}`;
1611
2066
  }).join(", ");
1612
2067
  return /* @__PURE__ */ jsxs("tr", { children: [
1613
- /* @__PURE__ */ jsx("td", { children: node.label }),
2068
+ /* @__PURE__ */ jsx("td", { children: inlineToText(node.label) }),
1614
2069
  /* @__PURE__ */ jsx("td", { children: node.group ?? "" }),
1615
2070
  /* @__PURE__ */ jsx("td", { children: connections })
1616
2071
  ] }, node.id);
@@ -1618,7 +2073,7 @@ function Graph({
1618
2073
  ] })
1619
2074
  ] });
1620
2075
  }
1621
- var SR_ONLY_STYLE = {
2076
+ var SR_ONLY_STYLE2 = {
1622
2077
  position: "absolute",
1623
2078
  width: "1px",
1624
2079
  height: "1px",
@@ -1644,14 +2099,20 @@ var NODE_SEP2 = 60;
1644
2099
  var RANK_SEP2 = 80;
1645
2100
  var EDGE_SEP2 = 10;
1646
2101
  var LAYOUT_PADDING2 = 40;
1647
- var CHAR_WIDTH = 7.5;
1648
2102
  function computeEntitySize(entity) {
1649
2103
  const attrs = entity.attributes ?? [];
1650
2104
  const height = ENTITY_HEADER_HEIGHT + attrs.length * ENTITY_ATTR_HEIGHT + ENTITY_PADDING;
1651
- let maxTextWidth = entity.label.length * (CHAR_WIDTH + 1);
2105
+ const labelDimensions = measureText(entity.label, {
2106
+ fontSize: "14px",
2107
+ fontFamily: "Inter, system-ui, sans-serif"
2108
+ });
2109
+ let maxTextWidth = labelDimensions.width;
1652
2110
  for (const attr of attrs) {
1653
- const attrText = `${attr.name}: ${attr.type}`;
1654
- maxTextWidth = Math.max(maxTextWidth, attrText.length * CHAR_WIDTH);
2111
+ const attrNameDimensions = measureText(attr.name, {
2112
+ fontSize: "12px",
2113
+ fontFamily: "ui-monospace, monospace"
2114
+ });
2115
+ maxTextWidth = Math.max(maxTextWidth, attrNameDimensions.width + 100);
1655
2116
  }
1656
2117
  const width = Math.max(ENTITY_MIN_WIDTH, maxTextWidth + ENTITY_PADDING * 2 + 16);
1657
2118
  return { width, height };
@@ -1742,16 +2203,13 @@ function drawCrowsFoot(g, x, y, angle, symbol) {
1742
2203
  g.append("line").attr("x1", tx - Math.cos(perpAngle) * halfLen).attr("y1", ty - Math.sin(perpAngle) * halfLen).attr("x2", tx + Math.cos(perpAngle) * halfLen).attr("y2", ty + Math.sin(perpAngle) * halfLen).attr("stroke", "var(--glyph-relation-line, #6b7a94)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1743
2204
  }
1744
2205
  }
1745
- function renderRelation(svgElement, layout) {
2206
+ function renderRelation(svgElement, layout, zoomBehavior) {
1746
2207
  const svg = d32.select(svgElement);
1747
2208
  svg.selectAll("*").remove();
1748
2209
  const width = Math.max(layout.width, 200);
1749
2210
  const height = Math.max(layout.height, 200);
1750
2211
  svg.attr("viewBox", `0 0 ${width} ${height}`);
1751
2212
  const root = svg.append("g").attr("class", "glyph-relation-root");
1752
- const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
1753
- root.attr("transform", event.transform.toString());
1754
- });
1755
2213
  svg.call(zoomBehavior);
1756
2214
  const entityMap = /* @__PURE__ */ new Map();
1757
2215
  for (const entity of layout.entities) {
@@ -1778,7 +2236,7 @@ function renderRelation(svgElement, layout) {
1778
2236
  if (rel.label) {
1779
2237
  const mid = rel.points[Math.floor(rel.points.length / 2)];
1780
2238
  if (mid) {
1781
- edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 10).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-label, #6b7a94)").text(rel.label);
2239
+ edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 10).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-label, #6b7a94)").text(inlineToText(rel.label));
1782
2240
  }
1783
2241
  }
1784
2242
  const pFirst = rel.points[0];
@@ -1806,7 +2264,7 @@ function renderRelation(svgElement, layout) {
1806
2264
  const headerHeight = ENTITY_HEADER_HEIGHT;
1807
2265
  entityG.append("rect").attr("x", x).attr("y", y).attr("width", entity.width).attr("height", headerHeight).attr("rx", 4).attr("ry", 4).attr("fill", "var(--glyph-relation-header-bg, #00d4aa)");
1808
2266
  entityG.append("rect").attr("x", x).attr("y", y + headerHeight - 4).attr("width", entity.width).attr("height", 4).attr("fill", "var(--glyph-relation-header-bg, #00d4aa)");
1809
- entityG.append("text").attr("x", entity.x).attr("y", y + headerHeight / 2).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-weight", "bold").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-header-text, #fff)").text(entity.label);
2267
+ entityG.append("text").attr("x", entity.x).attr("y", y + headerHeight / 2).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-weight", "bold").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-header-text, #fff)").text(inlineToText(entity.label));
1810
2268
  if (attrs.length > 0) {
1811
2269
  entityG.append("line").attr("x1", x).attr("y1", y + headerHeight).attr("x2", x + entity.width).attr("y2", y + headerHeight).attr("stroke", "var(--glyph-relation-entity-border, #a8b5c8)").attr("stroke-width", 1);
1812
2270
  }
@@ -1815,7 +2273,7 @@ function renderRelation(svgElement, layout) {
1815
2273
  if (!attr) continue;
1816
2274
  const attrY = y + headerHeight + i * ENTITY_ATTR_HEIGHT + ENTITY_ATTR_HEIGHT / 2 + 4;
1817
2275
  const textEl = entityG.append("text").attr("x", x + ENTITY_PADDING).attr("y", attrY).attr("dy", "0.35em").attr("font-size", "12px").attr("font-family", "system-ui, -apple-system, monospace").attr("fill", "var(--glyph-relation-attr-text, #1a2035)");
1818
- const nameSpan = textEl.append("tspan").text(attr.name);
2276
+ const nameSpan = textEl.append("tspan").text(inlineToText(attr.name));
1819
2277
  if (attr.primaryKey) {
1820
2278
  nameSpan.attr("font-weight", "bold").attr("text-decoration", "underline");
1821
2279
  }
@@ -1823,29 +2281,44 @@ function renderRelation(svgElement, layout) {
1823
2281
  }
1824
2282
  }
1825
2283
  }
1826
- function Relation({ data }) {
2284
+ function Relation({ data, block }) {
1827
2285
  const svgRef = useRef(null);
2286
+ const rootRef = useRef(null);
1828
2287
  const layoutResult = useMemo(() => {
1829
2288
  return computeRelationLayout(data);
1830
2289
  }, [data]);
2290
+ const { overlayProps, zoomBehavior, zoomIn, zoomOut, resetZoom } = useZoomInteraction({
2291
+ svgRef,
2292
+ rootRef,
2293
+ interactionMode: data.interactionMode ?? "modifier-key",
2294
+ blockId: block.id
2295
+ });
1831
2296
  useEffect(() => {
1832
2297
  if (!svgRef.current) return;
1833
- renderRelation(svgRef.current, layoutResult);
1834
- }, [layoutResult]);
2298
+ renderRelation(svgRef.current, layoutResult, zoomBehavior);
2299
+ const rootElement = svgRef.current.querySelector(".glyph-relation-root");
2300
+ if (rootElement) {
2301
+ rootRef.current = rootElement;
2302
+ }
2303
+ }, [layoutResult, zoomBehavior]);
1835
2304
  const ariaLabel = `Entity-relationship diagram with ${data.entities.length} entities and ${data.relationships.length} relationships`;
1836
2305
  return /* @__PURE__ */ jsxs("div", { className: "glyph-relation-container", children: [
1837
- /* @__PURE__ */ jsx(
1838
- "svg",
1839
- {
1840
- ref: svgRef,
1841
- role: "img",
1842
- "aria-label": ariaLabel,
1843
- width: "100%",
1844
- height: "100%",
1845
- style: { minHeight: 300, maxHeight: 700, display: "block" }
1846
- }
1847
- ),
1848
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Entity-relationship data", style: SR_ONLY_STYLE2, children: [
2306
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
2307
+ /* @__PURE__ */ jsx(
2308
+ "svg",
2309
+ {
2310
+ ref: svgRef,
2311
+ role: "img",
2312
+ "aria-label": ariaLabel,
2313
+ width: "100%",
2314
+ height: "100%",
2315
+ style: { minHeight: 300, maxHeight: 700, display: "block" }
2316
+ }
2317
+ ),
2318
+ overlayProps && /* @__PURE__ */ jsx(InteractionOverlay, { ...overlayProps }),
2319
+ /* @__PURE__ */ jsx(ZoomControls, { onZoomIn: zoomIn, onZoomOut: zoomOut, onReset: resetZoom })
2320
+ ] }),
2321
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Entity-relationship data", style: SR_ONLY_STYLE3, children: [
1849
2322
  /* @__PURE__ */ jsx("caption", { children: "Entities and relationships" }),
1850
2323
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1851
2324
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Entity" }),
@@ -1860,7 +2333,7 @@ function Relation({ data }) {
1860
2333
  return `${dir} ${target} [${r.cardinality}]${r.label ? ` (${r.label})` : ""}`;
1861
2334
  }).join(", ");
1862
2335
  return /* @__PURE__ */ jsxs("tr", { children: [
1863
- /* @__PURE__ */ jsx("td", { children: entity.label }),
2336
+ /* @__PURE__ */ jsx("td", { children: inlineToText(entity.label) }),
1864
2337
  /* @__PURE__ */ jsx("td", { children: attrs }),
1865
2338
  /* @__PURE__ */ jsx("td", { children: rels })
1866
2339
  ] }, entity.id);
@@ -1868,7 +2341,7 @@ function Relation({ data }) {
1868
2341
  ] })
1869
2342
  ] });
1870
2343
  }
1871
- var SR_ONLY_STYLE2 = {
2344
+ var SR_ONLY_STYLE3 = {
1872
2345
  position: "absolute",
1873
2346
  width: "1px",
1874
2347
  height: "1px",
@@ -2398,9 +2871,9 @@ function CodeDiff({ data, block }) {
2398
2871
  }
2399
2872
  return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": summary, style: containerStyle11, children: [
2400
2873
  (beforeLabel || afterLabel) && /* @__PURE__ */ jsxs("div", { style: labelBarStyle, children: [
2401
- beforeLabel && /* @__PURE__ */ jsx("span", { children: beforeLabel }),
2874
+ beforeLabel && /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(RichText, { content: beforeLabel }) }),
2402
2875
  beforeLabel && afterLabel && /* @__PURE__ */ jsx("span", { children: "\u2192" }),
2403
- afterLabel && /* @__PURE__ */ jsx("span", { children: afterLabel })
2876
+ afterLabel && /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(RichText, { content: afterLabel }) })
2404
2877
  ] }),
2405
2878
  /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsx("table", { role: "grid", style: tableStyle2, children: /* @__PURE__ */ jsx("tbody", { children: diffLines.map((line6, i) => /* @__PURE__ */ jsxs(
2406
2879
  "tr",
@@ -2504,7 +2977,7 @@ function renderNodeShape(nodeG, node, fillOpacity, strokeWidth) {
2504
2977
  }
2505
2978
  }
2506
2979
  }
2507
- function renderFlowchart(svgElement, layout) {
2980
+ function renderFlowchart(svgElement, layout, zoomBehavior) {
2508
2981
  const svg = d32.select(svgElement);
2509
2982
  svg.selectAll("*").remove();
2510
2983
  const width = Math.max(layout.width, 200);
@@ -2516,9 +2989,6 @@ function renderFlowchart(svgElement, layout) {
2516
2989
  const nodeStrokeWidth = getThemeVar2(container, "--glyph-node-stroke-width", "1.5");
2517
2990
  const nodeFillOpacity = getThemeVar2(container, "--glyph-node-fill-opacity", "0.85");
2518
2991
  const root = svg.append("g").attr("class", "glyph-flowchart-root");
2519
- const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
2520
- root.attr("transform", event.transform.toString());
2521
- });
2522
2992
  svg.call(zoomBehavior);
2523
2993
  const lineGen = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveBasis);
2524
2994
  const edgeGroup = root.append("g").attr("class", "glyph-flowchart-edges");
@@ -2528,7 +2998,7 @@ function renderFlowchart(svgElement, layout) {
2528
2998
  if (edge.label) {
2529
2999
  const mid = edge.points[Math.floor(edge.points.length / 2)];
2530
3000
  if (mid) {
2531
- edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("fill", "var(--glyph-text-muted, #6b7a94)").text(edge.label);
3001
+ edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("fill", "var(--glyph-text-muted, #6b7a94)").text(inlineToText(edge.label));
2532
3002
  }
2533
3003
  }
2534
3004
  }
@@ -2536,19 +3006,34 @@ function renderFlowchart(svgElement, layout) {
2536
3006
  for (const node of layout.nodes) {
2537
3007
  const nodeG = nodeGroup.append("g").attr("class", "glyph-flowchart-node");
2538
3008
  renderNodeShape(nodeG, node, nodeFillOpacity, nodeStrokeWidth);
2539
- nodeG.append("text").attr("x", node.x).attr("y", node.y).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(node.label);
3009
+ nodeG.append("text").attr("x", node.x).attr("y", node.y).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(inlineToText(node.label));
2540
3010
  }
2541
3011
  }
2542
- function Flowchart({ data, container }) {
3012
+ function Flowchart({
3013
+ data,
3014
+ block,
3015
+ container
3016
+ }) {
2543
3017
  const svgRef = useRef(null);
3018
+ const rootRef = useRef(null);
2544
3019
  const layoutResult = useMemo(() => computeLayout(data.nodes, data.edges, data.direction), [data]);
3020
+ const { overlayProps, zoomBehavior, zoomIn, zoomOut, resetZoom } = useZoomInteraction({
3021
+ svgRef,
3022
+ rootRef,
3023
+ interactionMode: data.interactionMode ?? "modifier-key",
3024
+ blockId: block.id
3025
+ });
2545
3026
  useEffect(() => {
2546
3027
  if (!svgRef.current) return;
2547
- renderFlowchart(svgRef.current, layoutResult);
2548
- }, [layoutResult]);
3028
+ renderFlowchart(svgRef.current, layoutResult, zoomBehavior);
3029
+ const rootElement = svgRef.current.querySelector(".glyph-flowchart-root");
3030
+ if (rootElement) {
3031
+ rootRef.current = rootElement;
3032
+ }
3033
+ }, [layoutResult, zoomBehavior]);
2549
3034
  const nodeCount = data.nodes.length;
2550
3035
  const edgeCount = data.edges.length;
2551
- const ariaLabel = data.title ? `${data.title}: flowchart with ${nodeCount} nodes and ${edgeCount} edges` : `Flowchart with ${nodeCount} nodes and ${edgeCount} edges`;
3036
+ const ariaLabel = data.title ? `${inlineToText(data.title)}: flowchart with ${nodeCount} nodes and ${edgeCount} edges` : `Flowchart with ${nodeCount} nodes and ${edgeCount} edges`;
2552
3037
  return /* @__PURE__ */ jsxs("div", { className: "glyph-flowchart-container", children: [
2553
3038
  data.title && /* @__PURE__ */ jsx(
2554
3039
  "div",
@@ -2560,25 +3045,29 @@ function Flowchart({ data, container }) {
2560
3045
  color: "var(--glyph-heading, #edf0f5)",
2561
3046
  marginBottom: "0.5rem"
2562
3047
  },
2563
- children: data.title
3048
+ children: /* @__PURE__ */ jsx(RichText, { content: data.title })
2564
3049
  }
2565
3050
  ),
2566
- /* @__PURE__ */ jsx(
2567
- "svg",
2568
- {
2569
- ref: svgRef,
2570
- role: "img",
2571
- "aria-label": ariaLabel,
2572
- width: "100%",
2573
- height: "100%",
2574
- style: {
2575
- minHeight: container.tier === "compact" ? 200 : 300,
2576
- maxHeight: container.tier === "compact" ? 500 : 700,
2577
- display: "block"
3051
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
3052
+ /* @__PURE__ */ jsx(
3053
+ "svg",
3054
+ {
3055
+ ref: svgRef,
3056
+ role: "img",
3057
+ "aria-label": ariaLabel,
3058
+ width: "100%",
3059
+ height: "100%",
3060
+ style: {
3061
+ minHeight: container.tier === "compact" ? 200 : 300,
3062
+ maxHeight: container.tier === "compact" ? 500 : 700,
3063
+ display: "block"
3064
+ }
2578
3065
  }
2579
- }
2580
- ),
2581
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Flowchart data", style: SR_ONLY_STYLE3, children: [
3066
+ ),
3067
+ overlayProps && /* @__PURE__ */ jsx(InteractionOverlay, { ...overlayProps }),
3068
+ /* @__PURE__ */ jsx(ZoomControls, { onZoomIn: zoomIn, onZoomOut: zoomOut, onReset: resetZoom })
3069
+ ] }),
3070
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Flowchart data", style: SR_ONLY_STYLE4, children: [
2582
3071
  /* @__PURE__ */ jsx("caption", { children: "Flowchart nodes and connections" }),
2583
3072
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
2584
3073
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
@@ -2589,10 +3078,10 @@ function Flowchart({ data, container }) {
2589
3078
  const connections = data.edges.filter((e) => e.from === node.id || e.to === node.id).map((e) => {
2590
3079
  const target = e.from === node.id ? e.to : e.from;
2591
3080
  const dir = e.from === node.id ? "->" : "<-";
2592
- return `${dir} ${target}${e.label ? ` (${e.label})` : ""}`;
3081
+ return `${dir} ${target}${e.label ? ` (${inlineToText(e.label)})` : ""}`;
2593
3082
  }).join(", ");
2594
3083
  return /* @__PURE__ */ jsxs("tr", { children: [
2595
- /* @__PURE__ */ jsx("td", { children: node.label }),
3084
+ /* @__PURE__ */ jsx("td", { children: inlineToText(node.label) }),
2596
3085
  /* @__PURE__ */ jsx("td", { children: node.type }),
2597
3086
  /* @__PURE__ */ jsx("td", { children: connections })
2598
3087
  ] }, node.id);
@@ -2600,7 +3089,7 @@ function Flowchart({ data, container }) {
2600
3089
  ] })
2601
3090
  ] });
2602
3091
  }
2603
- var SR_ONLY_STYLE3 = {
3092
+ var SR_ONLY_STYLE4 = {
2604
3093
  position: "absolute",
2605
3094
  width: "1px",
2606
3095
  height: "1px",
@@ -3014,20 +3503,24 @@ function renderActorBox(actor, cx, y, keyPrefix, width = ACTOR_WIDTH, height = A
3014
3503
  strokeWidth: 1.5
3015
3504
  }
3016
3505
  ),
3017
- /* @__PURE__ */ jsx(
3018
- "text",
3506
+ /* @__PURE__ */ jsx("foreignObject", { x: cx - width / 2, y, width, height, children: /* @__PURE__ */ jsx(
3507
+ "div",
3019
3508
  {
3020
- x: cx,
3021
- y: y + height / 2,
3022
- dy: "0.35em",
3023
- textAnchor: "middle",
3024
- fontSize,
3025
- fontFamily: "Inter, system-ui, sans-serif",
3026
- fontWeight: 600,
3027
- fill: "var(--glyph-text, #d4dae3)",
3028
- children: actor.label
3509
+ style: {
3510
+ display: "flex",
3511
+ alignItems: "center",
3512
+ justifyContent: "center",
3513
+ width: "100%",
3514
+ height: "100%",
3515
+ fontSize,
3516
+ fontFamily: "Inter, system-ui, sans-serif",
3517
+ fontWeight: 600,
3518
+ color: "var(--glyph-text, #d4dae3)",
3519
+ textAlign: "center"
3520
+ },
3521
+ children: /* @__PURE__ */ jsx(RichText, { content: actor.label })
3029
3522
  }
3030
- )
3523
+ ) })
3031
3524
  ] }, `${keyPrefix}-${actor.id}`);
3032
3525
  }
3033
3526
  function renderSelfMessage(x, y, label, idx, fontSize = "12px") {
@@ -3042,18 +3535,19 @@ function renderSelfMessage(x, y, label, idx, fontSize = "12px") {
3042
3535
  markerEnd: "url(#seq-arrow-solid)"
3043
3536
  }
3044
3537
  ),
3045
- /* @__PURE__ */ jsx(
3046
- "text",
3538
+ /* @__PURE__ */ jsx("foreignObject", { x: x + SELF_ARC_WIDTH + 6, y, width: 150, height: SELF_ARC_HEIGHT, children: /* @__PURE__ */ jsx(
3539
+ "div",
3047
3540
  {
3048
- x: x + SELF_ARC_WIDTH + 6,
3049
- y: y + SELF_ARC_HEIGHT / 2,
3050
- dy: "0.35em",
3051
- fontSize,
3052
- fontFamily: "Inter, system-ui, sans-serif",
3053
- fill: "var(--glyph-text, #d4dae3)",
3054
- children: label
3541
+ style: {
3542
+ display: "flex",
3543
+ alignItems: "center",
3544
+ fontSize,
3545
+ fontFamily: "Inter, system-ui, sans-serif",
3546
+ color: "var(--glyph-text, #d4dae3)"
3547
+ },
3548
+ children: /* @__PURE__ */ jsx(RichText, { content: label })
3055
3549
  }
3056
- )
3550
+ ) })
3057
3551
  ] }, `msg-${idx}`);
3058
3552
  }
3059
3553
  function renderStandardMessage(fromX, toX, y, label, isDashed, idx, fontSize = "12px") {
@@ -3073,18 +3567,20 @@ function renderStandardMessage(fromX, toX, y, label, isDashed, idx, fontSize = "
3073
3567
  markerEnd: `url(#${markerId})`
3074
3568
  }
3075
3569
  ),
3076
- /* @__PURE__ */ jsx(
3077
- "text",
3570
+ /* @__PURE__ */ jsx("foreignObject", { x: midX - 75, y: y - 22, width: 150, height: 20, children: /* @__PURE__ */ jsx(
3571
+ "div",
3078
3572
  {
3079
- x: midX,
3080
- y: y - 8,
3081
- textAnchor: "middle",
3082
- fontSize,
3083
- fontFamily: "Inter, system-ui, sans-serif",
3084
- fill: "var(--glyph-text, #d4dae3)",
3085
- children: label
3573
+ style: {
3574
+ display: "flex",
3575
+ justifyContent: "center",
3576
+ fontSize,
3577
+ fontFamily: "Inter, system-ui, sans-serif",
3578
+ color: "var(--glyph-text, #d4dae3)",
3579
+ textAlign: "center"
3580
+ },
3581
+ children: /* @__PURE__ */ jsx(RichText, { content: label })
3086
3582
  }
3087
- )
3583
+ ) })
3088
3584
  ] }, `msg-${idx}`);
3089
3585
  }
3090
3586
  function Sequence({ data, container }) {
@@ -3109,7 +3605,7 @@ function Sequence({ data, container }) {
3109
3605
  const firstMsgY = lifelineStartY + MSG_SPACING;
3110
3606
  const lastMsgY = firstMsgY + (messageCount - 1) * MSG_SPACING;
3111
3607
  const svgHeight = lastMsgY + BOTTOM_PADDING + effectiveActorHeight;
3112
- const ariaLabel = data.title ? `${data.title}: sequence diagram with ${actorCount} actors and ${messageCount} messages` : `Sequence diagram with ${actorCount} actors and ${messageCount} messages`;
3608
+ const ariaLabel = data.title ? `${inlineToText(data.title)}: sequence diagram with ${actorCount} actors and ${messageCount} messages` : `Sequence diagram with ${actorCount} actors and ${messageCount} messages`;
3113
3609
  return /* @__PURE__ */ jsxs("div", { className: "glyph-sequence-container", children: [
3114
3610
  data.title && /* @__PURE__ */ jsx(
3115
3611
  "div",
@@ -3121,7 +3617,7 @@ function Sequence({ data, container }) {
3121
3617
  color: "var(--glyph-heading, #edf0f5)",
3122
3618
  marginBottom: "0.5rem"
3123
3619
  },
3124
- children: data.title
3620
+ children: /* @__PURE__ */ jsx(RichText, { content: data.title })
3125
3621
  }
3126
3622
  ),
3127
3623
  /* @__PURE__ */ jsxs(
@@ -3222,7 +3718,7 @@ function Sequence({ data, container }) {
3222
3718
  ]
3223
3719
  }
3224
3720
  ),
3225
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Sequence data", style: SR_ONLY_STYLE4, children: [
3721
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Sequence data", style: SR_ONLY_STYLE5, children: [
3226
3722
  /* @__PURE__ */ jsx("caption", { children: "Sequence messages in order" }),
3227
3723
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3228
3724
  /* @__PURE__ */ jsx("th", { scope: "col", children: "#" }),
@@ -3236,16 +3732,16 @@ function Sequence({ data, container }) {
3236
3732
  const toActor = data.actors.find((a) => a.id === msg.to);
3237
3733
  return /* @__PURE__ */ jsxs("tr", { children: [
3238
3734
  /* @__PURE__ */ jsx("td", { children: idx + 1 }),
3239
- /* @__PURE__ */ jsx("td", { children: fromActor?.label ?? msg.from }),
3240
- /* @__PURE__ */ jsx("td", { children: toActor?.label ?? msg.to }),
3241
- /* @__PURE__ */ jsx("td", { children: msg.label }),
3735
+ /* @__PURE__ */ jsx("td", { children: fromActor ? inlineToText(fromActor.label) : msg.from }),
3736
+ /* @__PURE__ */ jsx("td", { children: toActor ? inlineToText(toActor.label) : msg.to }),
3737
+ /* @__PURE__ */ jsx("td", { children: inlineToText(msg.label) }),
3242
3738
  /* @__PURE__ */ jsx("td", { children: msg.type })
3243
3739
  ] }, idx);
3244
3740
  }) })
3245
3741
  ] })
3246
3742
  ] });
3247
3743
  }
3248
- var SR_ONLY_STYLE4 = {
3744
+ var SR_ONLY_STYLE5 = {
3249
3745
  position: "absolute",
3250
3746
  width: "1px",
3251
3747
  height: "1px",
@@ -3651,7 +4147,7 @@ function Architecture({
3651
4147
  }
3652
4148
  }
3653
4149
  ),
3654
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Architecture data", style: SR_ONLY_STYLE5, children: [
4150
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Architecture data", style: SR_ONLY_STYLE6, children: [
3655
4151
  /* @__PURE__ */ jsx("caption", { children: "Architecture nodes and connections" }),
3656
4152
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3657
4153
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
@@ -3695,7 +4191,7 @@ function countLeafNodes(children) {
3695
4191
  }
3696
4192
  return count;
3697
4193
  }
3698
- var SR_ONLY_STYLE5 = {
4194
+ var SR_ONLY_STYLE6 = {
3699
4195
  position: "absolute",
3700
4196
  width: "1px",
3701
4197
  height: "1px",
@@ -3883,7 +4379,7 @@ function layoutTree(data) {
3883
4379
  };
3884
4380
  }
3885
4381
  function renderAccessibleList(root, children) {
3886
- return /* @__PURE__ */ jsx("ul", { style: SR_ONLY_STYLE6, role: "list", "aria-label": "Mind map structure", children: /* @__PURE__ */ jsxs("li", { children: [
4382
+ return /* @__PURE__ */ jsx("ul", { style: SR_ONLY_STYLE7, role: "list", "aria-label": "Mind map structure", children: /* @__PURE__ */ jsxs("li", { children: [
3887
4383
  root,
3888
4384
  children.length > 0 && renderAccessibleChildren(children)
3889
4385
  ] }) });
@@ -4010,7 +4506,7 @@ function MindMap({ data, container }) {
4010
4506
  renderAccessibleList(data.root, data.children)
4011
4507
  ] });
4012
4508
  }
4013
- var SR_ONLY_STYLE6 = {
4509
+ var SR_ONLY_STYLE7 = {
4014
4510
  position: "absolute",
4015
4511
  width: "1px",
4016
4512
  height: "1px",
@@ -4085,7 +4581,7 @@ function Equation({ data }) {
4085
4581
  const ariaLabel = `Equation: ${data.expression}`;
4086
4582
  return /* @__PURE__ */ jsxs("div", { style: containerStyle, role: "math", "aria-label": ariaLabel, children: [
4087
4583
  error || html === "" ? /* @__PURE__ */ jsx("code", { style: fallbackStyle, children: data.expression }) : /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } }),
4088
- data.label !== void 0 && /* @__PURE__ */ jsx("div", { style: labelStyle2, children: data.label })
4584
+ data.label !== void 0 && /* @__PURE__ */ jsx("div", { style: labelStyle2, children: /* @__PURE__ */ jsx(RichText, { content: data.label }) })
4089
4585
  ] });
4090
4586
  }
4091
4587
  if (data.steps !== void 0 && data.steps.length > 0) {
@@ -4096,10 +4592,10 @@ function Equation({ data }) {
4096
4592
  const { html, error } = renderLatex(step.expression);
4097
4593
  return /* @__PURE__ */ jsxs("div", { style: stepRowStyle, children: [
4098
4594
  error || html === "" ? /* @__PURE__ */ jsx("code", { style: fallbackStyle, children: step.expression }) : /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } }),
4099
- step.annotation !== void 0 && /* @__PURE__ */ jsx("span", { style: annotationStyle, children: step.annotation })
4595
+ step.annotation !== void 0 && /* @__PURE__ */ jsx("span", { style: annotationStyle, children: /* @__PURE__ */ jsx(RichText, { content: step.annotation }) })
4100
4596
  ] }, idx);
4101
4597
  }) }),
4102
- data.label !== void 0 && /* @__PURE__ */ jsx("div", { style: labelStyle2, children: data.label })
4598
+ data.label !== void 0 && /* @__PURE__ */ jsx("div", { style: labelStyle2, children: /* @__PURE__ */ jsx(RichText, { content: data.label }) })
4103
4599
  ] });
4104
4600
  }
4105
4601
  return /* @__PURE__ */ jsx("div", { style: containerStyle, role: "math", "aria-label": "Empty equation", children: /* @__PURE__ */ jsx("span", { style: { color: "var(--glyph-text-muted, #6b7a94)" }, children: "No equation provided" }) });
@@ -6525,8 +7021,8 @@ function Kanban({
6525
7021
  style: cardStyle(isGrabbed, card.priority),
6526
7022
  onKeyDown: (e) => handleCardKeyDown(e, card.id, col.id, cardIndex),
6527
7023
  children: [
6528
- /* @__PURE__ */ jsx("div", { style: cardTitleStyle, children: card.title }),
6529
- card.description && /* @__PURE__ */ jsx("div", { style: cardDescStyle, children: card.description }),
7024
+ /* @__PURE__ */ jsx("div", { style: cardTitleStyle, children: /* @__PURE__ */ jsx(RichText, { content: card.title }) }),
7025
+ card.description && /* @__PURE__ */ jsx("div", { style: cardDescStyle, children: /* @__PURE__ */ jsx(RichText, { content: card.description }) }),
6530
7026
  card.tags && card.tags.length > 0 && /* @__PURE__ */ jsx("div", { style: tagContainerStyle, children: card.tags.map((tag) => /* @__PURE__ */ jsx("span", { style: tagStyle, children: tag }, tag)) })
6531
7027
  ]
6532
7028
  },