@icen.ai/ui 0.8.1 → 0.9.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.
Files changed (141) hide show
  1. package/AGENTS.md +49 -6
  2. package/README.md +12 -9
  3. package/dist/behaviors/ai-branch.d.mts +19 -0
  4. package/dist/behaviors/ai-branch.mjs +217 -0
  5. package/dist/behaviors/ai-chat.d.mts +8 -6
  6. package/dist/behaviors/ai-chat.mjs +68 -8
  7. package/dist/behaviors/ai-composer.mjs +108 -20
  8. package/dist/behaviors/ai-feedback.d.mts +26 -0
  9. package/dist/behaviors/ai-feedback.mjs +303 -0
  10. package/dist/behaviors/ai-panel.d.mts +15 -1
  11. package/dist/behaviors/ai-panel.mjs +26 -0
  12. package/dist/behaviors/ai-threads.d.mts +38 -0
  13. package/dist/behaviors/ai-threads.mjs +319 -0
  14. package/dist/behaviors/ai-tool.mjs +5 -0
  15. package/dist/behaviors/ai-tools.mjs +5 -0
  16. package/dist/behaviors/charts.d.mts +103 -1
  17. package/dist/behaviors/charts.mjs +795 -0
  18. package/dist/behaviors/events.d.mts +193 -0
  19. package/dist/behaviors/kb-access.d.mts +34 -0
  20. package/dist/behaviors/kb-access.mjs +569 -0
  21. package/dist/behaviors/kb-acl.d.mts +50 -0
  22. package/dist/behaviors/kb-acl.mjs +405 -0
  23. package/dist/behaviors/kb-answer.d.mts +38 -0
  24. package/dist/behaviors/kb-answer.mjs +2055 -0
  25. package/dist/behaviors/kb-audit.d.mts +23 -0
  26. package/dist/behaviors/kb-audit.mjs +375 -0
  27. package/dist/behaviors/kb-canvas.d.mts +44 -0
  28. package/dist/behaviors/kb-canvas.mjs +589 -0
  29. package/dist/behaviors/kb-chain.d.mts +15 -0
  30. package/dist/behaviors/kb-chain.mjs +270 -0
  31. package/dist/behaviors/kb-checkpoint.d.mts +16 -0
  32. package/dist/behaviors/kb-checkpoint.mjs +342 -0
  33. package/dist/behaviors/kb-chunks.d.mts +31 -0
  34. package/dist/behaviors/kb-chunks.mjs +475 -0
  35. package/dist/behaviors/kb-citation.d.mts +38 -0
  36. package/dist/behaviors/kb-citation.mjs +677 -0
  37. package/dist/behaviors/kb-clarify.d.mts +20 -0
  38. package/dist/behaviors/kb-clarify.mjs +248 -0
  39. package/dist/behaviors/kb-conflict.d.mts +22 -0
  40. package/dist/behaviors/kb-conflict.mjs +284 -0
  41. package/dist/behaviors/kb-connector.d.mts +17 -0
  42. package/dist/behaviors/kb-connector.mjs +375 -0
  43. package/dist/behaviors/kb-core.d.mts +529 -0
  44. package/dist/behaviors/kb-core.mjs +890 -0
  45. package/dist/behaviors/kb-eval.d.mts +22 -0
  46. package/dist/behaviors/kb-eval.mjs +408 -0
  47. package/dist/behaviors/kb-explain.d.mts +11 -0
  48. package/dist/behaviors/kb-explain.mjs +243 -0
  49. package/dist/behaviors/kb-filter.d.mts +31 -0
  50. package/dist/behaviors/kb-filter.mjs +587 -0
  51. package/dist/behaviors/kb-gap.d.mts +27 -0
  52. package/dist/behaviors/kb-gap.mjs +273 -0
  53. package/dist/behaviors/kb-hittest.d.mts +34 -0
  54. package/dist/behaviors/kb-hittest.mjs +409 -0
  55. package/dist/behaviors/kb-metadata.d.mts +29 -0
  56. package/dist/behaviors/kb-metadata.mjs +326 -0
  57. package/dist/behaviors/kb-passage.d.mts +41 -0
  58. package/dist/behaviors/kb-passage.mjs +529 -0
  59. package/dist/behaviors/kb-pipeline.d.mts +18 -0
  60. package/dist/behaviors/kb-pipeline.mjs +381 -0
  61. package/dist/behaviors/kb-qa.d.mts +26 -0
  62. package/dist/behaviors/kb-qa.mjs +332 -0
  63. package/dist/behaviors/kb-rerank.d.mts +20 -0
  64. package/dist/behaviors/kb-rerank.mjs +315 -0
  65. package/dist/behaviors/kb-retrieval.d.mts +49 -0
  66. package/dist/behaviors/kb-retrieval.mjs +746 -0
  67. package/dist/behaviors/kb-review.d.mts +44 -0
  68. package/dist/behaviors/kb-review.mjs +451 -0
  69. package/dist/behaviors/kb-sandbox.d.mts +33 -0
  70. package/dist/behaviors/kb-sandbox.mjs +325 -0
  71. package/dist/behaviors/kb-segment.d.mts +32 -0
  72. package/dist/behaviors/kb-segment.mjs +499 -0
  73. package/dist/behaviors/kb-sources.d.mts +27 -0
  74. package/dist/behaviors/kb-sources.mjs +373 -0
  75. package/dist/behaviors/kb-sql.d.mts +43 -0
  76. package/dist/behaviors/kb-sql.mjs +948 -0
  77. package/dist/behaviors/kb-trace.d.mts +16 -0
  78. package/dist/behaviors/kb-trace.mjs +414 -0
  79. package/dist/behaviors/kb-visibility.d.mts +44 -0
  80. package/dist/behaviors/kb-visibility.mjs +403 -0
  81. package/dist/behaviors/kb-who-can.d.mts +32 -0
  82. package/dist/behaviors/kb-who-can.mjs +379 -0
  83. package/dist/components/ai-branch.mjs +3 -0
  84. package/dist/components/ai-chat.css +295 -0
  85. package/dist/components/ai-feedback.mjs +3 -0
  86. package/dist/components/ai-panel.css +217 -0
  87. package/dist/components/ai-threads.mjs +3 -0
  88. package/dist/components/chart-graph.css +57 -0
  89. package/dist/components/chart-graph.mjs +4 -0
  90. package/dist/components/chart-map.mjs +3 -0
  91. package/dist/components/chart-scatter.mjs +1 -1
  92. package/dist/components/charts.css +32 -0
  93. package/dist/components/charts.mjs +1 -0
  94. package/dist/components/kb-access.mjs +4 -0
  95. package/dist/components/kb-acl.mjs +4 -0
  96. package/dist/components/kb-agent.css +481 -0
  97. package/dist/components/kb-answer.mjs +4 -0
  98. package/dist/components/kb-audit.mjs +4 -0
  99. package/dist/components/kb-canvas.mjs +4 -0
  100. package/dist/components/kb-chain.mjs +4 -0
  101. package/dist/components/kb-checkpoint.mjs +4 -0
  102. package/dist/components/kb-chunks.mjs +4 -0
  103. package/dist/components/kb-citation.mjs +4 -0
  104. package/dist/components/kb-clarify.mjs +4 -0
  105. package/dist/components/kb-conflict.mjs +4 -0
  106. package/dist/components/kb-connector.mjs +4 -0
  107. package/dist/components/kb-data.css +649 -0
  108. package/dist/components/kb-eval.mjs +4 -0
  109. package/dist/components/kb-explain.mjs +4 -0
  110. package/dist/components/kb-filter.mjs +4 -0
  111. package/dist/components/kb-gap.mjs +4 -0
  112. package/dist/components/kb-ground.css +566 -0
  113. package/dist/components/kb-hittest.mjs +4 -0
  114. package/dist/components/kb-ingest.css +898 -0
  115. package/dist/components/kb-metadata.mjs +4 -0
  116. package/dist/components/kb-ops.css +548 -0
  117. package/dist/components/kb-passage.mjs +4 -0
  118. package/dist/components/kb-perm.css +444 -0
  119. package/dist/components/kb-pipeline.mjs +4 -0
  120. package/dist/components/kb-qa.mjs +4 -0
  121. package/dist/components/kb-rerank.mjs +4 -0
  122. package/dist/components/kb-retrieval.mjs +4 -0
  123. package/dist/components/kb-review.mjs +4 -0
  124. package/dist/components/kb-sandbox.mjs +4 -0
  125. package/dist/components/kb-search.css +940 -0
  126. package/dist/components/kb-segment.mjs +4 -0
  127. package/dist/components/kb-sources.mjs +4 -0
  128. package/dist/components/kb-sql.mjs +4 -0
  129. package/dist/components/kb-trace.mjs +4 -0
  130. package/dist/components/kb-visibility.mjs +4 -0
  131. package/dist/components/kb-who-can.mjs +4 -0
  132. package/dist/components/kb.css +252 -0
  133. package/dist/components/kb.mjs +9 -0
  134. package/dist/index.d.mts +37 -2
  135. package/dist/index.mjs +8555 -205
  136. package/dist/registry.json +325 -3
  137. package/dist/ui.css +5451 -45
  138. package/docs/research/2026-10-03-ai-kb-components.md +392 -0
  139. package/docs/research/2026-10-03-kb-permissions.md +992 -0
  140. package/docs/spec/kb-family.md +443 -0
  141. package/package.json +2 -2
@@ -1525,10 +1525,803 @@ function renderChart(el, raw) {
1525
1525
  }
1526
1526
  };
1527
1527
  }
1528
+ var GRAPH_CHART_TYPE = "graph";
1529
+ var MAP_CHART_TYPE = "map";
1530
+ function buildLayoutState(nodes, edges, width, height) {
1531
+ const ids = [];
1532
+ const index = /* @__PURE__ */ new Map();
1533
+ for (const node of nodes) {
1534
+ const id = String(node?.id ?? "").trim();
1535
+ if (!id || index.has(id)) continue;
1536
+ index.set(id, ids.length);
1537
+ ids.push(id);
1538
+ }
1539
+ const pairs = [];
1540
+ for (const edge of edges) {
1541
+ const a = index.get(String(edge?.source ?? "").trim());
1542
+ const b = index.get(String(edge?.target ?? "").trim());
1543
+ if (a == null || b == null || a === b) continue;
1544
+ pairs.push([a, b]);
1545
+ }
1546
+ const n = ids.length;
1547
+ return {
1548
+ ids,
1549
+ index,
1550
+ x: new Float64Array(n),
1551
+ y: new Float64Array(n),
1552
+ pairs,
1553
+ width,
1554
+ height,
1555
+ k: n > 0 ? Math.sqrt(width * height / n) : 1
1556
+ };
1557
+ }
1558
+ function relaxLayout(state, rounds, pinned = -1, tempScale = 1) {
1559
+ const { x, y, pairs, width, height, k } = state;
1560
+ const n = x.length;
1561
+ if (n <= 1 || rounds <= 0) return;
1562
+ const cx = width / 2;
1563
+ const cy = height / 2;
1564
+ const gravity = 0.015;
1565
+ const tempStart = Math.min(width, height) / 6 * tempScale;
1566
+ const dx = new Float64Array(n);
1567
+ const dy = new Float64Array(n);
1568
+ for (let round = 0; round < rounds; round++) {
1569
+ dx.fill(0);
1570
+ dy.fill(0);
1571
+ const temp = Math.max(0.5, tempStart * (1 - round / rounds));
1572
+ for (let i = 0; i < n; i++) {
1573
+ for (let j = i + 1; j < n; j++) {
1574
+ let vx = x[j] - x[i];
1575
+ let vy = y[j] - y[i];
1576
+ let d2 = vx * vx + vy * vy;
1577
+ if (d2 < 0.01) {
1578
+ vx = 0.5 + (j - i) % 7 * 0.25;
1579
+ vy = (i * 3 + j) % 5 * 0.3 - 0.6;
1580
+ d2 = vx * vx + vy * vy;
1581
+ }
1582
+ const d = Math.sqrt(d2);
1583
+ const f = k * k / d;
1584
+ const ux = vx / d * f;
1585
+ const uy = vy / d * f;
1586
+ dx[i] -= ux;
1587
+ dy[i] -= uy;
1588
+ dx[j] += ux;
1589
+ dy[j] += uy;
1590
+ }
1591
+ }
1592
+ for (const [a, b] of pairs) {
1593
+ let vx = x[b] - x[a];
1594
+ let vy = y[b] - y[a];
1595
+ let d = Math.sqrt(vx * vx + vy * vy);
1596
+ if (d < 0.01) {
1597
+ vx = 1;
1598
+ vy = 0;
1599
+ d = 1;
1600
+ }
1601
+ const f = Math.min(d * d / k, k * 3);
1602
+ const ux = vx / d * f;
1603
+ const uy = vy / d * f;
1604
+ dx[a] += ux;
1605
+ dy[a] += uy;
1606
+ dx[b] -= ux;
1607
+ dy[b] -= uy;
1608
+ }
1609
+ for (let i = 0; i < n; i++) {
1610
+ if (i === pinned) continue;
1611
+ dx[i] += (cx - x[i]) * gravity;
1612
+ dy[i] += (cy - y[i]) * gravity;
1613
+ const dm = Math.sqrt(dx[i] * dx[i] + dy[i] * dy[i]);
1614
+ if (dm <= 0) continue;
1615
+ const v = Math.min(dm, temp) / dm;
1616
+ x[i] = Math.min(width, Math.max(0, x[i] + dx[i] * v));
1617
+ y[i] = Math.min(height, Math.max(0, y[i] + dy[i] * v));
1618
+ }
1619
+ }
1620
+ }
1621
+ function fitLayout(state, margin) {
1622
+ const { x, y, width, height } = state;
1623
+ const n = x.length;
1624
+ if (n === 0) return;
1625
+ let minX = Infinity;
1626
+ let maxX = -Infinity;
1627
+ let minY = Infinity;
1628
+ let maxY = -Infinity;
1629
+ for (let i = 0; i < n; i++) {
1630
+ if (x[i] < minX) minX = x[i];
1631
+ if (x[i] > maxX) maxX = x[i];
1632
+ if (y[i] < minY) minY = y[i];
1633
+ if (y[i] > maxY) maxY = y[i];
1634
+ }
1635
+ const spanX = Math.max(1e-6, maxX - minX);
1636
+ const spanY = Math.max(1e-6, maxY - minY);
1637
+ const scale = Math.min((width - margin * 2) / spanX, (height - margin * 2) / spanY, 4);
1638
+ const offX = (width - spanX * scale) / 2 - minX * scale;
1639
+ const offY = (height - spanY * scale) / 2 - minY * scale;
1640
+ for (let i = 0; i < n; i++) {
1641
+ x[i] = x[i] * scale + offX;
1642
+ y[i] = y[i] * scale + offY;
1643
+ }
1644
+ }
1645
+ function initRingLayout(state, seed = 0) {
1646
+ const { ids, index, x, y, width, height } = state;
1647
+ const n = ids.length;
1648
+ if (n === 0) return;
1649
+ if (n === 1) {
1650
+ x[0] = width / 2;
1651
+ y[0] = height / 2;
1652
+ return;
1653
+ }
1654
+ const order = [...ids].sort();
1655
+ const radius = Math.min(width, height) / 2 * 0.76;
1656
+ const deg = (seed % 360 + 360) % 360;
1657
+ const seedAngle = deg * Math.PI / 180;
1658
+ for (let rank = 0; rank < n; rank++) {
1659
+ const i = index.get(order[rank] ?? "") ?? -1;
1660
+ if (i < 0) continue;
1661
+ const angle = seedAngle - Math.PI / 2 + rank * 2 * Math.PI / n;
1662
+ x[i] = width / 2 + radius * Math.cos(angle);
1663
+ y[i] = height / 2 + radius * Math.sin(angle);
1664
+ }
1665
+ }
1666
+ function layoutGraph(nodes, edges, opts) {
1667
+ const width = Math.max(16, opts?.width ?? 320);
1668
+ const height = Math.max(16, opts?.height ?? 200);
1669
+ const state = buildLayoutState(nodes, edges, width, height);
1670
+ if (state.ids.length === 0) return /* @__PURE__ */ new Map();
1671
+ initRingLayout(state, opts?.seed ?? 0);
1672
+ relaxLayout(state, Math.max(1, Math.round(opts?.iterations ?? 300)));
1673
+ fitLayout(state, Math.max(8, Math.min(width, height) * 0.08));
1674
+ const out = /* @__PURE__ */ new Map();
1675
+ state.ids.forEach((id, i) => out.set(id, { x: state.x[i], y: state.y[i] }));
1676
+ return out;
1677
+ }
1678
+ var datumMarkInfo = /* @__PURE__ */ new WeakMap();
1679
+ var datumEventControl = /* @__PURE__ */ new WeakMap();
1680
+ function dispatchDatumEvent(root, type, mark, phase, pointer) {
1681
+ const numOr = (v) => v == null ? void 0 : Number(v);
1682
+ const detail = {
1683
+ type,
1684
+ phase,
1685
+ chart: chartDelegateMeta.get(root)?.type() ?? "vbar",
1686
+ index: numOr(mark.getAttribute("data-chart-index")),
1687
+ seriesIndex: numOr(mark.getAttribute("data-chart-series")),
1688
+ seriesName: mark.getAttribute("data-chart-series-name") ?? void 0,
1689
+ label: mark.getAttribute("data-chart-label") ?? void 0,
1690
+ value: numOr(mark.getAttribute("data-chart-value")),
1691
+ target: mark,
1692
+ pointerX: pointer?.x,
1693
+ pointerY: pointer?.y,
1694
+ datum: datumMarkInfo.get(mark)?.datum
1695
+ };
1696
+ emitIcen(root, `icen:chart-${type}`, detail);
1697
+ }
1698
+ function bindDatumEvents(root) {
1699
+ const existing = datumEventControl.get(root);
1700
+ if (existing) return existing;
1701
+ const tipEnabled = () => chartDelegateMeta.get(root)?.tooltip() ?? true;
1702
+ const markOf = (e) => {
1703
+ const t = e.target;
1704
+ return t instanceof Element ? t.closest("[data-chart-mark]") : null;
1705
+ };
1706
+ let hoverMark = null;
1707
+ let suppressClick = false;
1708
+ const bindings = [];
1709
+ const on = (type, fn) => {
1710
+ const listener = (evt) => fn(evt);
1711
+ root.addEventListener(type, listener);
1712
+ bindings.push([type, listener]);
1713
+ };
1714
+ const enterMark = (mark, px, py) => {
1715
+ hoverMark = mark;
1716
+ mark.classList.add("is-hover");
1717
+ dispatchDatumEvent(root, "hover", mark, "enter", { x: px, y: py });
1718
+ if (tipEnabled()) {
1719
+ const tip = chartTooltip();
1720
+ tip.textContent = datumMarkInfo.get(mark)?.tip ?? tooltipText(mark);
1721
+ applyTooltipSizing(mark);
1722
+ tip.hidden = false;
1723
+ moveTooltip(px, py);
1724
+ }
1725
+ };
1726
+ const leaveMark = (pointer) => {
1727
+ if (!hoverMark) return;
1728
+ hoverMark.classList.remove("is-hover");
1729
+ dispatchDatumEvent(root, "hover", hoverMark, "leave", pointer);
1730
+ hoverMark = null;
1731
+ chartTooltip().hidden = true;
1732
+ };
1733
+ on("pointerover", (e) => {
1734
+ const mark = markOf(e);
1735
+ if (!mark || mark === hoverMark) return;
1736
+ leaveMark({ x: e.clientX, y: e.clientY });
1737
+ enterMark(mark, e.clientX, e.clientY);
1738
+ });
1739
+ on("pointermove", (e) => {
1740
+ if (!hoverMark) return;
1741
+ dispatchDatumEvent(root, "hover", hoverMark, "move", { x: e.clientX, y: e.clientY });
1742
+ if (tipEnabled()) moveTooltip(e.clientX, e.clientY);
1743
+ });
1744
+ on("pointerout", (e) => {
1745
+ const mark = markOf(e);
1746
+ if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
1747
+ });
1748
+ on("pointerleave", () => leaveMark());
1749
+ on("click", (e) => {
1750
+ if (suppressClick) {
1751
+ suppressClick = false;
1752
+ return;
1753
+ }
1754
+ const mark = markOf(e);
1755
+ if (mark) dispatchDatumEvent(root, "click", mark, void 0, { x: e.clientX, y: e.clientY });
1756
+ });
1757
+ on("dblclick", (e) => {
1758
+ const mark = markOf(e);
1759
+ if (mark) dispatchDatumEvent(root, "dblclick", mark, void 0, { x: e.clientX, y: e.clientY });
1760
+ });
1761
+ on("contextmenu", (e) => {
1762
+ const mark = markOf(e);
1763
+ if (!mark) return;
1764
+ e.preventDefault();
1765
+ dispatchDatumEvent(root, "contextmenu", mark, void 0, { x: e.clientX, y: e.clientY });
1766
+ });
1767
+ const control = {
1768
+ unbind() {
1769
+ for (const [type, listener] of bindings) root.removeEventListener(type, listener);
1770
+ bindings.length = 0;
1771
+ if (hoverMark) {
1772
+ hoverMark.classList.remove("is-hover");
1773
+ hoverMark = null;
1774
+ }
1775
+ chartTooltip().hidden = true;
1776
+ datumEventControl.delete(root);
1777
+ },
1778
+ suppressNextClick() {
1779
+ suppressClick = true;
1780
+ }
1781
+ };
1782
+ datumEventControl.set(root, control);
1783
+ return control;
1784
+ }
1785
+ function buildClusterModel(keys, labels) {
1786
+ const seen = /* @__PURE__ */ new Set();
1787
+ for (const key of keys) {
1788
+ if (typeof key === "string" && key !== "") seen.add(key);
1789
+ }
1790
+ if (seen.size === 0) return null;
1791
+ const list = [...seen].sort();
1792
+ return {
1793
+ keys: list,
1794
+ indexOf: new Map(list.map((k, i) => [k, i])),
1795
+ label: (key) => labels?.[key] ?? key,
1796
+ color: (index) => paletteVar(index + 1)
1797
+ };
1798
+ }
1799
+ function registerDatumMark(mark, datum, tip) {
1800
+ datumMarkInfo.set(mark, { datum, tip });
1801
+ }
1802
+ function truncateText(text, max = 8) {
1803
+ const chars = Array.from(text);
1804
+ return chars.length > max ? `${chars.slice(0, max).join("")}\u2026` : text;
1805
+ }
1806
+ function svgClientPoint(svg, clientX, clientY, viewBoxWidth, viewBoxHeight) {
1807
+ const rect = svg.getBoundingClientRect();
1808
+ const scale = Math.min(rect.width / viewBoxWidth, rect.height / viewBoxHeight);
1809
+ if (!Number.isFinite(scale) || scale <= 0) {
1810
+ return { x: viewBoxWidth / 2, y: viewBoxHeight / 2 };
1811
+ }
1812
+ return {
1813
+ x: (clientX - rect.left - (rect.width - viewBoxWidth * scale) / 2) / scale,
1814
+ y: (clientY - rect.top - (rect.height - viewBoxHeight * scale) / 2) / scale
1815
+ };
1816
+ }
1817
+ function normalizeGraphSpec(raw) {
1818
+ let src = raw;
1819
+ if (typeof src === "string") {
1820
+ try {
1821
+ src = JSON.parse(src);
1822
+ } catch {
1823
+ src = {};
1824
+ }
1825
+ }
1826
+ if (!src || typeof src !== "object" || Array.isArray(src)) src = {};
1827
+ const s = src;
1828
+ const spec = { ...normalizeChartSpec(s), type: "graph", nodes: [], edges: [] };
1829
+ if (Array.isArray(s.nodes)) {
1830
+ const seen = /* @__PURE__ */ new Set();
1831
+ for (const v of s.nodes) {
1832
+ if (!v || typeof v !== "object" || Array.isArray(v)) continue;
1833
+ const r = v;
1834
+ const id = String(r.id ?? "").trim();
1835
+ if (!id || seen.has(id)) continue;
1836
+ seen.add(id);
1837
+ const node = { id };
1838
+ if (r.label != null) node.label = String(r.label);
1839
+ if (typeof r.cluster === "string" && r.cluster !== "") node.cluster = r.cluster;
1840
+ const weight = toNum(r.weight);
1841
+ if (weight != null) node.weight = weight;
1842
+ if (r.meta && typeof r.meta === "object" && !Array.isArray(r.meta)) {
1843
+ node.meta = { ...r.meta };
1844
+ }
1845
+ spec.nodes.push(node);
1846
+ }
1847
+ }
1848
+ const known = new Set(spec.nodes.map((n) => n.id));
1849
+ if (Array.isArray(s.edges)) {
1850
+ for (const v of s.edges) {
1851
+ if (!v || typeof v !== "object" || Array.isArray(v)) continue;
1852
+ const r = v;
1853
+ const source = String(r.source ?? "").trim();
1854
+ const target = String(r.target ?? "").trim();
1855
+ if (!known.has(source) || !known.has(target) || source === target) continue;
1856
+ const edge = { source, target };
1857
+ const weight = toNum(r.weight);
1858
+ if (weight != null) edge.weight = weight;
1859
+ spec.edges.push(edge);
1860
+ }
1861
+ }
1862
+ if (s.directed === true) spec.directed = true;
1863
+ if (s.clusterLabels && typeof s.clusterLabels === "object" && !Array.isArray(s.clusterLabels)) {
1864
+ const labels = {};
1865
+ for (const [key, value] of Object.entries(s.clusterLabels)) {
1866
+ if (typeof value === "string" && value !== "") labels[key] = value;
1867
+ }
1868
+ spec.clusterLabels = labels;
1869
+ }
1870
+ return spec;
1871
+ }
1872
+ function normalizeMapSpec(raw) {
1873
+ let src = raw;
1874
+ if (typeof src === "string") {
1875
+ try {
1876
+ src = JSON.parse(src);
1877
+ } catch {
1878
+ src = {};
1879
+ }
1880
+ }
1881
+ if (!src || typeof src !== "object" || Array.isArray(src)) src = {};
1882
+ const s = src;
1883
+ const spec = { ...normalizeChartSpec(s), type: "map", points: [] };
1884
+ if (Array.isArray(s.points)) {
1885
+ const seen = /* @__PURE__ */ new Set();
1886
+ for (const v of s.points) {
1887
+ if (!v || typeof v !== "object" || Array.isArray(v)) continue;
1888
+ const r = v;
1889
+ const id = String(r.id ?? "").trim();
1890
+ if (!id || seen.has(id)) continue;
1891
+ seen.add(id);
1892
+ const point = { id, x: toNum(r.x) ?? 0, y: toNum(r.y) ?? 0 };
1893
+ if (typeof r.cluster === "string" && r.cluster !== "") point.cluster = r.cluster;
1894
+ if (r.label != null) point.label = String(r.label);
1895
+ if (r.meta && typeof r.meta === "object" && !Array.isArray(r.meta)) {
1896
+ point.meta = { ...r.meta };
1897
+ }
1898
+ spec.points.push(point);
1899
+ }
1900
+ }
1901
+ if (s.clusters && typeof s.clusters === "object" && !Array.isArray(s.clusters)) {
1902
+ const labels = {};
1903
+ for (const [key, value] of Object.entries(s.clusters)) {
1904
+ if (typeof value === "string" && value !== "") labels[key] = value;
1905
+ }
1906
+ spec.clusters = labels;
1907
+ }
1908
+ return spec;
1909
+ }
1910
+ var GRAPH_W = 360;
1911
+ var GRAPH_H = 260;
1912
+ var GRAPH_PAD = 28;
1913
+ var GRAPH_LW = GRAPH_W - GRAPH_PAD * 2;
1914
+ var GRAPH_LH = GRAPH_H - GRAPH_PAD * 2;
1915
+ var graphMarkerSeq = 0;
1916
+ function renderGraph(el, raw) {
1917
+ const noop = () => void 0;
1918
+ let current = normalizeGraphSpec(raw);
1919
+ if (typeof document === "undefined") {
1920
+ return { el, spec: current, update: noop, on: () => noop, destroy: noop };
1921
+ }
1922
+ const legendState = { hidden: null };
1923
+ let layout = null;
1924
+ let svgRef = null;
1925
+ let nodeEls = [];
1926
+ let edgeList = [];
1927
+ let nodeRadii = [];
1928
+ let directed = false;
1929
+ let arrowId = "";
1930
+ const edgeEnds = (from, to) => {
1931
+ const ax = GRAPH_PAD + (layout ? layout.x[from] : 0);
1932
+ const ay = GRAPH_PAD + (layout ? layout.y[from] : 0);
1933
+ const bx = GRAPH_PAD + (layout ? layout.x[to] : 0);
1934
+ const by = GRAPH_PAD + (layout ? layout.y[to] : 0);
1935
+ if (!directed) {
1936
+ return { x1: ax.toFixed(1), y1: ay.toFixed(1), x2: bx.toFixed(1), y2: by.toFixed(1) };
1937
+ }
1938
+ const dx = bx - ax;
1939
+ const dy = by - ay;
1940
+ const d = Math.hypot(dx, dy) || 1;
1941
+ const cut = (nodeRadii[to] ?? 6) + 4;
1942
+ return {
1943
+ x1: ax.toFixed(1),
1944
+ y1: ay.toFixed(1),
1945
+ x2: (bx - dx / d * cut).toFixed(1),
1946
+ y2: (by - dy / d * cut).toFixed(1)
1947
+ };
1948
+ };
1949
+ const paint = (body) => {
1950
+ directed = current.directed === true;
1951
+ layout = null;
1952
+ svgRef = null;
1953
+ nodeEls = [];
1954
+ edgeList = [];
1955
+ arrowId = "";
1956
+ const nodes = current.nodes;
1957
+ if (nodes.length === 0) {
1958
+ nodeRadii = [];
1959
+ renderEmpty(body, current.emptyLabel);
1960
+ return;
1961
+ }
1962
+ const clusters = buildClusterModel(nodes.map((n) => n.cluster), current.clusterLabels);
1963
+ const tone = toneVar(current.tone);
1964
+ const idOf = new Map(nodes.map((n, i) => [n.id, i]));
1965
+ const degree = nodes.map(() => 0);
1966
+ for (const e of current.edges) {
1967
+ const a = idOf.get(e.source);
1968
+ const b = idOf.get(e.target);
1969
+ if (a == null || b == null || a === b) continue;
1970
+ degree[a] += 1;
1971
+ degree[b] += 1;
1972
+ }
1973
+ const weights = nodes.map((n, i) => n.weight ?? degree[i]);
1974
+ let wMin = Infinity;
1975
+ let wMax = -Infinity;
1976
+ for (const w of weights) {
1977
+ if (w < wMin) wMin = w;
1978
+ if (w > wMax) wMax = w;
1979
+ }
1980
+ nodeRadii = weights.map((w) => 6 + (wMax > wMin ? (w - wMin) / (wMax - wMin) : 0.5) * 12);
1981
+ let eMin = Infinity;
1982
+ let eMax = -Infinity;
1983
+ for (const e of current.edges) {
1984
+ if (e.weight == null) continue;
1985
+ if (e.weight < eMin) eMin = e.weight;
1986
+ if (e.weight > eMax) eMax = e.weight;
1987
+ }
1988
+ const edgeWidth = (e) => e.weight == null ? 1 : 0.6 + (eMax > eMin ? (e.weight - eMin) / (eMax - eMin) : 0.5) * 1.9;
1989
+ const wrap = document.createElement("div");
1990
+ wrap.className = "chart-graph";
1991
+ if (current.tone) wrap.style.setProperty("--chart-tone", toneVar(current.tone));
1992
+ const svg = svgEl("svg", { viewBox: `0 0 ${GRAPH_W} ${GRAPH_H}`, role: "img" });
1993
+ svgRef = svg;
1994
+ svg.addEventListener("pointerdown", onNodePointerDown);
1995
+ const defs = svgEl("defs", {});
1996
+ if (directed) {
1997
+ arrowId = `icen-chart-graph-arrow-${++graphMarkerSeq}`;
1998
+ const marker = svgEl("marker", {
1999
+ id: arrowId,
2000
+ viewBox: "0 0 8 8",
2001
+ refX: "7",
2002
+ refY: "4",
2003
+ markerWidth: "6",
2004
+ markerHeight: "6",
2005
+ orient: "auto"
2006
+ });
2007
+ marker.appendChild(svgEl("path", { d: "M0.5,0.8 L7.5,4 L0.5,7.2 Z", fill: "var(--token-text-faint)" }));
2008
+ defs.appendChild(marker);
2009
+ }
2010
+ layout = buildLayoutState(nodes, current.edges, GRAPH_LW, GRAPH_LH);
2011
+ initRingLayout(layout, 0);
2012
+ relaxLayout(layout, 300);
2013
+ fitLayout(layout, 20);
2014
+ const edgeLayer = svgEl("g", { class: "chart-graph-edges" });
2015
+ const nodeLayer = svgEl("g", { class: "chart-graph-nodes" });
2016
+ for (const e of current.edges) {
2017
+ const from = idOf.get(e.source);
2018
+ const to = idOf.get(e.target);
2019
+ if (from == null || to == null || from === to) continue;
2020
+ const line = svgEl("line", {
2021
+ ...edgeEnds(from, to),
2022
+ stroke: "var(--token-line-soft)",
2023
+ "stroke-width": edgeWidth(e).toFixed(2),
2024
+ ...directed ? { "marker-end": `url(#${arrowId})` } : {}
2025
+ });
2026
+ line.classList.add("chart-graph-edge");
2027
+ const fromKey = nodes[from]?.cluster;
2028
+ const toKey = nodes[to]?.cluster;
2029
+ if (fromKey) line.setAttribute("data-edge-cluster-from", fromKey);
2030
+ if (toKey) line.setAttribute("data-edge-cluster-to", toKey);
2031
+ edgeList.push({ el: line, from, to });
2032
+ edgeLayer.appendChild(line);
2033
+ }
2034
+ nodes.forEach((node, i) => {
2035
+ const clusterKey = node.cluster;
2036
+ const ci = clusterKey != null && clusters ? clusters.indexOf.get(clusterKey) : void 0;
2037
+ const clusterName = ci != null && clusters && clusterKey != null ? clusters.label(clusterKey) : void 0;
2038
+ const fill = clusters ? ci != null ? clusters.color(ci) : "var(--token-text-faint)" : tone;
2039
+ const pos = { x: GRAPH_PAD + layout.x[i], y: GRAPH_PAD + layout.y[i] };
2040
+ const g = svgEl("g", {
2041
+ class: "chart-graph-node",
2042
+ transform: `translate(${pos.x.toFixed(1)},${pos.y.toFixed(1)})`
2043
+ });
2044
+ g.setAttribute("data-chart-node-id", node.id);
2045
+ setMarkData(g, {
2046
+ index: i,
2047
+ seriesIndex: ci,
2048
+ seriesName: clusterName,
2049
+ label: node.label ?? node.id,
2050
+ value: weights[i]
2051
+ });
2052
+ const dot = svgEl("circle", {
2053
+ class: "chart-graph-dot",
2054
+ r: (nodeRadii[i] ?? 6).toFixed(1),
2055
+ fill,
2056
+ "fill-opacity": "0.92",
2057
+ stroke: "var(--token-card)",
2058
+ "stroke-width": "1.5"
2059
+ });
2060
+ const hit = svgEl("circle", {
2061
+ class: "chart-graph-hit",
2062
+ r: String(Math.max((nodeRadii[i] ?? 6) + 4, 11)),
2063
+ fill: "transparent",
2064
+ "data-chart-hit": "1"
2065
+ });
2066
+ const labelText = truncateText(node.label ?? node.id);
2067
+ const head = clusterName ? `${clusterName} \xB7 ${labelText}` : labelText;
2068
+ const tip = weights[i] > 0 ? `${head}\uFF1A${weights[i]}` : head;
2069
+ const title = svgEl("title", {});
2070
+ title.textContent = tip;
2071
+ g.append(dot, hit, title);
2072
+ registerDatumMark(g, { id: node.id, label: node.label ?? node.id, cluster: clusterKey, meta: node.meta }, tip);
2073
+ nodeEls.push(g);
2074
+ nodeLayer.appendChild(g);
2075
+ });
2076
+ svg.append(defs, edgeLayer, nodeLayer);
2077
+ wrap.appendChild(svg);
2078
+ body.appendChild(wrap);
2079
+ if (clusters) {
2080
+ const legend = toggleLegend(
2081
+ clusters.keys.map((key, ci) => ({ key, label: clusters.label(key), tone: ci + 1, si: ci })),
2082
+ wrap
2083
+ );
2084
+ body.appendChild(legend);
2085
+ const syncEdges = () => {
2086
+ const hiddenOf = (key) => key != null && legend.querySelector(`[data-chart-key="${CSS.escape(key)}"]`)?.classList.contains("is-off") === true;
2087
+ for (const edge of edgeList) {
2088
+ edge.el.classList.toggle(
2089
+ "is-off",
2090
+ hiddenOf(edge.el.getAttribute("data-edge-cluster-from")) || hiddenOf(edge.el.getAttribute("data-edge-cluster-to"))
2091
+ );
2092
+ }
2093
+ };
2094
+ legend.addEventListener("click", syncEdges);
2095
+ legend.addEventListener("keydown", (e) => {
2096
+ if (e.key === "Enter" || e.key === " ") syncEdges();
2097
+ });
2098
+ }
2099
+ };
2100
+ const paintPositions = () => {
2101
+ if (!layout) return;
2102
+ for (let i = 0; i < nodeEls.length; i++) {
2103
+ nodeEls[i]?.setAttribute(
2104
+ "transform",
2105
+ `translate(${(GRAPH_PAD + layout.x[i]).toFixed(1)},${(GRAPH_PAD + layout.y[i]).toFixed(1)})`
2106
+ );
2107
+ }
2108
+ for (const edge of edgeList) {
2109
+ const ends = edgeEnds(edge.from, edge.to);
2110
+ edge.el.setAttribute("x1", ends.x1);
2111
+ edge.el.setAttribute("y1", ends.y1);
2112
+ edge.el.setAttribute("x2", ends.x2);
2113
+ edge.el.setAttribute("y2", ends.y2);
2114
+ }
2115
+ };
2116
+ let drag = null;
2117
+ const applyDragFrame = () => {
2118
+ if (!drag || !layout || !svgRef) return;
2119
+ const p = svgClientPoint(svgRef, drag.px, drag.py, GRAPH_W, GRAPH_H);
2120
+ const i = drag.i;
2121
+ layout.x[i] = Math.min(GRAPH_LW, Math.max(0, p.x - GRAPH_PAD));
2122
+ layout.y[i] = Math.min(GRAPH_LH, Math.max(0, p.y - GRAPH_PAD));
2123
+ relaxLayout(layout, 20, i, 0.4);
2124
+ paintPositions();
2125
+ drag.raf = 0;
2126
+ };
2127
+ const onDragMove = (e) => {
2128
+ if (!drag) return;
2129
+ if (Math.abs(e.clientX - drag.px) + Math.abs(e.clientY - drag.py) > 2) drag.moved = true;
2130
+ drag.px = e.clientX;
2131
+ drag.py = e.clientY;
2132
+ if (drag.raf === 0) drag.raf = requestAnimationFrame(applyDragFrame);
2133
+ };
2134
+ const endDrag = () => {
2135
+ if (!drag) return;
2136
+ if (drag.raf !== 0) cancelAnimationFrame(drag.raf);
2137
+ nodeEls[drag.i]?.classList.remove("is-drag");
2138
+ svgRef?.classList.remove("is-dragging");
2139
+ if (drag.moved) eventsControl.suppressNextClick();
2140
+ drag = null;
2141
+ window.removeEventListener("pointermove", onDragMove);
2142
+ window.removeEventListener("pointerup", endDrag);
2143
+ window.removeEventListener("pointercancel", endDrag);
2144
+ };
2145
+ const onNodePointerDown = (e) => {
2146
+ if (e.button !== 0 || !layout) return;
2147
+ const target = e.target;
2148
+ const g = target instanceof Element ? target.closest(".chart-graph-node") : null;
2149
+ if (!g) return;
2150
+ const i = layout.index.get(g.getAttribute("data-chart-node-id") ?? "");
2151
+ if (i == null) return;
2152
+ e.preventDefault();
2153
+ endDrag();
2154
+ drag = { i, moved: false, raf: 0, px: e.clientX, py: e.clientY };
2155
+ g.classList.add("is-drag");
2156
+ svgRef?.classList.add("is-dragging");
2157
+ window.addEventListener("pointermove", onDragMove);
2158
+ window.addEventListener("pointerup", endDrag);
2159
+ window.addEventListener("pointercancel", endDrag);
2160
+ };
2161
+ chartDelegateMeta.set(el, { type: () => GRAPH_CHART_TYPE, tooltip: () => current.tooltip !== false });
2162
+ const eventsControl = bindDatumEvents(el);
2163
+ const render = () => {
2164
+ if (drag) endDrag();
2165
+ const clusters = buildClusterModel(current.nodes.map((n) => n.cluster), current.clusterLabels);
2166
+ if (current.legend !== void 0) legendState.hidden = current.legend === false;
2167
+ withChrome(el, current, clusters != null, paint, legendState);
2168
+ };
2169
+ render();
2170
+ return {
2171
+ el,
2172
+ spec: current,
2173
+ update(next) {
2174
+ current = normalizeGraphSpec(next);
2175
+ render();
2176
+ },
2177
+ on(name, listener) {
2178
+ const handler = (event) => {
2179
+ listener(event.detail, event);
2180
+ };
2181
+ el.addEventListener(`icen:chart-${name}`, handler);
2182
+ return () => el.removeEventListener(`icen:chart-${name}`, handler);
2183
+ },
2184
+ destroy() {
2185
+ if (drag) endDrag();
2186
+ el.textContent = "";
2187
+ eventsControl.unbind();
2188
+ chartTooltip().hidden = true;
2189
+ }
2190
+ };
2191
+ }
2192
+ var MAP_W = 360;
2193
+ var MAP_H = 220;
2194
+ var MAP_PAD = 14;
2195
+ function renderMap(el, raw) {
2196
+ const noop = () => void 0;
2197
+ let current = normalizeMapSpec(raw);
2198
+ if (typeof document === "undefined") {
2199
+ return { el, spec: current, update: noop, on: () => noop, destroy: noop };
2200
+ }
2201
+ const legendState = { hidden: null };
2202
+ const paint = (body) => {
2203
+ const points = current.points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
2204
+ if (points.length === 0) {
2205
+ renderEmpty(body, current.emptyLabel);
2206
+ return;
2207
+ }
2208
+ const clusters = buildClusterModel(points.map((p) => p.cluster), current.clusters);
2209
+ const tone = toneVar(current.tone);
2210
+ let xMin = Infinity;
2211
+ let xMax = -Infinity;
2212
+ let yMin = Infinity;
2213
+ let yMax = -Infinity;
2214
+ for (const p of points) {
2215
+ if (p.x < xMin) xMin = p.x;
2216
+ if (p.x > xMax) xMax = p.x;
2217
+ if (p.y < yMin) yMin = p.y;
2218
+ if (p.y > yMax) yMax = p.y;
2219
+ }
2220
+ if (xMax === xMin) xMax = xMin + 1;
2221
+ if (yMax === yMin) yMax = yMin + 1;
2222
+ const xAt = (x) => MAP_PAD + (x - xMin) / (xMax - xMin) * (MAP_W - MAP_PAD * 2);
2223
+ const yAt = (y) => MAP_H - MAP_PAD - (y - yMin) / (yMax - yMin) * (MAP_H - MAP_PAD * 2);
2224
+ const wrap = document.createElement("div");
2225
+ wrap.className = "chart-map";
2226
+ if (current.tone) wrap.style.setProperty("--chart-tone", toneVar(current.tone));
2227
+ const svg = svgEl("svg", { viewBox: `0 0 ${MAP_W} ${MAP_H}`, role: "img" });
2228
+ for (let i = 0; i <= 3; i++) {
2229
+ const y = MAP_PAD + i * (MAP_H - MAP_PAD * 2) / 3;
2230
+ svg.appendChild(svgEl("line", {
2231
+ x1: String(MAP_PAD),
2232
+ x2: String(MAP_W - MAP_PAD),
2233
+ y1: String(y),
2234
+ y2: String(y),
2235
+ stroke: "var(--token-line-soft)",
2236
+ "stroke-width": "0.75",
2237
+ "stroke-dasharray": "2 4"
2238
+ }));
2239
+ }
2240
+ for (let i = 0; i <= 4; i++) {
2241
+ const x = MAP_PAD + i * (MAP_W - MAP_PAD * 2) / 4;
2242
+ svg.appendChild(svgEl("line", {
2243
+ x1: String(x),
2244
+ x2: String(x),
2245
+ y1: String(MAP_PAD),
2246
+ y2: String(MAP_H - MAP_PAD),
2247
+ stroke: "var(--token-line-soft)",
2248
+ "stroke-width": "0.75",
2249
+ "stroke-dasharray": "2 4"
2250
+ }));
2251
+ }
2252
+ points.forEach((p, i) => {
2253
+ const ci = p.cluster != null && clusters ? clusters.indexOf.get(p.cluster) : void 0;
2254
+ const clusterName = ci != null && clusters && p.cluster != null ? clusters.label(p.cluster) : void 0;
2255
+ const color = clusters ? ci != null ? clusters.color(ci) : "var(--token-text-faint)" : tone;
2256
+ const label = p.label ?? p.id;
2257
+ const labelText = truncateText(label);
2258
+ const tip = clusterName ? `${clusterName} \xB7 ${labelText}` : labelText;
2259
+ const g = svgEl("g", {
2260
+ class: "chart-map-point",
2261
+ transform: `translate(${xAt(p.x).toFixed(1)},${yAt(p.y).toFixed(1)})`
2262
+ });
2263
+ g.setAttribute("data-chart-point-id", p.id);
2264
+ setMarkData(g, { index: i, seriesIndex: ci, seriesName: clusterName, label });
2265
+ const dot = svgEl("circle", {
2266
+ class: "chart-map-dot",
2267
+ r: "4",
2268
+ fill: color,
2269
+ "fill-opacity": "0.7",
2270
+ stroke: color,
2271
+ "stroke-width": "1"
2272
+ });
2273
+ const hit = svgEl("circle", { class: "chart-map-hit", r: "9", fill: "transparent", "data-chart-hit": "1" });
2274
+ const title = svgEl("title", {});
2275
+ title.textContent = tip;
2276
+ g.append(dot, hit, title);
2277
+ registerDatumMark(g, { id: p.id, label, cluster: p.cluster, meta: p.meta }, tip);
2278
+ svg.appendChild(g);
2279
+ });
2280
+ wrap.appendChild(svg);
2281
+ body.appendChild(wrap);
2282
+ if (clusters) {
2283
+ body.appendChild(
2284
+ toggleLegend(
2285
+ clusters.keys.map((key, ci) => ({ key, label: clusters.label(key), tone: ci + 1, si: ci })),
2286
+ wrap
2287
+ )
2288
+ );
2289
+ }
2290
+ };
2291
+ chartDelegateMeta.set(el, { type: () => MAP_CHART_TYPE, tooltip: () => current.tooltip !== false });
2292
+ const eventsControl = bindDatumEvents(el);
2293
+ const render = () => {
2294
+ const clusters = buildClusterModel(current.points.map((p) => p.cluster), current.clusters);
2295
+ if (current.legend !== void 0) legendState.hidden = current.legend === false;
2296
+ withChrome(el, current, clusters != null, paint, legendState);
2297
+ };
2298
+ render();
2299
+ return {
2300
+ el,
2301
+ spec: current,
2302
+ update(next) {
2303
+ current = normalizeMapSpec(next);
2304
+ render();
2305
+ },
2306
+ on(name, listener) {
2307
+ const handler = (event) => {
2308
+ listener(event.detail, event);
2309
+ };
2310
+ el.addEventListener(`icen:chart-${name}`, handler);
2311
+ return () => el.removeEventListener(`icen:chart-${name}`, handler);
2312
+ },
2313
+ destroy() {
2314
+ el.textContent = "";
2315
+ eventsControl.unbind();
2316
+ chartTooltip().hidden = true;
2317
+ }
2318
+ };
2319
+ }
1528
2320
  export {
1529
2321
  bindChartEvents,
1530
2322
  chartFormatValue,
1531
2323
  inferChartType,
2324
+ layoutGraph,
1532
2325
  normalizeChartSpec,
1533
2326
  registerChartTone,
1534
2327
  renderArea,
@@ -1536,9 +2329,11 @@ export {
1536
2329
  renderChart,
1537
2330
  renderDonut,
1538
2331
  renderGauge,
2332
+ renderGraph,
1539
2333
  renderHBar,
1540
2334
  renderHeatmap,
1541
2335
  renderLine,
2336
+ renderMap,
1542
2337
  renderRadar,
1543
2338
  renderScatter,
1544
2339
  renderSparkline,