@glyphjs/components 0.2.0 → 0.4.0

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,5 +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
- import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { RichText } from '@glyphjs/runtime';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
4
  import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
4
5
  import * as d32 from 'd3';
5
6
  import { scaleTime, scaleOrdinal } from 'd3';
@@ -49,8 +50,8 @@ function Callout({ data }) {
49
50
  return /* @__PURE__ */ jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle11, children: [
50
51
  /* @__PURE__ */ jsx("span", { style: iconStyle, "aria-hidden": "true", children: CALLOUT_ICONS[type] }),
51
52
  /* @__PURE__ */ jsxs("div", { style: bodyStyle3, children: [
52
- title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: title }),
53
- /* @__PURE__ */ jsx("div", { children: content })
53
+ title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: /* @__PURE__ */ jsx(RichText, { content: title }) }),
54
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(RichText, { content }) })
54
55
  ] })
55
56
  ] });
56
57
  }
@@ -528,7 +529,7 @@ function Steps({ data }) {
528
529
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: indicatorStyle(status), children: status === "completed" ? "\u2713" : "" }),
529
530
  /* @__PURE__ */ jsxs("div", { style: bodyStyle, children: [
530
531
  /* @__PURE__ */ jsx("div", { style: titleStyle(status), children: step.title }),
531
- /* @__PURE__ */ jsx("div", { style: contentStyle(status), children: step.content })
532
+ /* @__PURE__ */ jsx("div", { style: contentStyle(status), children: /* @__PURE__ */ jsx(RichText, { content: step.content }) })
532
533
  ] })
533
534
  ]
534
535
  },
@@ -1187,7 +1188,7 @@ function Timeline({ data }) {
1187
1188
  fontWeight: 700,
1188
1189
  marginTop: 2
1189
1190
  },
1190
- children: pe.event.title
1191
+ children: /* @__PURE__ */ jsx(RichText, { content: pe.event.title })
1191
1192
  }
1192
1193
  ),
1193
1194
  pe.event.description && /* @__PURE__ */ jsx(
@@ -1198,7 +1199,7 @@ function Timeline({ data }) {
1198
1199
  color: "var(--glyph-timeline-desc-color, #7a8599)",
1199
1200
  marginTop: 2
1200
1201
  },
1201
- children: pe.event.description
1202
+ children: /* @__PURE__ */ jsx(RichText, { content: pe.event.description })
1202
1203
  }
1203
1204
  )
1204
1205
  ] })
@@ -1216,12 +1217,16 @@ function Timeline({ data }) {
1216
1217
  clipPath: "inset(50%)",
1217
1218
  whiteSpace: "nowrap"
1218
1219
  },
1219
- children: sorted.map((e, idx) => /* @__PURE__ */ jsxs("li", { children: [
1220
- /* @__PURE__ */ jsx("time", { dateTime: isoDate(e.date), children: formatDate(e.date) }),
1221
- " \u2014 ",
1222
- /* @__PURE__ */ jsx("strong", { children: e.title }),
1223
- e.description ? `: ${e.description}` : ""
1224
- ] }, idx))
1220
+ children: sorted.map((e, idx) => {
1221
+ const titleText = typeof e.title === "string" ? e.title : "Event";
1222
+ const descText = typeof e.description === "string" ? e.description : "";
1223
+ return /* @__PURE__ */ jsxs("li", { children: [
1224
+ /* @__PURE__ */ jsx("time", { dateTime: isoDate(e.date), children: formatDate(e.date) }),
1225
+ " \u2014 ",
1226
+ /* @__PURE__ */ jsx("strong", { children: titleText }),
1227
+ descText ? `: ${descText}` : ""
1228
+ ] }, idx);
1229
+ })
1225
1230
  }
1226
1231
  )
1227
1232
  ]
@@ -1425,6 +1430,326 @@ function computeForceLayout(nodes, edges) {
1425
1430
  height: maxY + LAYOUT_PADDING
1426
1431
  };
1427
1432
  }
1433
+ function useZoomInteraction({
1434
+ svgRef,
1435
+ rootRef,
1436
+ interactionMode
1437
+ }) {
1438
+ const [isActive, setIsActive] = useState(interactionMode === "always");
1439
+ const [hasAttemptedScroll, setHasAttemptedScroll] = useState(false);
1440
+ const containerRef = useRef(null);
1441
+ useEffect(() => {
1442
+ setIsActive(interactionMode === "always");
1443
+ setHasAttemptedScroll(false);
1444
+ }, [interactionMode]);
1445
+ const filterFunction = useCallback(
1446
+ (event) => {
1447
+ if (interactionMode === "always") {
1448
+ return true;
1449
+ }
1450
+ if (interactionMode === "modifier-key") {
1451
+ if (event.type === "mousedown") return true;
1452
+ if (event.type === "wheel") {
1453
+ const wheelEvent = event;
1454
+ const hasModifier = wheelEvent.altKey;
1455
+ if (!hasModifier && !hasAttemptedScroll) {
1456
+ setHasAttemptedScroll(true);
1457
+ setTimeout(() => setHasAttemptedScroll(false), 3e3);
1458
+ }
1459
+ return hasModifier;
1460
+ }
1461
+ return true;
1462
+ }
1463
+ if (interactionMode === "click-to-activate") {
1464
+ return isActive;
1465
+ }
1466
+ return true;
1467
+ },
1468
+ [interactionMode, isActive, hasAttemptedScroll]
1469
+ );
1470
+ useEffect(() => {
1471
+ if (interactionMode !== "modifier-key" || !svgRef.current) return;
1472
+ const svg = svgRef.current;
1473
+ const container = svg.parentElement;
1474
+ if (!container) return;
1475
+ const handleWheel = (event) => {
1476
+ const target = event.target;
1477
+ if (event.altKey && svg.contains(target)) {
1478
+ event.preventDefault();
1479
+ event.stopPropagation();
1480
+ }
1481
+ };
1482
+ container.addEventListener("wheel", handleWheel, { passive: false, capture: true });
1483
+ return () => {
1484
+ container.removeEventListener("wheel", handleWheel, { capture: true });
1485
+ };
1486
+ }, [interactionMode, svgRef]);
1487
+ const zoomBehavior = useMemo(() => {
1488
+ const zoom3 = d32.zoom().scaleExtent([0.1, 4]);
1489
+ if (typeof zoom3.filter === "function") {
1490
+ zoom3.filter(filterFunction);
1491
+ }
1492
+ zoom3.on("zoom", (event) => {
1493
+ if (rootRef.current) {
1494
+ d32.select(rootRef.current).attr("transform", event.transform.toString());
1495
+ }
1496
+ });
1497
+ return zoom3;
1498
+ }, [filterFunction, rootRef]);
1499
+ const handleActivate = useCallback(() => {
1500
+ if (interactionMode === "click-to-activate") {
1501
+ setIsActive(true);
1502
+ }
1503
+ }, [interactionMode]);
1504
+ useEffect(() => {
1505
+ if (interactionMode !== "click-to-activate" || !isActive) return;
1506
+ const handleKeyDown = (e) => {
1507
+ if (e.key === "Escape") {
1508
+ setIsActive(false);
1509
+ }
1510
+ };
1511
+ document.addEventListener("keydown", handleKeyDown);
1512
+ return () => document.removeEventListener("keydown", handleKeyDown);
1513
+ }, [interactionMode, isActive]);
1514
+ useEffect(() => {
1515
+ if (interactionMode !== "click-to-activate" || !isActive) return;
1516
+ const handleClickOutside = (e) => {
1517
+ const container = svgRef.current?.parentElement;
1518
+ if (container && !container.contains(e.target)) {
1519
+ setIsActive(false);
1520
+ }
1521
+ };
1522
+ document.addEventListener("click", handleClickOutside, true);
1523
+ return () => document.removeEventListener("click", handleClickOutside, true);
1524
+ }, [interactionMode, isActive, svgRef]);
1525
+ useEffect(() => {
1526
+ if (svgRef.current) {
1527
+ containerRef.current = svgRef.current.parentElement;
1528
+ }
1529
+ }, [svgRef]);
1530
+ const overlayProps = useMemo(() => {
1531
+ if (interactionMode === "always") return null;
1532
+ if (interactionMode === "modifier-key" && !hasAttemptedScroll) return null;
1533
+ if (interactionMode === "click-to-activate" && isActive) return null;
1534
+ return {
1535
+ mode: interactionMode,
1536
+ isActive,
1537
+ onActivate: handleActivate,
1538
+ width: "100%",
1539
+ height: "100%"
1540
+ };
1541
+ }, [interactionMode, isActive, hasAttemptedScroll, handleActivate]);
1542
+ const zoomIn = useCallback(() => {
1543
+ if (!svgRef.current) return;
1544
+ d32.select(svgRef.current).transition().duration(300).call(zoomBehavior.scaleBy, 1.3);
1545
+ }, [svgRef, zoomBehavior]);
1546
+ const zoomOut = useCallback(() => {
1547
+ if (!svgRef.current) return;
1548
+ d32.select(svgRef.current).transition().duration(300).call(zoomBehavior.scaleBy, 1 / 1.3);
1549
+ }, [svgRef, zoomBehavior]);
1550
+ const resetZoom = useCallback(() => {
1551
+ if (!svgRef.current) return;
1552
+ d32.select(svgRef.current).transition().duration(300).call(zoomBehavior.transform, d32.zoomIdentity);
1553
+ }, [svgRef, zoomBehavior]);
1554
+ return {
1555
+ isActive,
1556
+ overlayProps,
1557
+ zoomBehavior,
1558
+ zoomIn,
1559
+ zoomOut,
1560
+ resetZoom
1561
+ };
1562
+ }
1563
+ function InteractionOverlay({
1564
+ mode,
1565
+ isActive,
1566
+ onActivate,
1567
+ width,
1568
+ height
1569
+ }) {
1570
+ if (mode === "modifier-key") {
1571
+ return /* @__PURE__ */ jsx(
1572
+ "div",
1573
+ {
1574
+ className: "glyph-interaction-overlay",
1575
+ style: {
1576
+ ...OVERLAY_BASE_STYLE,
1577
+ width,
1578
+ height,
1579
+ pointerEvents: "none"
1580
+ },
1581
+ "aria-hidden": "true",
1582
+ children: /* @__PURE__ */ jsx("div", { style: TOOLTIP_STYLE, children: /* @__PURE__ */ jsx("span", { style: TOOLTIP_TEXT_STYLE, children: "Alt + scroll to zoom" }) })
1583
+ }
1584
+ );
1585
+ }
1586
+ if (mode === "click-to-activate" && !isActive) {
1587
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1588
+ /* @__PURE__ */ jsx(
1589
+ "div",
1590
+ {
1591
+ className: "glyph-interaction-overlay",
1592
+ style: {
1593
+ ...OVERLAY_BASE_STYLE,
1594
+ ...ACTIVATION_OVERLAY_STYLE,
1595
+ width,
1596
+ height
1597
+ },
1598
+ onClick: onActivate,
1599
+ role: "button",
1600
+ tabIndex: 0,
1601
+ "aria-label": "Click to activate graph interaction",
1602
+ onKeyDown: (e) => {
1603
+ if (e.key === "Enter" || e.key === " ") {
1604
+ e.preventDefault();
1605
+ onActivate();
1606
+ }
1607
+ },
1608
+ children: /* @__PURE__ */ jsx("div", { style: ACTIVATION_TEXT_STYLE, children: "Click to interact" })
1609
+ }
1610
+ ),
1611
+ /* @__PURE__ */ jsx("div", { style: SR_ONLY_STYLE, role: "status", "aria-live": "polite", "aria-atomic": "true", children: "Graph interaction inactive. Click to activate." })
1612
+ ] });
1613
+ }
1614
+ if (mode === "click-to-activate" && isActive) {
1615
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1616
+ /* @__PURE__ */ jsx(
1617
+ "div",
1618
+ {
1619
+ style: {
1620
+ ...OVERLAY_BASE_STYLE,
1621
+ width,
1622
+ height,
1623
+ pointerEvents: "none",
1624
+ border: "2px solid var(--glyph-interaction-active-border, #0a9d7c)",
1625
+ borderRadius: "4px"
1626
+ },
1627
+ "aria-hidden": "true"
1628
+ }
1629
+ ),
1630
+ /* @__PURE__ */ jsx("div", { style: SR_ONLY_STYLE, role: "status", "aria-live": "polite", "aria-atomic": "true", children: "Graph interaction active. Press Escape to deactivate." })
1631
+ ] });
1632
+ }
1633
+ return /* @__PURE__ */ jsx(Fragment, {});
1634
+ }
1635
+ var OVERLAY_BASE_STYLE = {
1636
+ position: "absolute",
1637
+ top: 0,
1638
+ left: 0,
1639
+ display: "flex",
1640
+ alignItems: "center",
1641
+ justifyContent: "center",
1642
+ zIndex: 10
1643
+ };
1644
+ var TOOLTIP_STYLE = {
1645
+ position: "absolute",
1646
+ bottom: "12px",
1647
+ right: "12px",
1648
+ padding: "6px 10px",
1649
+ backgroundColor: "var(--glyph-interaction-tooltip-bg, rgba(26, 32, 53, 0.9))",
1650
+ color: "var(--glyph-interaction-tooltip-text, #f4f6fa)",
1651
+ borderRadius: "4px",
1652
+ fontSize: "12px",
1653
+ fontFamily: "Inter, system-ui, sans-serif",
1654
+ fontWeight: 500,
1655
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
1656
+ pointerEvents: "none"
1657
+ };
1658
+ var TOOLTIP_TEXT_STYLE = {
1659
+ display: "flex",
1660
+ alignItems: "center",
1661
+ gap: "4px"
1662
+ };
1663
+ var ACTIVATION_OVERLAY_STYLE = {
1664
+ backgroundColor: "var(--glyph-interaction-overlay-bg, rgba(244, 246, 250, 0.8))",
1665
+ cursor: "pointer",
1666
+ transition: "background-color 0.2s ease"
1667
+ };
1668
+ var ACTIVATION_TEXT_STYLE = {
1669
+ padding: "12px 20px",
1670
+ backgroundColor: "var(--glyph-interaction-tooltip-bg, rgba(26, 32, 53, 0.9))",
1671
+ color: "var(--glyph-interaction-tooltip-text, #f4f6fa)",
1672
+ borderRadius: "6px",
1673
+ fontSize: "14px",
1674
+ fontFamily: "Inter, system-ui, sans-serif",
1675
+ fontWeight: 500,
1676
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.2)"
1677
+ };
1678
+ var SR_ONLY_STYLE = {
1679
+ position: "absolute",
1680
+ width: "1px",
1681
+ height: "1px",
1682
+ padding: 0,
1683
+ margin: "-1px",
1684
+ overflow: "hidden",
1685
+ clip: "rect(0, 0, 0, 0)",
1686
+ whiteSpace: "nowrap",
1687
+ border: 0
1688
+ };
1689
+ function ZoomControls({ onZoomIn, onZoomOut, onReset }) {
1690
+ return /* @__PURE__ */ jsxs("div", { style: CONTROLS_CONTAINER_STYLE, children: [
1691
+ /* @__PURE__ */ jsx(
1692
+ "button",
1693
+ {
1694
+ onClick: onZoomIn,
1695
+ style: BUTTON_STYLE,
1696
+ "aria-label": "Zoom in",
1697
+ title: "Zoom in",
1698
+ type: "button",
1699
+ children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", children: [
1700
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "4", x2: "8", y2: "12", strokeWidth: "2" }),
1701
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "8", x2: "12", y2: "8", strokeWidth: "2" })
1702
+ ] })
1703
+ }
1704
+ ),
1705
+ /* @__PURE__ */ jsx(
1706
+ "button",
1707
+ {
1708
+ onClick: onZoomOut,
1709
+ style: BUTTON_STYLE,
1710
+ "aria-label": "Zoom out",
1711
+ title: "Zoom out",
1712
+ type: "button",
1713
+ 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" }) })
1714
+ }
1715
+ ),
1716
+ /* @__PURE__ */ jsx(
1717
+ "button",
1718
+ {
1719
+ onClick: onReset,
1720
+ style: BUTTON_STYLE,
1721
+ "aria-label": "Reset zoom",
1722
+ title: "Reset zoom",
1723
+ type: "button",
1724
+ 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" }) })
1725
+ }
1726
+ )
1727
+ ] });
1728
+ }
1729
+ var CONTROLS_CONTAINER_STYLE = {
1730
+ position: "absolute",
1731
+ top: "12px",
1732
+ right: "12px",
1733
+ display: "flex",
1734
+ flexDirection: "column",
1735
+ gap: "4px",
1736
+ zIndex: 10
1737
+ };
1738
+ var BUTTON_STYLE = {
1739
+ width: "32px",
1740
+ height: "32px",
1741
+ padding: "0",
1742
+ display: "flex",
1743
+ alignItems: "center",
1744
+ justifyContent: "center",
1745
+ backgroundColor: "var(--glyph-surface-raised, #f4f6fa)",
1746
+ border: "1px solid var(--glyph-border, #d0d8e4)",
1747
+ borderRadius: "4px",
1748
+ color: "var(--glyph-text, #1a2035)",
1749
+ cursor: "pointer",
1750
+ transition: "all 0.2s ease",
1751
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)"
1752
+ };
1428
1753
  var GROUP_PALETTE = [
1429
1754
  "#00d4aa",
1430
1755
  // cyan-green
@@ -1471,7 +1796,7 @@ function getThemeVar(container, varName, fallback) {
1471
1796
  return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
1472
1797
  }
1473
1798
  var ARROW_MARKER_ID = "glyph-graph-arrowhead";
1474
- function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, onNodeClick) {
1799
+ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, zoomBehavior, onNodeClick) {
1475
1800
  const svg = d32.select(svgElement);
1476
1801
  svg.selectAll("*").remove();
1477
1802
  const width = Math.max(layout.width, 200);
@@ -1484,9 +1809,6 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, o
1484
1809
  const nodeStrokeWidth = getThemeVar(container, "--glyph-node-stroke-width", "1.5");
1485
1810
  const nodeFillOpacity = getThemeVar(container, "--glyph-node-fill-opacity", "0.85");
1486
1811
  const root = svg.append("g").attr("class", "glyph-graph-root");
1487
- const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
1488
- root.attr("transform", event.transform.toString());
1489
- });
1490
1812
  svg.call(zoomBehavior);
1491
1813
  const navigableNodes = /* @__PURE__ */ new Set();
1492
1814
  const refByAnchor = /* @__PURE__ */ new Map();
@@ -1543,6 +1865,7 @@ function Graph({
1543
1865
  container
1544
1866
  }) {
1545
1867
  const svgRef = useRef(null);
1868
+ const rootRef = useRef(null);
1546
1869
  const groupIndex = useRef(/* @__PURE__ */ new Map());
1547
1870
  const layoutResult = useMemo(() => {
1548
1871
  const direction = resolveLayout(data);
@@ -1551,6 +1874,12 @@ function Graph({
1551
1874
  }
1552
1875
  return computeDagreLayout(data.nodes, data.edges, direction);
1553
1876
  }, [data]);
1877
+ const { overlayProps, zoomBehavior, zoomIn, zoomOut, resetZoom } = useZoomInteraction({
1878
+ svgRef,
1879
+ rootRef,
1880
+ interactionMode: data.interactionMode ?? "modifier-key",
1881
+ blockId: block.id
1882
+ });
1554
1883
  const handleNodeClick = useMemo(() => {
1555
1884
  if (!onInteraction) return void 0;
1556
1885
  return (nodeId, nodeLabel) => {
@@ -1571,27 +1900,36 @@ function Graph({
1571
1900
  groupIndex.current,
1572
1901
  outgoingRefs,
1573
1902
  onNavigate,
1903
+ zoomBehavior,
1574
1904
  handleNodeClick
1575
1905
  );
1576
- }, [layoutResult, outgoingRefs, onNavigate, handleNodeClick]);
1906
+ const rootElement = svgRef.current.querySelector(".glyph-graph-root");
1907
+ if (rootElement) {
1908
+ rootRef.current = rootElement;
1909
+ }
1910
+ }, [layoutResult, outgoingRefs, onNavigate, zoomBehavior, handleNodeClick]);
1577
1911
  const ariaLabel = `${data.type} graph with ${data.nodes.length} nodes and ${data.edges.length} edges`;
1578
1912
  return /* @__PURE__ */ jsxs("div", { className: "glyph-graph-container", children: [
1579
- /* @__PURE__ */ jsx(
1580
- "svg",
1581
- {
1582
- ref: svgRef,
1583
- role: "img",
1584
- "aria-label": ariaLabel,
1585
- width: "100%",
1586
- height: "100%",
1587
- style: {
1588
- minHeight: container.tier === "compact" ? 200 : 300,
1589
- maxHeight: container.tier === "compact" ? 500 : 700,
1590
- display: "block"
1913
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1914
+ /* @__PURE__ */ jsx(
1915
+ "svg",
1916
+ {
1917
+ ref: svgRef,
1918
+ role: "img",
1919
+ "aria-label": ariaLabel,
1920
+ width: "100%",
1921
+ height: "100%",
1922
+ style: {
1923
+ minHeight: container.tier === "compact" ? 200 : 300,
1924
+ maxHeight: container.tier === "compact" ? 500 : 700,
1925
+ display: "block"
1926
+ }
1591
1927
  }
1592
- }
1593
- ),
1594
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Graph data", style: SR_ONLY_STYLE, children: [
1928
+ ),
1929
+ overlayProps && /* @__PURE__ */ jsx(InteractionOverlay, { ...overlayProps }),
1930
+ /* @__PURE__ */ jsx(ZoomControls, { onZoomIn: zoomIn, onZoomOut: zoomOut, onReset: resetZoom })
1931
+ ] }),
1932
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Graph data", style: SR_ONLY_STYLE2, children: [
1595
1933
  /* @__PURE__ */ jsx("caption", { children: "Graph nodes and connections" }),
1596
1934
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1597
1935
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
@@ -1613,7 +1951,7 @@ function Graph({
1613
1951
  ] })
1614
1952
  ] });
1615
1953
  }
1616
- var SR_ONLY_STYLE = {
1954
+ var SR_ONLY_STYLE2 = {
1617
1955
  position: "absolute",
1618
1956
  width: "1px",
1619
1957
  height: "1px",
@@ -1737,16 +2075,13 @@ function drawCrowsFoot(g, x, y, angle, symbol) {
1737
2075
  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)");
1738
2076
  }
1739
2077
  }
1740
- function renderRelation(svgElement, layout) {
2078
+ function renderRelation(svgElement, layout, zoomBehavior) {
1741
2079
  const svg = d32.select(svgElement);
1742
2080
  svg.selectAll("*").remove();
1743
2081
  const width = Math.max(layout.width, 200);
1744
2082
  const height = Math.max(layout.height, 200);
1745
2083
  svg.attr("viewBox", `0 0 ${width} ${height}`);
1746
2084
  const root = svg.append("g").attr("class", "glyph-relation-root");
1747
- const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
1748
- root.attr("transform", event.transform.toString());
1749
- });
1750
2085
  svg.call(zoomBehavior);
1751
2086
  const entityMap = /* @__PURE__ */ new Map();
1752
2087
  for (const entity of layout.entities) {
@@ -1818,29 +2153,44 @@ function renderRelation(svgElement, layout) {
1818
2153
  }
1819
2154
  }
1820
2155
  }
1821
- function Relation({ data }) {
2156
+ function Relation({ data, block }) {
1822
2157
  const svgRef = useRef(null);
2158
+ const rootRef = useRef(null);
1823
2159
  const layoutResult = useMemo(() => {
1824
2160
  return computeRelationLayout(data);
1825
2161
  }, [data]);
2162
+ const { overlayProps, zoomBehavior, zoomIn, zoomOut, resetZoom } = useZoomInteraction({
2163
+ svgRef,
2164
+ rootRef,
2165
+ interactionMode: data.interactionMode ?? "modifier-key",
2166
+ blockId: block.id
2167
+ });
1826
2168
  useEffect(() => {
1827
2169
  if (!svgRef.current) return;
1828
- renderRelation(svgRef.current, layoutResult);
1829
- }, [layoutResult]);
2170
+ renderRelation(svgRef.current, layoutResult, zoomBehavior);
2171
+ const rootElement = svgRef.current.querySelector(".glyph-relation-root");
2172
+ if (rootElement) {
2173
+ rootRef.current = rootElement;
2174
+ }
2175
+ }, [layoutResult, zoomBehavior]);
1830
2176
  const ariaLabel = `Entity-relationship diagram with ${data.entities.length} entities and ${data.relationships.length} relationships`;
1831
2177
  return /* @__PURE__ */ jsxs("div", { className: "glyph-relation-container", children: [
1832
- /* @__PURE__ */ jsx(
1833
- "svg",
1834
- {
1835
- ref: svgRef,
1836
- role: "img",
1837
- "aria-label": ariaLabel,
1838
- width: "100%",
1839
- height: "100%",
1840
- style: { minHeight: 300, maxHeight: 700, display: "block" }
1841
- }
1842
- ),
1843
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Entity-relationship data", style: SR_ONLY_STYLE2, children: [
2178
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
2179
+ /* @__PURE__ */ jsx(
2180
+ "svg",
2181
+ {
2182
+ ref: svgRef,
2183
+ role: "img",
2184
+ "aria-label": ariaLabel,
2185
+ width: "100%",
2186
+ height: "100%",
2187
+ style: { minHeight: 300, maxHeight: 700, display: "block" }
2188
+ }
2189
+ ),
2190
+ overlayProps && /* @__PURE__ */ jsx(InteractionOverlay, { ...overlayProps }),
2191
+ /* @__PURE__ */ jsx(ZoomControls, { onZoomIn: zoomIn, onZoomOut: zoomOut, onReset: resetZoom })
2192
+ ] }),
2193
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Entity-relationship data", style: SR_ONLY_STYLE3, children: [
1844
2194
  /* @__PURE__ */ jsx("caption", { children: "Entities and relationships" }),
1845
2195
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1846
2196
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Entity" }),
@@ -1863,7 +2213,7 @@ function Relation({ data }) {
1863
2213
  ] })
1864
2214
  ] });
1865
2215
  }
1866
- var SR_ONLY_STYLE2 = {
2216
+ var SR_ONLY_STYLE3 = {
1867
2217
  position: "absolute",
1868
2218
  width: "1px",
1869
2219
  height: "1px",
@@ -1893,7 +2243,8 @@ function resolveSentiment(metric) {
1893
2243
  return "neutral";
1894
2244
  }
1895
2245
  function buildAriaLabel(metric) {
1896
- let label = `${metric.label}: ${metric.value}`;
2246
+ const labelText = typeof metric.label === "string" ? metric.label : "Metric";
2247
+ let label = `${labelText}: ${metric.value}`;
1897
2248
  if (metric.unit) label += ` ${metric.unit}`;
1898
2249
  if (metric.delta && metric.trend) {
1899
2250
  label += `, ${metric.trend} ${metric.delta}`;
@@ -1965,7 +2316,7 @@ function Kpi({ data, block, container }) {
1965
2316
  color: `var(--glyph-kpi-${sentiment}, inherit)`
1966
2317
  };
1967
2318
  return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": buildAriaLabel(metric), style: cardStyle2, children: [
1968
- /* @__PURE__ */ jsx("div", { style: labelStyle4, children: metric.label }),
2319
+ /* @__PURE__ */ jsx("div", { style: labelStyle4, children: /* @__PURE__ */ jsx(RichText, { content: metric.label }) }),
1969
2320
  /* @__PURE__ */ jsxs("div", { style: valueStyle, children: [
1970
2321
  metric.value,
1971
2322
  metric.unit && /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", fontWeight: 400, marginLeft: "0.25rem" }, children: metric.unit })
@@ -2079,7 +2430,7 @@ function Accordion({
2079
2430
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: "0.75rem", width: "1rem", flexShrink: 0 }, children: "\u25B8" }),
2080
2431
  section.title
2081
2432
  ] }),
2082
- /* @__PURE__ */ jsx("div", { style: contentStyle2, children: section.content })
2433
+ /* @__PURE__ */ jsx("div", { style: contentStyle2, children: /* @__PURE__ */ jsx(RichText, { content: section.content }) })
2083
2434
  ]
2084
2435
  },
2085
2436
  i
@@ -2098,6 +2449,7 @@ var accordionDefinition = {
2098
2449
  var YES_VALUES = /* @__PURE__ */ new Set(["yes", "true", "full"]);
2099
2450
  var NO_VALUES = /* @__PURE__ */ new Set(["no", "false", "none"]);
2100
2451
  function classifyValue(value) {
2452
+ if (typeof value !== "string") return "text";
2101
2453
  const lower = value.toLowerCase().trim();
2102
2454
  if (YES_VALUES.has(lower)) return "yes";
2103
2455
  if (NO_VALUES.has(lower)) return "no";
@@ -2135,7 +2487,7 @@ function renderValue(value) {
2135
2487
  }
2136
2488
  );
2137
2489
  default:
2138
- return /* @__PURE__ */ jsx("span", { children: value });
2490
+ return /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(RichText, { content: value }) });
2139
2491
  }
2140
2492
  }
2141
2493
  function Comparison({
@@ -2231,7 +2583,7 @@ function Comparison({
2231
2583
  fontSize: "0.75rem",
2232
2584
  color: "var(--glyph-text-muted, #6b7a94)"
2233
2585
  },
2234
- children: option.description
2586
+ children: /* @__PURE__ */ jsx(RichText, { content: option.description })
2235
2587
  }
2236
2588
  )
2237
2589
  ]
@@ -2252,8 +2604,8 @@ function Comparison({
2252
2604
  }
2253
2605
  ),
2254
2606
  options.map((_, colIndex) => {
2255
- const value = feature.values[colIndex] ?? "";
2256
- return /* @__PURE__ */ jsx("td", { style: cellStyle2(rowIndex), children: value ? renderValue(value) : null }, colIndex);
2607
+ const value = feature.values[colIndex];
2608
+ return /* @__PURE__ */ jsx("td", { style: cellStyle2(rowIndex), children: value !== void 0 && value !== "" ? renderValue(value) : null }, colIndex);
2257
2609
  })
2258
2610
  ] }, rowIndex)) })
2259
2611
  ] }) })
@@ -2497,7 +2849,7 @@ function renderNodeShape(nodeG, node, fillOpacity, strokeWidth) {
2497
2849
  }
2498
2850
  }
2499
2851
  }
2500
- function renderFlowchart(svgElement, layout) {
2852
+ function renderFlowchart(svgElement, layout, zoomBehavior) {
2501
2853
  const svg = d32.select(svgElement);
2502
2854
  svg.selectAll("*").remove();
2503
2855
  const width = Math.max(layout.width, 200);
@@ -2509,9 +2861,6 @@ function renderFlowchart(svgElement, layout) {
2509
2861
  const nodeStrokeWidth = getThemeVar2(container, "--glyph-node-stroke-width", "1.5");
2510
2862
  const nodeFillOpacity = getThemeVar2(container, "--glyph-node-fill-opacity", "0.85");
2511
2863
  const root = svg.append("g").attr("class", "glyph-flowchart-root");
2512
- const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
2513
- root.attr("transform", event.transform.toString());
2514
- });
2515
2864
  svg.call(zoomBehavior);
2516
2865
  const lineGen = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveBasis);
2517
2866
  const edgeGroup = root.append("g").attr("class", "glyph-flowchart-edges");
@@ -2532,13 +2881,28 @@ function renderFlowchart(svgElement, layout) {
2532
2881
  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);
2533
2882
  }
2534
2883
  }
2535
- function Flowchart({ data, container }) {
2884
+ function Flowchart({
2885
+ data,
2886
+ block,
2887
+ container
2888
+ }) {
2536
2889
  const svgRef = useRef(null);
2890
+ const rootRef = useRef(null);
2537
2891
  const layoutResult = useMemo(() => computeLayout(data.nodes, data.edges, data.direction), [data]);
2892
+ const { overlayProps, zoomBehavior, zoomIn, zoomOut, resetZoom } = useZoomInteraction({
2893
+ svgRef,
2894
+ rootRef,
2895
+ interactionMode: data.interactionMode ?? "modifier-key",
2896
+ blockId: block.id
2897
+ });
2538
2898
  useEffect(() => {
2539
2899
  if (!svgRef.current) return;
2540
- renderFlowchart(svgRef.current, layoutResult);
2541
- }, [layoutResult]);
2900
+ renderFlowchart(svgRef.current, layoutResult, zoomBehavior);
2901
+ const rootElement = svgRef.current.querySelector(".glyph-flowchart-root");
2902
+ if (rootElement) {
2903
+ rootRef.current = rootElement;
2904
+ }
2905
+ }, [layoutResult, zoomBehavior]);
2542
2906
  const nodeCount = data.nodes.length;
2543
2907
  const edgeCount = data.edges.length;
2544
2908
  const ariaLabel = data.title ? `${data.title}: flowchart with ${nodeCount} nodes and ${edgeCount} edges` : `Flowchart with ${nodeCount} nodes and ${edgeCount} edges`;
@@ -2556,22 +2920,26 @@ function Flowchart({ data, container }) {
2556
2920
  children: data.title
2557
2921
  }
2558
2922
  ),
2559
- /* @__PURE__ */ jsx(
2560
- "svg",
2561
- {
2562
- ref: svgRef,
2563
- role: "img",
2564
- "aria-label": ariaLabel,
2565
- width: "100%",
2566
- height: "100%",
2567
- style: {
2568
- minHeight: container.tier === "compact" ? 200 : 300,
2569
- maxHeight: container.tier === "compact" ? 500 : 700,
2570
- display: "block"
2923
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
2924
+ /* @__PURE__ */ jsx(
2925
+ "svg",
2926
+ {
2927
+ ref: svgRef,
2928
+ role: "img",
2929
+ "aria-label": ariaLabel,
2930
+ width: "100%",
2931
+ height: "100%",
2932
+ style: {
2933
+ minHeight: container.tier === "compact" ? 200 : 300,
2934
+ maxHeight: container.tier === "compact" ? 500 : 700,
2935
+ display: "block"
2936
+ }
2571
2937
  }
2572
- }
2573
- ),
2574
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Flowchart data", style: SR_ONLY_STYLE3, children: [
2938
+ ),
2939
+ overlayProps && /* @__PURE__ */ jsx(InteractionOverlay, { ...overlayProps }),
2940
+ /* @__PURE__ */ jsx(ZoomControls, { onZoomIn: zoomIn, onZoomOut: zoomOut, onReset: resetZoom })
2941
+ ] }),
2942
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Flowchart data", style: SR_ONLY_STYLE4, children: [
2575
2943
  /* @__PURE__ */ jsx("caption", { children: "Flowchart nodes and connections" }),
2576
2944
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
2577
2945
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
@@ -2593,7 +2961,7 @@ function Flowchart({ data, container }) {
2593
2961
  ] })
2594
2962
  ] });
2595
2963
  }
2596
- var SR_ONLY_STYLE3 = {
2964
+ var SR_ONLY_STYLE4 = {
2597
2965
  position: "absolute",
2598
2966
  width: "1px",
2599
2967
  height: "1px",
@@ -3215,7 +3583,7 @@ function Sequence({ data, container }) {
3215
3583
  ]
3216
3584
  }
3217
3585
  ),
3218
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Sequence data", style: SR_ONLY_STYLE4, children: [
3586
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Sequence data", style: SR_ONLY_STYLE5, children: [
3219
3587
  /* @__PURE__ */ jsx("caption", { children: "Sequence messages in order" }),
3220
3588
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3221
3589
  /* @__PURE__ */ jsx("th", { scope: "col", children: "#" }),
@@ -3238,7 +3606,7 @@ function Sequence({ data, container }) {
3238
3606
  ] })
3239
3607
  ] });
3240
3608
  }
3241
- var SR_ONLY_STYLE4 = {
3609
+ var SR_ONLY_STYLE5 = {
3242
3610
  position: "absolute",
3243
3611
  width: "1px",
3244
3612
  height: "1px",
@@ -3644,7 +4012,7 @@ function Architecture({
3644
4012
  }
3645
4013
  }
3646
4014
  ),
3647
- /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Architecture data", style: SR_ONLY_STYLE5, children: [
4015
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Architecture data", style: SR_ONLY_STYLE6, children: [
3648
4016
  /* @__PURE__ */ jsx("caption", { children: "Architecture nodes and connections" }),
3649
4017
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3650
4018
  /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
@@ -3688,7 +4056,7 @@ function countLeafNodes(children) {
3688
4056
  }
3689
4057
  return count;
3690
4058
  }
3691
- var SR_ONLY_STYLE5 = {
4059
+ var SR_ONLY_STYLE6 = {
3692
4060
  position: "absolute",
3693
4061
  width: "1px",
3694
4062
  height: "1px",
@@ -3876,7 +4244,7 @@ function layoutTree(data) {
3876
4244
  };
3877
4245
  }
3878
4246
  function renderAccessibleList(root, children) {
3879
- return /* @__PURE__ */ jsx("ul", { style: SR_ONLY_STYLE6, role: "list", "aria-label": "Mind map structure", children: /* @__PURE__ */ jsxs("li", { children: [
4247
+ return /* @__PURE__ */ jsx("ul", { style: SR_ONLY_STYLE7, role: "list", "aria-label": "Mind map structure", children: /* @__PURE__ */ jsxs("li", { children: [
3880
4248
  root,
3881
4249
  children.length > 0 && renderAccessibleChildren(children)
3882
4250
  ] }) });
@@ -4003,7 +4371,7 @@ function MindMap({ data, container }) {
4003
4371
  renderAccessibleList(data.root, data.children)
4004
4372
  ] });
4005
4373
  }
4006
- var SR_ONLY_STYLE6 = {
4374
+ var SR_ONLY_STYLE7 = {
4007
4375
  position: "absolute",
4008
4376
  width: "1px",
4009
4377
  height: "1px",
@@ -4204,7 +4572,8 @@ function isCorrect(question, selected) {
4204
4572
  }
4205
4573
  function renderMultipleChoice(question, qIndex, state, updateState, baseId) {
4206
4574
  const selected = typeof state.selected === "number" ? state.selected : null;
4207
- return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": question.question, children: question.options.map((option, oIndex) => {
4575
+ const ariaLabel = typeof question.question === "string" ? question.question : "Question";
4576
+ return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": ariaLabel, children: question.options.map((option, oIndex) => {
4208
4577
  const isSelected = selected === oIndex;
4209
4578
  const isCorrectOption = state.submitted && oIndex === question.answer;
4210
4579
  const isIncorrectSelection = state.submitted && isSelected && oIndex !== question.answer;
@@ -4230,7 +4599,7 @@ function renderMultipleChoice(question, qIndex, state, updateState, baseId) {
4230
4599
  "aria-checked": isSelected
4231
4600
  }
4232
4601
  ),
4233
- option
4602
+ /* @__PURE__ */ jsx(RichText, { content: option })
4234
4603
  ]
4235
4604
  },
4236
4605
  oIndex
@@ -4239,7 +4608,8 @@ function renderMultipleChoice(question, qIndex, state, updateState, baseId) {
4239
4608
  }
4240
4609
  function renderTrueFalse(question, qIndex, state, updateState, baseId) {
4241
4610
  const selected = typeof state.selected === "boolean" ? state.selected : null;
4242
- return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": question.question, children: [true, false].map((value) => {
4611
+ const ariaLabel = typeof question.question === "string" ? question.question : "Question";
4612
+ return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": ariaLabel, children: [true, false].map((value) => {
4243
4613
  const isSelected = selected === value;
4244
4614
  const isCorrectOption = state.submitted && value === question.answer;
4245
4615
  const isIncorrectSelection = state.submitted && isSelected && value !== question.answer;
@@ -4303,7 +4673,7 @@ function renderMultiSelect(question, qIndex, state, updateState) {
4303
4673
  "aria-checked": isSelected
4304
4674
  }
4305
4675
  ),
4306
- option
4676
+ /* @__PURE__ */ jsx(RichText, { content: option })
4307
4677
  ]
4308
4678
  },
4309
4679
  oIndex
@@ -4342,7 +4712,7 @@ function Quiz({ data, block, onInteraction }) {
4342
4712
  children: [
4343
4713
  /* @__PURE__ */ jsxs("div", { style: questionTextStyle, children: [
4344
4714
  questions.length > 1 ? `${String(qIndex + 1)}. ` : "",
4345
- question.question
4715
+ /* @__PURE__ */ jsx(RichText, { content: question.question })
4346
4716
  ] }),
4347
4717
  question.type === "multiple-choice" && renderMultipleChoice(question, qIndex, state, updateState, baseId),
4348
4718
  question.type === "true-false" && renderTrueFalse(question, qIndex, state, updateState, baseId),
@@ -4366,15 +4736,24 @@ function Quiz({ data, block, onInteraction }) {
4366
4736
  let selected;
4367
4737
  switch (question.type) {
4368
4738
  case "multiple-choice":
4369
- selected = typeof state.selected === "number" ? [question.options[state.selected] ?? String(state.selected)] : [];
4739
+ if (typeof state.selected === "number") {
4740
+ const opt = question.options[state.selected];
4741
+ selected = [typeof opt === "string" ? opt : String(state.selected)];
4742
+ } else {
4743
+ selected = [];
4744
+ }
4370
4745
  break;
4371
4746
  case "true-false":
4372
4747
  selected = typeof state.selected === "boolean" ? [state.selected ? "True" : "False"] : [];
4373
4748
  break;
4374
4749
  case "multi-select":
4375
- selected = Array.isArray(state.selected) ? state.selected.map((idx) => question.options[idx] ?? String(idx)) : [];
4750
+ selected = Array.isArray(state.selected) ? state.selected.map((idx) => {
4751
+ const opt = question.options[idx];
4752
+ return typeof opt === "string" ? opt : String(idx);
4753
+ }) : [];
4376
4754
  break;
4377
4755
  }
4756
+ const questionText = typeof question.question === "string" ? question.question : "Question";
4378
4757
  onInteraction({
4379
4758
  kind: "quiz-submit",
4380
4759
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4382,7 +4761,7 @@ function Quiz({ data, block, onInteraction }) {
4382
4761
  blockType: block.type,
4383
4762
  payload: {
4384
4763
  questionIndex: qIndex,
4385
- question: question.question,
4764
+ question: questionText,
4386
4765
  selected,
4387
4766
  correct: correct2,
4388
4767
  score: { correct: newScore, total: questions.length }
@@ -4395,7 +4774,7 @@ function Quiz({ data, block, onInteraction }) {
4395
4774
  ),
4396
4775
  /* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
4397
4776
  state.submitted && /* @__PURE__ */ jsx("div", { style: feedbackStyle(correct), children: correct ? "Correct!" : "Incorrect" }),
4398
- state.submitted && question.explanation && /* @__PURE__ */ jsx("div", { style: explanationStyle, children: question.explanation })
4777
+ state.submitted && question.explanation && /* @__PURE__ */ jsx("div", { style: explanationStyle, children: /* @__PURE__ */ jsx(RichText, { content: question.explanation }) })
4399
4778
  ] })
4400
4779
  ]
4401
4780
  },
@@ -4533,8 +4912,8 @@ function Card({ data, block, container }) {
4533
4912
  /* @__PURE__ */ jsxs("div", { style: cardBodyStyle, children: [
4534
4913
  card.icon && /* @__PURE__ */ jsx("div", { style: iconStyle, children: card.icon }),
4535
4914
  /* @__PURE__ */ jsx("h3", { style: titleStyle2, children: card.title }),
4536
- card.subtitle && /* @__PURE__ */ jsx("div", { style: subtitleStyle, children: card.subtitle }),
4537
- card.body && /* @__PURE__ */ jsx("div", { style: bodyStyle3, children: card.body }),
4915
+ card.subtitle && /* @__PURE__ */ jsx("div", { style: subtitleStyle, children: /* @__PURE__ */ jsx(RichText, { content: card.subtitle }) }),
4916
+ card.body && /* @__PURE__ */ jsx("div", { style: bodyStyle3, children: /* @__PURE__ */ jsx(RichText, { content: card.body }) }),
4538
4917
  card.actions && card.actions.length > 0 && /* @__PURE__ */ jsx("div", { style: actionsStyle, children: card.actions.map((action, j) => /* @__PURE__ */ jsx(
4539
4918
  "a",
4540
4919
  {
@@ -4641,7 +5020,7 @@ function renderStatGroup(items, keyPrefix) {
4641
5020
  return /* @__PURE__ */ jsx("div", { style: rowStyle, "data-group": "stat", children: items.map((item, i) => /* @__PURE__ */ jsxs("div", { style: statStyle, children: [
4642
5021
  /* @__PURE__ */ jsx("div", { style: valueStyle, children: item.value }),
4643
5022
  /* @__PURE__ */ jsx("div", { style: labelStyle4, children: item.label }),
4644
- item.description && /* @__PURE__ */ jsx("div", { style: descStyle, children: item.description })
5023
+ item.description && /* @__PURE__ */ jsx("div", { style: descStyle, children: /* @__PURE__ */ jsx(RichText, { content: item.description }) })
4645
5024
  ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4646
5025
  }
4647
5026
  function renderProgressGroup(items, keyPrefix, colorOffset) {
@@ -4734,7 +5113,7 @@ function renderFactGroup(items, keyPrefix) {
4734
5113
  children: item.icon
4735
5114
  }
4736
5115
  ),
4737
- item.text
5116
+ /* @__PURE__ */ jsx(RichText, { content: item.text })
4738
5117
  ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4739
5118
  }
4740
5119
  function renderTextGroup(items, keyPrefix) {
@@ -5216,17 +5595,21 @@ function Poll({ data, block, onInteraction }) {
5216
5595
  blockId: block.id,
5217
5596
  blockType: block.type,
5218
5597
  payload: {
5219
- selectedOptions: selected.map((i) => options[i] ?? String(i)),
5598
+ selectedOptions: selected.map((i) => {
5599
+ const opt = options[i];
5600
+ return typeof opt === "string" ? opt : String(i);
5601
+ }),
5220
5602
  selectedIndices: [...selected]
5221
5603
  }
5222
5604
  });
5223
5605
  }
5224
5606
  };
5225
5607
  const totalVotes = votes.reduce((a, b) => a + b, 0);
5608
+ const questionAriaLabel = typeof question === "string" ? question : "Poll question";
5226
5609
  return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Poll", style: containerStyle3, children: [
5227
5610
  title && /* @__PURE__ */ jsx("div", { style: headerStyle2, children: title }),
5228
- /* @__PURE__ */ jsx("div", { style: questionStyle, children: question }),
5229
- /* @__PURE__ */ jsx("div", { role: "group", "aria-label": question, style: optionsStyle, children: options.map((option, index) => /* @__PURE__ */ jsxs("label", { style: optionLabelStyle2(selected.includes(index)), children: [
5611
+ /* @__PURE__ */ jsx("div", { style: questionStyle, children: /* @__PURE__ */ jsx(RichText, { content: question }) }),
5612
+ /* @__PURE__ */ jsx("div", { role: "group", "aria-label": questionAriaLabel, style: optionsStyle, children: options.map((option, index) => /* @__PURE__ */ jsxs("label", { style: optionLabelStyle2(selected.includes(index)), children: [
5230
5613
  /* @__PURE__ */ jsx(
5231
5614
  "input",
5232
5615
  {
@@ -5238,7 +5621,7 @@ function Poll({ data, block, onInteraction }) {
5238
5621
  "aria-checked": selected.includes(index)
5239
5622
  }
5240
5623
  ),
5241
- option
5624
+ /* @__PURE__ */ jsx(RichText, { content: option })
5242
5625
  ] }, index)) }),
5243
5626
  !hasVoted && /* @__PURE__ */ jsx(
5244
5627
  "button",
@@ -5257,9 +5640,10 @@ function Poll({ data, block, onInteraction }) {
5257
5640
  showResults && hasVoted && /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", style: resultsStyle, children: options.map((option, index) => {
5258
5641
  const count = votes[index] ?? 0;
5259
5642
  const percentage = totalVotes > 0 ? count / totalVotes * 100 : 0;
5643
+ const optionLabel = typeof option === "string" ? option : "Option";
5260
5644
  return /* @__PURE__ */ jsxs("div", { style: resultRowStyle, children: [
5261
5645
  /* @__PURE__ */ jsxs("div", { style: resultLabelStyle, children: [
5262
- /* @__PURE__ */ jsx("span", { children: option }),
5646
+ /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(RichText, { content: option }) }),
5263
5647
  /* @__PURE__ */ jsxs("span", { children: [
5264
5648
  String(count),
5265
5649
  " vote",
@@ -5277,7 +5661,7 @@ function Poll({ data, block, onInteraction }) {
5277
5661
  "aria-valuenow": percentage,
5278
5662
  "aria-valuemin": 0,
5279
5663
  "aria-valuemax": 100,
5280
- "aria-label": `${option}: ${String(Math.round(percentage))}%`,
5664
+ "aria-label": `${optionLabel}: ${String(Math.round(percentage))}%`,
5281
5665
  children: /* @__PURE__ */ jsx("div", { style: barFillStyle(percentage) })
5282
5666
  }
5283
5667
  )
@@ -5379,6 +5763,8 @@ function Rating({
5379
5763
  newRatings[itemIndex] = value;
5380
5764
  setRatings(newRatings);
5381
5765
  if (onInteraction) {
5766
+ const item = items[itemIndex];
5767
+ const itemLabel = item ? typeof item.label === "string" ? item.label : "Item" : "";
5382
5768
  onInteraction({
5383
5769
  kind: "rating-change",
5384
5770
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5386,10 +5772,10 @@ function Rating({
5386
5772
  blockType: block.type,
5387
5773
  payload: {
5388
5774
  itemIndex,
5389
- itemLabel: items[itemIndex]?.label ?? "",
5775
+ itemLabel,
5390
5776
  value,
5391
- allRatings: items.map((item, i) => ({
5392
- label: item.label,
5777
+ allRatings: items.map((item2, i) => ({
5778
+ label: typeof item2.label === "string" ? item2.label : "Item",
5393
5779
  value: i === itemIndex ? value : newRatings[i] ?? null
5394
5780
  }))
5395
5781
  }
@@ -5401,10 +5787,11 @@ function Rating({
5401
5787
  items.map((item, itemIndex) => {
5402
5788
  const currentRating = ratings[itemIndex] ?? null;
5403
5789
  const isLast = itemIndex === items.length - 1;
5790
+ const itemLabelText = typeof item.label === "string" ? item.label : "Item";
5404
5791
  return /* @__PURE__ */ jsxs("div", { style: itemStyle2(isLast), children: [
5405
- /* @__PURE__ */ jsx("div", { style: itemLabelStyle, children: item.label }),
5406
- item.description && /* @__PURE__ */ jsx("div", { style: itemDescriptionStyle, children: item.description }),
5407
- /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": `Rate ${item.label}`, style: starsContainerStyle, children: Array.from({ length: scale }, (_, starIndex) => {
5792
+ /* @__PURE__ */ jsx("div", { style: itemLabelStyle, children: /* @__PURE__ */ jsx(RichText, { content: item.label }) }),
5793
+ item.description && /* @__PURE__ */ jsx("div", { style: itemDescriptionStyle, children: /* @__PURE__ */ jsx(RichText, { content: item.description }) }),
5794
+ /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": `Rate ${itemLabelText}`, style: starsContainerStyle, children: Array.from({ length: scale }, (_, starIndex) => {
5408
5795
  const value = starIndex + 1;
5409
5796
  const isHovered = hoveredStar !== null && hoveredStar.itemIndex === itemIndex && value <= hoveredStar.value;
5410
5797
  const isFilled = currentRating !== null && value <= currentRating;
@@ -5458,7 +5845,7 @@ function Rating({
5458
5845
  whiteSpace: "nowrap",
5459
5846
  border: 0
5460
5847
  },
5461
- children: currentRating !== null && `${item.label} rated ${String(currentRating)} out of ${String(scale)}`
5848
+ children: currentRating !== null && `${itemLabelText} rated ${String(currentRating)} out of ${String(scale)}`
5462
5849
  }
5463
5850
  )
5464
5851
  ] }, itemIndex);
@@ -5565,12 +5952,12 @@ function Ranker({
5565
5952
  payload: {
5566
5953
  orderedItems: newItems.map((item, i) => ({
5567
5954
  id: item.id,
5568
- label: item.label,
5955
+ label: typeof item.label === "string" ? item.label : "Item",
5569
5956
  rank: i + 1
5570
5957
  })),
5571
5958
  movedItem: {
5572
5959
  id: moved.id,
5573
- label: moved.label,
5960
+ label: typeof moved.label === "string" ? moved.label : "Item",
5574
5961
  fromRank: fromIndex + 1,
5575
5962
  toRank: toIndex + 1
5576
5963
  }
@@ -5608,26 +5995,29 @@ function Ranker({
5608
5995
  };
5609
5996
  return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Ranker", style: containerStyle5, children: [
5610
5997
  title && /* @__PURE__ */ jsx("div", { style: headerStyle4, children: title }),
5611
- /* @__PURE__ */ jsx("ul", { role: "list", "aria-label": title ?? "Rank items", style: listStyle, children: items.map((item, index) => /* @__PURE__ */ jsxs(
5612
- "li",
5613
- {
5614
- role: "listitem",
5615
- "aria-grabbed": grabbedIndex === index,
5616
- "aria-label": `${item.label}, rank ${String(index + 1)}`,
5617
- tabIndex: 0,
5618
- style: itemStyle3(false, grabbedIndex === index),
5619
- onKeyDown: (e) => handleKeyDown(e, index),
5620
- children: [
5621
- /* @__PURE__ */ jsx("span", { style: gripStyle, "aria-hidden": "true", children: "\u283F" }),
5622
- /* @__PURE__ */ jsx("span", { style: rankBadgeStyle, children: String(index + 1) }),
5623
- /* @__PURE__ */ jsxs("div", { style: itemContentStyle, children: [
5624
- /* @__PURE__ */ jsx("div", { style: itemLabelStyle2, children: item.label }),
5625
- item.description && /* @__PURE__ */ jsx("div", { style: itemDescriptionStyle2, children: item.description })
5626
- ] })
5627
- ]
5628
- },
5629
- item.id
5630
- )) }),
5998
+ /* @__PURE__ */ jsx("ul", { role: "list", "aria-label": title ?? "Rank items", style: listStyle, children: items.map((item, index) => {
5999
+ const itemLabelText = typeof item.label === "string" ? item.label : "Item";
6000
+ return /* @__PURE__ */ jsxs(
6001
+ "li",
6002
+ {
6003
+ role: "listitem",
6004
+ "aria-grabbed": grabbedIndex === index,
6005
+ "aria-label": `${itemLabelText}, rank ${String(index + 1)}`,
6006
+ tabIndex: 0,
6007
+ style: itemStyle3(false, grabbedIndex === index),
6008
+ onKeyDown: (e) => handleKeyDown(e, index),
6009
+ children: [
6010
+ /* @__PURE__ */ jsx("span", { style: gripStyle, "aria-hidden": "true", children: "\u283F" }),
6011
+ /* @__PURE__ */ jsx("span", { style: rankBadgeStyle, children: String(index + 1) }),
6012
+ /* @__PURE__ */ jsxs("div", { style: itemContentStyle, children: [
6013
+ /* @__PURE__ */ jsx("div", { style: itemLabelStyle2, children: /* @__PURE__ */ jsx(RichText, { content: item.label }) }),
6014
+ item.description && /* @__PURE__ */ jsx("div", { style: itemDescriptionStyle2, children: item.description })
6015
+ ] })
6016
+ ]
6017
+ },
6018
+ item.id
6019
+ );
6020
+ }) }),
5631
6021
  /* @__PURE__ */ jsx(
5632
6022
  "div",
5633
6023
  {
@@ -5643,7 +6033,7 @@ function Ranker({
5643
6033
  whiteSpace: "nowrap",
5644
6034
  border: 0
5645
6035
  },
5646
- children: grabbedIndex !== null && `${items[grabbedIndex]?.label ?? ""} grabbed, rank ${String(grabbedIndex + 1)} of ${String(items.length)}. Use arrow keys to move.`
6036
+ children: grabbedIndex !== null && items[grabbedIndex] !== void 0 ? `${typeof items[grabbedIndex].label === "string" ? items[grabbedIndex].label : "Item"} grabbed, rank ${String(grabbedIndex + 1)} of ${String(items.length)}. Use arrow keys to move.` : ""
5647
6037
  }
5648
6038
  )
5649
6039
  ] });
@@ -5729,11 +6119,11 @@ function Slider({
5729
6119
  blockType: block.type,
5730
6120
  payload: {
5731
6121
  parameterId: param.id,
5732
- parameterLabel: param.label,
6122
+ parameterLabel: typeof param.label === "string" ? param.label : "Parameter",
5733
6123
  value: newValue,
5734
6124
  allValues: parameters.map((p, i) => ({
5735
6125
  id: p.id,
5736
- label: p.label,
6126
+ label: typeof p.label === "string" ? p.label : "Parameter",
5737
6127
  value: i === paramIndex ? newValue : newValues[i] ?? 0
5738
6128
  }))
5739
6129
  }
@@ -5753,7 +6143,7 @@ function Slider({
5753
6143
  const isLast = index === parameters.length - 1;
5754
6144
  return /* @__PURE__ */ jsxs("div", { style: parameterStyle(isLast), children: [
5755
6145
  /* @__PURE__ */ jsxs("div", { style: parameterHeaderStyle, children: [
5756
- /* @__PURE__ */ jsx("label", { htmlFor: `${baseId}-${param.id}`, style: parameterLabelStyle, children: param.label }),
6146
+ /* @__PURE__ */ jsx("label", { htmlFor: `${baseId}-${param.id}`, style: parameterLabelStyle, children: /* @__PURE__ */ jsx(RichText, { content: param.label }) }),
5757
6147
  /* @__PURE__ */ jsx("span", { style: parameterValueStyle, "aria-live": "polite", children: formatValue(currentValue, param.unit) })
5758
6148
  ] }),
5759
6149
  /* @__PURE__ */ jsx(
@@ -5866,7 +6256,8 @@ function computeWeightedTotals(rows, columns, values) {
5866
6256
  const weight = col.weight ?? 1;
5867
6257
  total += score * weight;
5868
6258
  }
5869
- return { rowId: row.id, rowLabel: row.label, total: Math.round(total * 100) / 100 };
6259
+ const rowLabel = typeof row.label === "string" ? row.label : "Row";
6260
+ return { rowId: row.id, rowLabel, total: Math.round(total * 100) / 100 };
5870
6261
  });
5871
6262
  }
5872
6263
  function Matrix({
@@ -5909,9 +6300,9 @@ function Matrix({
5909
6300
  blockType: block.type,
5910
6301
  payload: {
5911
6302
  rowId,
5912
- rowLabel: row.label,
6303
+ rowLabel: typeof row.label === "string" ? row.label : "Row",
5913
6304
  columnId,
5914
- columnLabel: col.label,
6305
+ columnLabel: typeof col.label === "string" ? col.label : "Column",
5915
6306
  value: clamped,
5916
6307
  allValues: payloadValues,
5917
6308
  weightedTotals: computeWeightedTotals(rows, columns, newValues)
@@ -5930,7 +6321,7 @@ function Matrix({
5930
6321
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
5931
6322
  /* @__PURE__ */ jsx("th", { style: thStyle }),
5932
6323
  columns.map((col) => /* @__PURE__ */ jsxs("th", { style: thStyle, children: [
5933
- col.label,
6324
+ /* @__PURE__ */ jsx(RichText, { content: col.label }),
5934
6325
  (col.weight ?? 1) !== 1 && /* @__PURE__ */ jsxs("div", { style: weightStyle, children: [
5935
6326
  "\xD7",
5936
6327
  String(col.weight)
@@ -5940,10 +6331,12 @@ function Matrix({
5940
6331
  ] }) }),
5941
6332
  /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => {
5942
6333
  const rowTotal = totals.find((t) => t.rowId === row.id)?.total ?? 0;
6334
+ const rowLabelText = typeof row.label === "string" ? row.label : "Row";
5943
6335
  return /* @__PURE__ */ jsxs("tr", { children: [
5944
- /* @__PURE__ */ jsx("th", { scope: "row", style: rowHeaderStyle, children: row.label }),
6336
+ /* @__PURE__ */ jsx("th", { scope: "row", style: rowHeaderStyle, children: /* @__PURE__ */ jsx(RichText, { content: row.label }) }),
5945
6337
  columns.map((col) => {
5946
6338
  const cellValue = values[row.id]?.[col.id] ?? 0;
6339
+ const colLabelText = typeof col.label === "string" ? col.label : "Column";
5947
6340
  return /* @__PURE__ */ jsx("td", { style: cellStyle, children: /* @__PURE__ */ jsx(
5948
6341
  "input",
5949
6342
  {
@@ -5952,7 +6345,7 @@ function Matrix({
5952
6345
  max: scale,
5953
6346
  value: cellValue,
5954
6347
  onChange: (e) => handleChange(row.id, col.id, Number(e.target.value)),
5955
- "aria-label": `Score for ${row.label} on ${col.label}`,
6348
+ "aria-label": `Score for ${rowLabelText} on ${colLabelText}`,
5956
6349
  style: inputStyle
5957
6350
  }
5958
6351
  ) }, col.id);
@@ -6246,7 +6639,7 @@ function Form({ data, block, onInteraction }) {
6246
6639
  };
6247
6640
  return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Form", style: containerStyle8, children: [
6248
6641
  title && /* @__PURE__ */ jsx("div", { style: headerStyle7, children: title }),
6249
- description && /* @__PURE__ */ jsx("div", { style: descriptionStyle, children: description }),
6642
+ description && /* @__PURE__ */ jsx("div", { style: descriptionStyle, children: /* @__PURE__ */ jsx(RichText, { content: description }) }),
6250
6643
  /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: formStyle, noValidate: true, children: [
6251
6644
  fields.map(
6252
6645
  (field) => renderField({ field, baseId, values, validation, submitted, updateValue })
@@ -6633,6 +7026,9 @@ var annotationNoteStyle = {
6633
7026
  marginTop: "0.125rem"
6634
7027
  };
6635
7028
  function computeSegments(text, annotations) {
7029
+ if (typeof text !== "string") {
7030
+ return [{ text: "", start: 0, annotation: null }];
7031
+ }
6636
7032
  if (annotations.length === 0) {
6637
7033
  return [{ text, start: 0, annotation: null }];
6638
7034
  }
@@ -6712,7 +7108,7 @@ function Annotate({
6712
7108
  allAnnotations: newAnnotations.map((a) => ({
6713
7109
  start: a.start,
6714
7110
  end: a.end,
6715
- text: text.slice(a.start, a.end),
7111
+ text: typeof text === "string" ? text.slice(a.start, a.end) : "",
6716
7112
  label: a.label
6717
7113
  }))
6718
7114
  }
@@ -6738,7 +7134,7 @@ function Annotate({
6738
7134
  title && /* @__PURE__ */ jsx("div", { style: headerStyle9, children: title }),
6739
7135
  /* @__PURE__ */ jsxs("div", { style: bodyStyle2, children: [
6740
7136
  /* @__PURE__ */ jsxs("div", { ref: textRef, role: "document", style: textPaneStyle, onMouseUp: handleMouseUp, children: [
6741
- segments.map((seg, i) => {
7137
+ typeof text === "string" ? segments.map((seg, i) => {
6742
7138
  if (seg.annotation) {
6743
7139
  const color3 = labelColorMap.get(seg.annotation.label) ?? "#888";
6744
7140
  return /* @__PURE__ */ jsx(
@@ -6756,7 +7152,7 @@ function Annotate({
6756
7152
  );
6757
7153
  }
6758
7154
  return /* @__PURE__ */ jsx("span", { children: seg.text }, i);
6759
- }),
7155
+ }) : /* @__PURE__ */ jsx(RichText, { content: text }),
6760
7156
  pickerPos && /* @__PURE__ */ jsx(
6761
7157
  "div",
6762
7158
  {
@@ -6795,12 +7191,13 @@ function Annotate({
6795
7191
  /* @__PURE__ */ jsxs("div", { role: "list", children: [
6796
7192
  annotations.map((ann, i) => {
6797
7193
  const color3 = labelColorMap.get(ann.label) ?? "#888";
7194
+ const annotatedText = typeof text === "string" ? text.slice(ann.start, ann.end) : "";
6798
7195
  return /* @__PURE__ */ jsxs("div", { role: "listitem", style: annotationItemStyle(color3), children: [
6799
7196
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.375rem" }, children: [
6800
7197
  /* @__PURE__ */ jsx("span", { style: colorDotStyle(color3) }),
6801
7198
  /* @__PURE__ */ jsx("strong", { style: { fontSize: "0.75rem" }, children: ann.label })
6802
7199
  ] }),
6803
- /* @__PURE__ */ jsx("div", { style: annotationTextStyle, children: text.slice(ann.start, ann.end) }),
7200
+ /* @__PURE__ */ jsx("div", { style: annotationTextStyle, children: annotatedText }),
6804
7201
  ann.note && /* @__PURE__ */ jsx("div", { style: annotationNoteStyle, children: ann.note })
6805
7202
  ] }, i);
6806
7203
  }),