@icen.ai/ui 0.8.0 → 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 (228) hide show
  1. package/AGENTS.md +63 -14
  2. package/README.md +17 -9
  3. package/dist/base.css +4 -3
  4. package/dist/behaviors/accordion.d.mts +5 -2
  5. package/dist/behaviors/accordion.mjs +36 -11
  6. package/dist/behaviors/ai-branch.d.mts +19 -0
  7. package/dist/behaviors/ai-branch.mjs +217 -0
  8. package/dist/behaviors/ai-chat.d.mts +11 -7
  9. package/dist/behaviors/ai-chat.mjs +134 -51
  10. package/dist/behaviors/ai-composer.d.mts +28 -3
  11. package/dist/behaviors/ai-composer.mjs +399 -229
  12. package/dist/behaviors/ai-core.d.mts +9 -4
  13. package/dist/behaviors/ai-core.mjs +21 -8
  14. package/dist/behaviors/ai-diff.d.mts +2 -2
  15. package/dist/behaviors/ai-diff.mjs +172 -41
  16. package/dist/behaviors/ai-feedback.d.mts +26 -0
  17. package/dist/behaviors/ai-feedback.mjs +303 -0
  18. package/dist/behaviors/ai-panel.d.mts +26 -9
  19. package/dist/behaviors/ai-panel.mjs +211 -110
  20. package/dist/behaviors/ai-provider.mjs +54 -48
  21. package/dist/behaviors/ai-threads.d.mts +38 -0
  22. package/dist/behaviors/ai-threads.mjs +319 -0
  23. package/dist/behaviors/ai-tool.d.mts +12 -4
  24. package/dist/behaviors/ai-tool.mjs +316 -263
  25. package/dist/behaviors/ai-tools.d.mts +9 -6
  26. package/dist/behaviors/ai-tools.mjs +264 -155
  27. package/dist/behaviors/back-top.mjs +16 -1
  28. package/dist/behaviors/carousel.d.mts +5 -2
  29. package/dist/behaviors/carousel.mjs +53 -26
  30. package/dist/behaviors/charts.d.mts +170 -22
  31. package/dist/behaviors/charts.mjs +1016 -150
  32. package/dist/behaviors/command-palette.d.mts +40 -6
  33. package/dist/behaviors/command-palette.mjs +143 -15
  34. package/dist/behaviors/context-menu.d.mts +21 -4
  35. package/dist/behaviors/context-menu.mjs +122 -38
  36. package/dist/behaviors/controls.d.mts +59 -0
  37. package/dist/behaviors/controls.mjs +601 -0
  38. package/dist/behaviors/copy.d.mts +5 -2
  39. package/dist/behaviors/copy.mjs +37 -12
  40. package/dist/behaviors/datatable.d.mts +12 -0
  41. package/dist/behaviors/datatable.mjs +226 -43
  42. package/dist/behaviors/date-picker.d.mts +2 -2
  43. package/dist/behaviors/date-picker.mjs +171 -32
  44. package/dist/behaviors/dropdown.d.mts +6 -2
  45. package/dist/behaviors/dropdown.mjs +164 -25
  46. package/dist/behaviors/events.d.mts +523 -0
  47. package/dist/behaviors/events.mjs +208 -0
  48. package/dist/behaviors/input.mjs +25 -12
  49. package/dist/behaviors/kb-access.d.mts +34 -0
  50. package/dist/behaviors/kb-access.mjs +569 -0
  51. package/dist/behaviors/kb-acl.d.mts +50 -0
  52. package/dist/behaviors/kb-acl.mjs +405 -0
  53. package/dist/behaviors/kb-answer.d.mts +38 -0
  54. package/dist/behaviors/kb-answer.mjs +2055 -0
  55. package/dist/behaviors/kb-audit.d.mts +23 -0
  56. package/dist/behaviors/kb-audit.mjs +375 -0
  57. package/dist/behaviors/kb-canvas.d.mts +44 -0
  58. package/dist/behaviors/kb-canvas.mjs +589 -0
  59. package/dist/behaviors/kb-chain.d.mts +15 -0
  60. package/dist/behaviors/kb-chain.mjs +270 -0
  61. package/dist/behaviors/kb-checkpoint.d.mts +16 -0
  62. package/dist/behaviors/kb-checkpoint.mjs +342 -0
  63. package/dist/behaviors/kb-chunks.d.mts +31 -0
  64. package/dist/behaviors/kb-chunks.mjs +475 -0
  65. package/dist/behaviors/kb-citation.d.mts +38 -0
  66. package/dist/behaviors/kb-citation.mjs +677 -0
  67. package/dist/behaviors/kb-clarify.d.mts +20 -0
  68. package/dist/behaviors/kb-clarify.mjs +248 -0
  69. package/dist/behaviors/kb-conflict.d.mts +22 -0
  70. package/dist/behaviors/kb-conflict.mjs +284 -0
  71. package/dist/behaviors/kb-connector.d.mts +17 -0
  72. package/dist/behaviors/kb-connector.mjs +375 -0
  73. package/dist/behaviors/kb-core.d.mts +529 -0
  74. package/dist/behaviors/kb-core.mjs +890 -0
  75. package/dist/behaviors/kb-eval.d.mts +22 -0
  76. package/dist/behaviors/kb-eval.mjs +408 -0
  77. package/dist/behaviors/kb-explain.d.mts +11 -0
  78. package/dist/behaviors/kb-explain.mjs +243 -0
  79. package/dist/behaviors/kb-filter.d.mts +31 -0
  80. package/dist/behaviors/kb-filter.mjs +587 -0
  81. package/dist/behaviors/kb-gap.d.mts +27 -0
  82. package/dist/behaviors/kb-gap.mjs +273 -0
  83. package/dist/behaviors/kb-hittest.d.mts +34 -0
  84. package/dist/behaviors/kb-hittest.mjs +409 -0
  85. package/dist/behaviors/kb-metadata.d.mts +29 -0
  86. package/dist/behaviors/kb-metadata.mjs +326 -0
  87. package/dist/behaviors/kb-passage.d.mts +41 -0
  88. package/dist/behaviors/kb-passage.mjs +529 -0
  89. package/dist/behaviors/kb-pipeline.d.mts +18 -0
  90. package/dist/behaviors/kb-pipeline.mjs +381 -0
  91. package/dist/behaviors/kb-qa.d.mts +26 -0
  92. package/dist/behaviors/kb-qa.mjs +332 -0
  93. package/dist/behaviors/kb-rerank.d.mts +20 -0
  94. package/dist/behaviors/kb-rerank.mjs +315 -0
  95. package/dist/behaviors/kb-retrieval.d.mts +49 -0
  96. package/dist/behaviors/kb-retrieval.mjs +746 -0
  97. package/dist/behaviors/kb-review.d.mts +44 -0
  98. package/dist/behaviors/kb-review.mjs +451 -0
  99. package/dist/behaviors/kb-sandbox.d.mts +33 -0
  100. package/dist/behaviors/kb-sandbox.mjs +325 -0
  101. package/dist/behaviors/kb-segment.d.mts +32 -0
  102. package/dist/behaviors/kb-segment.mjs +499 -0
  103. package/dist/behaviors/kb-sources.d.mts +27 -0
  104. package/dist/behaviors/kb-sources.mjs +373 -0
  105. package/dist/behaviors/kb-sql.d.mts +43 -0
  106. package/dist/behaviors/kb-sql.mjs +948 -0
  107. package/dist/behaviors/kb-trace.d.mts +16 -0
  108. package/dist/behaviors/kb-trace.mjs +414 -0
  109. package/dist/behaviors/kb-visibility.d.mts +44 -0
  110. package/dist/behaviors/kb-visibility.mjs +403 -0
  111. package/dist/behaviors/kb-who-can.d.mts +32 -0
  112. package/dist/behaviors/kb-who-can.mjs +379 -0
  113. package/dist/behaviors/modal.d.mts +24 -5
  114. package/dist/behaviors/modal.mjs +76 -10
  115. package/dist/behaviors/nav.d.mts +1 -1
  116. package/dist/behaviors/nav.mjs +33 -7
  117. package/dist/behaviors/notification.d.mts +20 -4
  118. package/dist/behaviors/notification.mjs +53 -73
  119. package/dist/behaviors/popover.d.mts +39 -3
  120. package/dist/behaviors/popover.mjs +90 -4
  121. package/dist/behaviors/select.d.mts +4 -2
  122. package/dist/behaviors/select.mjs +161 -24
  123. package/dist/behaviors/sidebar.d.mts +5 -2
  124. package/dist/behaviors/sidebar.mjs +43 -13
  125. package/dist/behaviors/slider.d.mts +5 -2
  126. package/dist/behaviors/slider.mjs +54 -43
  127. package/dist/behaviors/split-pane.d.mts +6 -2
  128. package/dist/behaviors/split-pane.mjs +62 -21
  129. package/dist/behaviors/tabs.d.mts +3 -2
  130. package/dist/behaviors/tabs.mjs +65 -14
  131. package/dist/behaviors/tag-input.d.mts +5 -2
  132. package/dist/behaviors/tag-input.mjs +61 -21
  133. package/dist/behaviors/theme.d.mts +13 -2
  134. package/dist/behaviors/theme.mjs +48 -4
  135. package/dist/behaviors/toast.d.mts +30 -8
  136. package/dist/behaviors/toast.mjs +74 -7
  137. package/dist/behaviors/tree.d.mts +5 -2
  138. package/dist/behaviors/tree.mjs +38 -15
  139. package/dist/behaviors/upload.mjs +21 -23
  140. package/dist/cli.mjs +1 -1
  141. package/dist/components/ai-branch.mjs +3 -0
  142. package/dist/components/ai-chat.css +295 -0
  143. package/dist/components/ai-feedback.mjs +3 -0
  144. package/dist/components/ai-panel.css +218 -1
  145. package/dist/components/ai-threads.mjs +3 -0
  146. package/dist/components/ai-tool-call.mjs +1 -1
  147. package/dist/components/badge.css +1 -1
  148. package/dist/components/breadcrumb.css +0 -32
  149. package/dist/components/chart-calendar.mjs +1 -2
  150. package/dist/components/chart-graph.css +57 -0
  151. package/dist/components/chart-graph.mjs +4 -0
  152. package/dist/components/chart-map.mjs +3 -0
  153. package/dist/components/chart-pie.css +0 -6
  154. package/dist/components/chart-scatter.mjs +1 -1
  155. package/dist/components/charts.css +33 -1
  156. package/dist/components/charts.mjs +1 -0
  157. package/dist/components/content.css +3 -16
  158. package/dist/components/datatable.css +1 -1
  159. package/dist/components/date-picker.css +1 -1
  160. package/dist/components/kb-access.mjs +4 -0
  161. package/dist/components/kb-acl.mjs +4 -0
  162. package/dist/components/kb-agent.css +481 -0
  163. package/dist/components/kb-answer.mjs +4 -0
  164. package/dist/components/kb-audit.mjs +4 -0
  165. package/dist/components/kb-canvas.mjs +4 -0
  166. package/dist/components/kb-chain.mjs +4 -0
  167. package/dist/components/kb-checkpoint.mjs +4 -0
  168. package/dist/components/kb-chunks.mjs +4 -0
  169. package/dist/components/kb-citation.mjs +4 -0
  170. package/dist/components/kb-clarify.mjs +4 -0
  171. package/dist/components/kb-conflict.mjs +4 -0
  172. package/dist/components/kb-connector.mjs +4 -0
  173. package/dist/components/kb-data.css +649 -0
  174. package/dist/components/kb-eval.mjs +4 -0
  175. package/dist/components/kb-explain.mjs +4 -0
  176. package/dist/components/kb-filter.mjs +4 -0
  177. package/dist/components/kb-gap.mjs +4 -0
  178. package/dist/components/kb-ground.css +566 -0
  179. package/dist/components/kb-hittest.mjs +4 -0
  180. package/dist/components/kb-ingest.css +898 -0
  181. package/dist/components/kb-metadata.mjs +4 -0
  182. package/dist/components/kb-ops.css +548 -0
  183. package/dist/components/kb-passage.mjs +4 -0
  184. package/dist/components/kb-perm.css +444 -0
  185. package/dist/components/kb-pipeline.mjs +4 -0
  186. package/dist/components/kb-qa.mjs +4 -0
  187. package/dist/components/kb-rerank.mjs +4 -0
  188. package/dist/components/kb-retrieval.mjs +4 -0
  189. package/dist/components/kb-review.mjs +4 -0
  190. package/dist/components/kb-sandbox.mjs +4 -0
  191. package/dist/components/kb-search.css +940 -0
  192. package/dist/components/kb-segment.mjs +4 -0
  193. package/dist/components/kb-sources.mjs +4 -0
  194. package/dist/components/kb-sql.mjs +4 -0
  195. package/dist/components/kb-trace.mjs +4 -0
  196. package/dist/components/kb-visibility.mjs +4 -0
  197. package/dist/components/kb-who-can.mjs +4 -0
  198. package/dist/components/kb.css +252 -0
  199. package/dist/components/kb.mjs +9 -0
  200. package/dist/components/notification.css +2 -3
  201. package/dist/components/pagination.css +3 -1
  202. package/dist/components/pagination.mjs +1 -0
  203. package/dist/components/rating.mjs +1 -0
  204. package/dist/components/segmented.css +9 -1
  205. package/dist/components/segmented.mjs +1 -1
  206. package/dist/components/select.css +67 -4
  207. package/dist/components/steps.css +13 -3
  208. package/dist/components/steps.mjs +1 -0
  209. package/dist/components/switch.css +3 -1
  210. package/dist/components/switch.mjs +1 -0
  211. package/dist/components/table.mjs +1 -0
  212. package/dist/components/tabs.css +23 -0
  213. package/dist/components/tag-input.css +1 -6
  214. package/dist/components/upload.css +0 -15
  215. package/dist/index.d.mts +48 -11
  216. package/dist/index.mjs +13205 -3615
  217. package/dist/registry.json +366 -15
  218. package/dist/retro-effects.css +3 -16
  219. package/dist/tokens/retro-effects.css +3 -16
  220. package/dist/tokens/style-profiles.css +3 -2
  221. package/dist/tokens/typography.css +2 -0
  222. package/dist/tokens.css +5 -2
  223. package/dist/ui.css +5713 -267
  224. package/docs/research/2026-10-03-ai-kb-components.md +392 -0
  225. package/docs/research/2026-10-03-kb-permissions.md +992 -0
  226. package/docs/spec/ai-native.md +7 -4
  227. package/docs/spec/kb-family.md +443 -0
  228. package/package.json +2 -2
@@ -1,6 +1,20 @@
1
+ // src/behaviors/events.ts
2
+ function emitIcen(source, type, detail) {
3
+ return source.dispatchEvent(
4
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
5
+ );
6
+ }
7
+
1
8
  // src/behaviors/charts.ts
2
9
  var SVG_NS = "http://www.w3.org/2000/svg";
10
+ var REGISTERED_TONES = /* @__PURE__ */ new Map();
11
+ function registerChartTone(name, cssVar) {
12
+ if (!name || !cssVar) return;
13
+ REGISTERED_TONES.set(name, cssVar.startsWith("var(") ? cssVar : `var(${cssVar})`);
14
+ }
3
15
  function toneVar(tone = "accent") {
16
+ const registered = REGISTERED_TONES.get(tone);
17
+ if (registered) return registered;
4
18
  switch (tone) {
5
19
  case "success":
6
20
  return "var(--token-success)";
@@ -154,17 +168,14 @@ function withChrome(el, opts, hasLegend, paint, state) {
154
168
  if (state) state.hidden = hidden;
155
169
  el.setAttribute("data-legend-hidden", String(hidden));
156
170
  syncEye();
157
- el.dispatchEvent(new CustomEvent("icen:chart-legend-visibility", {
158
- bubbles: true,
159
- detail: { visible: hidden ? false : true }
160
- }));
171
+ emitIcen(el, "icen:chart-legend-visibility", { visible: hidden ? false : true });
161
172
  });
162
173
  syncEye();
163
174
  head.append(title, eye);
164
175
  el.append(head, body);
165
176
  paint(body);
166
177
  }
167
- function toggleLegend(items, scope, _opts) {
178
+ function toggleLegend(items, scope) {
168
179
  const legend = document.createElement("div");
169
180
  legend.className = "chart-legend chart-legend--toggle";
170
181
  for (const item of items) {
@@ -183,10 +194,7 @@ function toggleLegend(items, scope, _opts) {
183
194
  scope.querySelectorAll(`[data-chart-key="${CSS.escape(item.key)}"]`).forEach((m) => {
184
195
  m.classList.toggle("is-off", off);
185
196
  });
186
- legend.dispatchEvent(new CustomEvent("icen:chart-legend-toggle", {
187
- bubbles: true,
188
- detail: { key: item.key, seriesIndex: item.si, hidden: off }
189
- }));
197
+ emitIcen(legend, "icen:chart-legend-toggle", { key: item.key, seriesIndex: item.si, hidden: off });
190
198
  };
191
199
  row.addEventListener("click", toggle);
192
200
  row.addEventListener("keydown", (e) => {
@@ -205,7 +213,7 @@ function toggleLegend(items, scope, _opts) {
205
213
  return legend;
206
214
  }
207
215
  function renderVBar(el, opts) {
208
- if (typeof document === "undefined") return;
216
+ if (typeof document === "undefined") return el;
209
217
  const seriesList = normalizeSeriesInput(opts);
210
218
  const labels = opts.labels ?? [];
211
219
  const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
@@ -256,11 +264,13 @@ function renderVBar(el, opts) {
256
264
  wrap.appendChild(track);
257
265
  }
258
266
  body.append(wrap, labelsRow(labelsUsed));
259
- if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap, opts));
267
+ if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap));
260
268
  });
269
+ attachChartInteraction(el, "vbar");
270
+ return el;
261
271
  }
262
272
  function renderHBar(el, opts) {
263
- if (typeof document === "undefined") return;
273
+ if (typeof document === "undefined") return el;
264
274
  withChrome(el, opts, false, (body) => {
265
275
  const n = Math.min(opts.labels.length, opts.values.length);
266
276
  if (n === 0) {
@@ -294,9 +304,11 @@ function renderHBar(el, opts) {
294
304
  });
295
305
  body.appendChild(wrap);
296
306
  });
307
+ attachChartInteraction(el, "hbar");
308
+ return el;
297
309
  }
298
310
  function renderStack(el, opts) {
299
- if (typeof document === "undefined") return;
311
+ if (typeof document === "undefined") return el;
300
312
  withChrome(el, opts, opts.segments.length > 0, (body) => {
301
313
  const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
302
314
  if (opts.segments.length === 0 || total <= 0) {
@@ -305,13 +317,13 @@ function renderStack(el, opts) {
305
317
  }
306
318
  const bar = document.createElement("div");
307
319
  bar.className = "chart-stack";
308
- for (const seg of opts.segments) {
320
+ for (const [segIndex, seg] of opts.segments.entries()) {
309
321
  const s = document.createElement("div");
310
322
  s.className = "chart-stack-seg";
311
323
  s.style.width = `${seg.value / total * 100}%`;
312
324
  if (seg.tone) s.style.setProperty("--chart-tone", toneVar(seg.tone));
313
325
  s.setAttribute("title", `${seg.label}: ${fmt(opts, seg.value)}`);
314
- setMarkData(s, { index: opts.segments.indexOf(seg), label: seg.label, value: seg.value });
326
+ setMarkData(s, { index: segIndex, label: seg.label, value: seg.value });
315
327
  bar.appendChild(s);
316
328
  }
317
329
  const legend = document.createElement("div");
@@ -321,9 +333,11 @@ function renderStack(el, opts) {
321
333
  }
322
334
  body.append(bar, legend);
323
335
  });
336
+ attachChartInteraction(el, "stack");
337
+ return el;
324
338
  }
325
339
  function renderDonut(el, opts) {
326
- if (typeof document === "undefined") return;
340
+ if (typeof document === "undefined") return el;
327
341
  withChrome(el, opts, opts.segments.length > 0, (body) => {
328
342
  const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
329
343
  if (opts.segments.length === 0 || total <= 0) {
@@ -370,10 +384,12 @@ function renderDonut(el, opts) {
370
384
  wrap.append(svg, legend);
371
385
  body.appendChild(wrap);
372
386
  });
387
+ attachChartInteraction(el, "donut");
388
+ return el;
373
389
  }
374
390
  var lineGradientSeq = 0;
375
391
  function renderLine(el, opts) {
376
- if (typeof document === "undefined") return;
392
+ if (typeof document === "undefined") return el;
377
393
  const seriesList = normalizeSeriesInput(opts);
378
394
  const labels = opts.labels ?? [];
379
395
  const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
@@ -478,11 +494,13 @@ function renderLine(el, opts) {
478
494
  if (defs.firstChild) svg.insertBefore(defs, svg.firstChild);
479
495
  wrap.append(svg, labelsRow(sampleLabels(labelsUsed)));
480
496
  body.appendChild(wrap);
481
- if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap, opts));
497
+ if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap));
482
498
  });
499
+ attachChartInteraction(el, "line");
500
+ return el;
483
501
  }
484
502
  function renderArea(el, opts) {
485
- if (typeof document === "undefined") return;
503
+ if (typeof document === "undefined") return el;
486
504
  withChrome(el, opts, false, (body) => {
487
505
  const n = Math.min(opts.labels.length, opts.values.length);
488
506
  if (n === 0) {
@@ -551,9 +569,11 @@ function renderArea(el, opts) {
551
569
  wrap.append(svg, labelsRow(sampleLabels(labels)));
552
570
  body.appendChild(wrap);
553
571
  });
572
+ attachChartInteraction(el, "area");
573
+ return el;
554
574
  }
555
575
  function renderRadar(el, opts) {
556
- if (typeof document === "undefined") return;
576
+ if (typeof document === "undefined") return el;
557
577
  withChrome(el, opts, opts.series.length > 1, (body) => {
558
578
  const axes = opts.axes;
559
579
  const n = axes.length;
@@ -655,13 +675,12 @@ function renderRadar(el, opts) {
655
675
  }
656
676
  legendItems.push({ key: s.name || `\u7CFB\u5217${si + 1}`, label: s.name || `\u7CFB\u5217${si + 1}`, tone: s.tone, si });
657
677
  });
658
- wrap.append(svg, opts.series.length > 1 ? toggleLegend(legendItems, wrap, opts) : (() => {
659
- const l = document.createElement("div");
660
- l.className = "chart-legend";
661
- return l;
662
- })());
678
+ const legend = opts.series.length > 1 ? toggleLegend(legendItems, wrap) : null;
679
+ wrap.append(svg, ...legend ? [legend] : []);
663
680
  body.appendChild(wrap);
664
681
  });
682
+ attachChartInteraction(el, "radar");
683
+ return el;
665
684
  }
666
685
  function niceCeil(v) {
667
686
  if (v <= 0) return 1;
@@ -680,8 +699,41 @@ function isoDate(d) {
680
699
  const day = String(d.getDate()).padStart(2, "0");
681
700
  return `${d.getFullYear()}-${m}-${day}`;
682
701
  }
702
+ function weekdayLabelAt(defaults, day) {
703
+ return defaults[(day + 6) % 7] ?? "";
704
+ }
705
+ function levelOf(v, max) {
706
+ return v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
707
+ }
708
+ function padFirstWeek(grid, firstDate, weekStart) {
709
+ const first = /* @__PURE__ */ new Date(`${firstDate}T00:00:00`);
710
+ const pad = (first.getDay() + 7 - weekStart) % 7;
711
+ for (let i = 0; i < pad; i++) {
712
+ const blank = document.createElement("span");
713
+ blank.className = "chart-heatmap-cell is-blank";
714
+ grid.appendChild(blank);
715
+ }
716
+ return pad;
717
+ }
718
+ function heatmapLegend(labels) {
719
+ const legend = document.createElement("div");
720
+ legend.className = "chart-heatmap-legend";
721
+ const less = document.createElement("span");
722
+ less.textContent = labels?.less ?? "\u5C11";
723
+ const more = document.createElement("span");
724
+ more.textContent = labels?.more ?? "\u591A";
725
+ legend.appendChild(less);
726
+ for (let lv = 0; lv <= 4; lv++) {
727
+ const cell = document.createElement("span");
728
+ cell.className = "chart-heatmap-cell";
729
+ cell.dataset.level = String(lv);
730
+ legend.appendChild(cell);
731
+ }
732
+ legend.appendChild(more);
733
+ return legend;
734
+ }
683
735
  function renderHeatmap(el, opts) {
684
- if (typeof document === "undefined") return;
736
+ if (typeof document === "undefined") return el;
685
737
  withChrome(el, opts, true, (body) => {
686
738
  let entries;
687
739
  if (opts.data && opts.data.length > 0) {
@@ -700,48 +752,29 @@ function renderHeatmap(el, opts) {
700
752
  const weeks = opts.weeks ?? Math.ceil(entries.length / 7);
701
753
  if (entries.length > weeks * 7) entries = entries.slice(entries.length - weeks * 7);
702
754
  const max = maxOf(entries.map((e) => e.value));
703
- const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
704
755
  const wrap = document.createElement("div");
705
756
  wrap.className = "chart-heatmap";
706
757
  if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
707
758
  const grid = document.createElement("div");
708
759
  grid.className = "chart-heatmap-grid";
709
760
  grid.setAttribute("role", "img");
710
- const first = /* @__PURE__ */ new Date(`${entries[0].date}T00:00:00`);
711
- const pad = (first.getDay() + 6) % 7;
712
- for (let i = 0; i < pad; i++) {
713
- const blank = document.createElement("span");
714
- blank.className = "chart-heatmap-cell is-blank";
715
- grid.appendChild(blank);
716
- }
717
- for (const entry of entries) {
761
+ padFirstWeek(grid, entries[0].date, opts.weekStart === 0 ? 0 : 1);
762
+ for (const [i, entry] of entries.entries()) {
718
763
  const cell = document.createElement("span");
719
764
  cell.className = "chart-heatmap-cell";
720
- cell.dataset.level = String(levelOf(entry.value));
765
+ cell.dataset.level = String(levelOf(entry.value, max));
721
766
  cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
722
- setMarkData(cell, { index: entries.indexOf(entry), label: entry.date, value: entry.value });
767
+ setMarkData(cell, { index: i, label: entry.date, value: entry.value });
723
768
  grid.appendChild(cell);
724
769
  }
725
- const legend = document.createElement("div");
726
- legend.className = "chart-heatmap-legend";
727
- const less = document.createElement("span");
728
- less.textContent = "\u5C11";
729
- const more = document.createElement("span");
730
- more.textContent = "\u591A";
731
- legend.appendChild(less);
732
- for (let lv = 0; lv <= 4; lv++) {
733
- const cell = document.createElement("span");
734
- cell.className = "chart-heatmap-cell";
735
- cell.dataset.level = String(lv);
736
- legend.appendChild(cell);
737
- }
738
- legend.appendChild(more);
739
- wrap.append(grid, legend);
770
+ wrap.append(grid, heatmapLegend(opts.labels));
740
771
  body.appendChild(wrap);
741
772
  });
773
+ attachChartInteraction(el, "heatmap");
774
+ return el;
742
775
  }
743
776
  function renderSparkline(el, opts) {
744
- if (typeof document === "undefined") return;
777
+ if (typeof document === "undefined") return el;
745
778
  withChrome(el, opts, false, (body) => {
746
779
  const values = opts.values;
747
780
  if (values.length === 0) {
@@ -796,9 +829,11 @@ function renderSparkline(el, opts) {
796
829
  wrap.appendChild(svg);
797
830
  body.appendChild(wrap);
798
831
  });
832
+ attachChartInteraction(el, "sparkline");
833
+ return el;
799
834
  }
800
835
  function renderGauge(el, opts) {
801
- if (typeof document === "undefined") return;
836
+ if (typeof document === "undefined") return el;
802
837
  el.textContent = "";
803
838
  const max = opts.max ?? 100;
804
839
  const ratio = max > 0 ? Math.min(1, Math.max(0, opts.value / max)) : 0;
@@ -842,9 +877,11 @@ function renderGauge(el, opts) {
842
877
  }
843
878
  wrap.append(svg, center);
844
879
  el.appendChild(wrap);
880
+ attachChartInteraction(el, "gauge");
881
+ return el;
845
882
  }
846
883
  function renderCalendar(el, opts) {
847
- if (typeof document === "undefined") return;
884
+ if (typeof document === "undefined") return el;
848
885
  withChrome(el, opts, true, (body) => {
849
886
  let entries;
850
887
  if (opts.data && opts.data.length > 0) {
@@ -858,6 +895,10 @@ function renderCalendar(el, opts) {
858
895
  const wrap = document.createElement("div");
859
896
  wrap.className = "chart-calendar";
860
897
  if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
898
+ const weekStart = opts.weekStart === 0 ? 0 : 1;
899
+ const weekdayDefaults = ["\u4E00", "", "\u4E09", "", "\u4E94", "", "\u65E5"];
900
+ const weekdayLabels = opts.labels?.weekdays ?? weekdayDefaults.map((_, i) => weekdayLabelAt(weekdayDefaults, (weekStart + i) % 7));
901
+ const monthLabel = (m) => opts.labels?.months?.[m] ?? `${m + 1}\u6708`;
861
902
  const grid = document.createElement("div");
862
903
  grid.className = "chart-heatmap-grid";
863
904
  grid.setAttribute("role", "img");
@@ -865,20 +906,13 @@ function renderCalendar(el, opts) {
865
906
  months.className = "chart-calendar-months";
866
907
  const weekdayRow = document.createElement("div");
867
908
  weekdayRow.className = "chart-calendar-weekdays";
868
- ["\u4E00", "", "\u4E09", "", "\u4E94", "", "\u65E5"].forEach((w) => {
909
+ weekdayLabels.forEach((w) => {
869
910
  const s = document.createElement("span");
870
911
  s.textContent = w;
871
912
  weekdayRow.appendChild(s);
872
913
  });
873
914
  const max = maxOf(entries.map((e) => e.value));
874
- const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
875
- const first = /* @__PURE__ */ new Date(`${entries[0].date}T00:00:00`);
876
- const pad = (first.getDay() + 6) % 7;
877
- for (let i = 0; i < pad; i++) {
878
- const blank = document.createElement("span");
879
- blank.className = "chart-heatmap-cell is-blank";
880
- grid.appendChild(blank);
881
- }
915
+ const pad = padFirstWeek(grid, entries[0].date, weekStart);
882
916
  let lastMonth = -1;
883
917
  entries.forEach((entry, i) => {
884
918
  const d = /* @__PURE__ */ new Date(`${entry.date}T00:00:00`);
@@ -887,39 +921,28 @@ function renderCalendar(el, opts) {
887
921
  lastMonth = d.getMonth();
888
922
  const label = document.createElement("span");
889
923
  label.className = "chart-calendar-month";
890
- label.textContent = `${d.getMonth() + 1}\u6708`;
924
+ label.textContent = monthLabel(d.getMonth());
891
925
  label.style.setProperty("--col", String(col));
892
926
  months.appendChild(label);
893
927
  }
894
928
  const cell = document.createElement("span");
895
929
  cell.className = "chart-heatmap-cell";
896
- cell.dataset.level = String(levelOf(entry.value));
930
+ cell.dataset.level = String(levelOf(entry.value, max));
897
931
  cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
898
932
  setMarkData(cell, { index: i, label: entry.date, value: entry.value });
899
933
  grid.appendChild(cell);
900
934
  });
901
- const legend = document.createElement("div");
902
- legend.className = "chart-heatmap-legend";
903
- const less = document.createElement("span");
904
- less.textContent = "\u5C11";
905
- const more = document.createElement("span");
906
- more.textContent = "\u591A";
907
- legend.appendChild(less);
908
- for (let lv = 0; lv <= 4; lv++) {
909
- const cell = document.createElement("span");
910
- cell.className = "chart-heatmap-cell";
911
- cell.dataset.level = String(lv);
912
- legend.appendChild(cell);
913
- }
914
- legend.appendChild(more);
935
+ const legend = heatmapLegend(opts.labels);
915
936
  const columns = Math.max(1, Math.ceil((pad + entries.length) / 7));
916
937
  months.style.setProperty("--columns", String(columns));
917
938
  wrap.append(months, weekdayRow, grid, legend);
918
939
  body.appendChild(wrap);
919
940
  });
941
+ attachChartInteraction(el, "calendar");
942
+ return el;
920
943
  }
921
944
  function renderScatter(el, opts) {
922
- if (typeof document === "undefined") return;
945
+ if (typeof document === "undefined") return el;
923
946
  withChrome(el, opts, false, (body) => {
924
947
  const pts = opts.points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
925
948
  if (pts.length === 0) {
@@ -964,7 +987,7 @@ function renderScatter(el, opts) {
964
987
  "font-size": "8",
965
988
  fill: "var(--token-text-faint)"
966
989
  });
967
- txt.textContent = String(Math.round(tv * 10) / 10);
990
+ txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
968
991
  svg.appendChild(txt);
969
992
  }
970
993
  for (let i = 0; i <= 4; i++) {
@@ -986,7 +1009,7 @@ function renderScatter(el, opts) {
986
1009
  "font-size": "8",
987
1010
  fill: "var(--token-text-faint)"
988
1011
  });
989
- txt.textContent = String(Math.round(tv * 10) / 10);
1012
+ txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
990
1013
  svg.appendChild(txt);
991
1014
  }
992
1015
  if (opts.yLabel) {
@@ -1010,7 +1033,7 @@ function renderScatter(el, opts) {
1010
1033
  "stroke-width": "1"
1011
1034
  });
1012
1035
  const title = svgEl("title", {});
1013
- title.textContent = `${p.label ?? `#${i + 1}`}: (${p.x}, ${p.y}${p.size != null ? `, size ${p.size}` : ""})`;
1036
+ title.textContent = `${p.label ?? `#${i + 1}`}: (${fmt(opts, p.x)}, ${fmt(opts, p.y)}${p.size != null ? `, size ${fmt(opts, p.size)}` : ""})`;
1014
1037
  c.appendChild(title);
1015
1038
  setMarkData(c, { index: i, label: p.label ?? `#${i + 1}`, value: p.y });
1016
1039
  c.dataset.chartX = String(p.x);
@@ -1019,6 +1042,8 @@ function renderScatter(el, opts) {
1019
1042
  wrap.appendChild(svg);
1020
1043
  body.appendChild(wrap);
1021
1044
  });
1045
+ attachChartInteraction(el, "scatter");
1046
+ return el;
1022
1047
  }
1023
1048
  function chartFormatValue(format) {
1024
1049
  if (!format) return (v) => String(v);
@@ -1149,7 +1174,7 @@ function normalizeChartSpec(raw) {
1149
1174
  }
1150
1175
  }
1151
1176
  if (!src || typeof src !== "object" || Array.isArray(src)) {
1152
- if (Array.isArray(raw)) src = { data: raw };
1177
+ if (Array.isArray(src)) src = { data: src };
1153
1178
  else return {};
1154
1179
  }
1155
1180
  const s = src;
@@ -1219,6 +1244,7 @@ function normalizeChartSpec(raw) {
1219
1244
  }
1220
1245
  return spec;
1221
1246
  }
1247
+ var TEMPORAL_HIT_RATIO = 0.6;
1222
1248
  function looksTemporal(labels) {
1223
1249
  if (labels.length === 0) return false;
1224
1250
  let hits = 0;
@@ -1232,7 +1258,7 @@ function looksTemporal(labels) {
1232
1258
  else if (/^(v|ver\.?|version[ .]?)?\d+(\.\d+)+$/i.test(l)) hits++;
1233
1259
  else if (/^\d{4}$/.test(l)) hits++;
1234
1260
  }
1235
- return hits / labels.length >= 0.6;
1261
+ return hits / labels.length >= TEMPORAL_HIT_RATIO;
1236
1262
  }
1237
1263
  function inferChartType(spec) {
1238
1264
  if (spec.points && spec.points.length > 0) return "scatter";
@@ -1267,7 +1293,7 @@ function dispatchChartEvent(root, type, mark, chart, phase, pointer) {
1267
1293
  pointerX: pointer?.x,
1268
1294
  pointerY: pointer?.y
1269
1295
  };
1270
- root.dispatchEvent(new CustomEvent(`icen:chart-${type}`, { detail, bubbles: true }));
1296
+ emitIcen(root, `icen:chart-${type}`, detail);
1271
1297
  }
1272
1298
  var tooltipEl = null;
1273
1299
  function chartTooltip() {
@@ -1280,6 +1306,12 @@ function chartTooltip() {
1280
1306
  }
1281
1307
  return tooltipEl;
1282
1308
  }
1309
+ function applyTooltipSizing(host) {
1310
+ const tip = chartTooltip();
1311
+ const raw = host?.closest(".chart, [data-chart-root]")?.getAttribute("data-panel-max") ?? null;
1312
+ const n = raw != null ? Number(raw) : NaN;
1313
+ tip.style.maxWidth = Number.isFinite(n) && n > 0 ? `${n}px` : "";
1314
+ }
1283
1315
  function tooltipText(mark) {
1284
1316
  const label = mark.getAttribute("data-chart-label") ?? "";
1285
1317
  const series = mark.getAttribute("data-chart-series-name");
@@ -1299,6 +1331,101 @@ function moveTooltip(x, y) {
1299
1331
  tip.style.left = `${Math.round(left)}px`;
1300
1332
  tip.style.top = `${Math.round(top)}px`;
1301
1333
  }
1334
+ var chartDelegateMeta = /* @__PURE__ */ new WeakMap();
1335
+ var chartDelegateUnbind = /* @__PURE__ */ new WeakMap();
1336
+ function bindChartEvents(root) {
1337
+ const noop = () => void 0;
1338
+ if (typeof document === "undefined") return noop;
1339
+ const attached = root;
1340
+ if (attached.__icenChartBound) {
1341
+ const prev = chartDelegateUnbind.get(root);
1342
+ if (prev) return prev;
1343
+ }
1344
+ for (let p = root.parentElement; p; p = p.parentElement) {
1345
+ if (p.__icenChartBound) return noop;
1346
+ }
1347
+ const typeNow = () => chartDelegateMeta.get(root)?.type() ?? "vbar";
1348
+ const tipEnabled = () => chartDelegateMeta.get(root)?.tooltip() ?? true;
1349
+ const markOf = (e) => {
1350
+ const t = e.target;
1351
+ return t instanceof Element ? t.closest("[data-chart-mark]") : null;
1352
+ };
1353
+ let hoverMark = null;
1354
+ const enterMark = (mark, x, y) => {
1355
+ hoverMark = mark;
1356
+ mark.classList.add("is-hover");
1357
+ dispatchChartEvent(root, "hover", mark, typeNow(), "enter", { x, y });
1358
+ if (tipEnabled()) {
1359
+ const tip = chartTooltip();
1360
+ tip.textContent = tooltipText(mark);
1361
+ applyTooltipSizing(mark);
1362
+ tip.hidden = false;
1363
+ moveTooltip(x, y);
1364
+ }
1365
+ };
1366
+ const leaveMark = (pointer) => {
1367
+ if (!hoverMark) return;
1368
+ hoverMark.classList.remove("is-hover");
1369
+ dispatchChartEvent(root, "hover", hoverMark, typeNow(), "leave", pointer);
1370
+ hoverMark = null;
1371
+ chartTooltip().hidden = true;
1372
+ };
1373
+ const bindings = [];
1374
+ const on = (type, fn) => {
1375
+ const listener = (evt) => fn(evt);
1376
+ root.addEventListener(type, listener);
1377
+ bindings.push([type, listener]);
1378
+ };
1379
+ on("pointerover", (e) => {
1380
+ const mark = markOf(e);
1381
+ if (!mark || mark === hoverMark) return;
1382
+ leaveMark({ x: e.clientX, y: e.clientY });
1383
+ enterMark(mark, e.clientX, e.clientY);
1384
+ });
1385
+ on("pointermove", (e) => {
1386
+ if (hoverMark && tipEnabled()) {
1387
+ dispatchChartEvent(root, "hover", hoverMark, typeNow(), "move", { x: e.clientX, y: e.clientY });
1388
+ moveTooltip(e.clientX, e.clientY);
1389
+ }
1390
+ });
1391
+ on("pointerout", (e) => {
1392
+ const mark = markOf(e);
1393
+ if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
1394
+ });
1395
+ on("pointerleave", () => leaveMark());
1396
+ on("click", (e) => {
1397
+ const mark = markOf(e);
1398
+ if (mark) dispatchChartEvent(root, "click", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
1399
+ });
1400
+ on("dblclick", (e) => {
1401
+ const mark = markOf(e);
1402
+ if (mark) dispatchChartEvent(root, "dblclick", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
1403
+ });
1404
+ on("contextmenu", (e) => {
1405
+ const mark = markOf(e);
1406
+ if (!mark) return;
1407
+ e.preventDefault();
1408
+ dispatchChartEvent(root, "contextmenu", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
1409
+ });
1410
+ const unbind = () => {
1411
+ for (const [type, listener] of bindings) root.removeEventListener(type, listener);
1412
+ bindings.length = 0;
1413
+ attached.__icenChartBound = false;
1414
+ chartDelegateUnbind.delete(root);
1415
+ if (hoverMark) {
1416
+ hoverMark.classList.remove("is-hover");
1417
+ hoverMark = null;
1418
+ }
1419
+ chartTooltip().hidden = true;
1420
+ };
1421
+ attached.__icenChartBound = true;
1422
+ chartDelegateUnbind.set(root, unbind);
1423
+ return unbind;
1424
+ }
1425
+ function attachChartInteraction(el, type) {
1426
+ chartDelegateMeta.set(el, { type: () => type, tooltip: () => true });
1427
+ bindChartEvents(el);
1428
+ }
1302
1429
  function hasLegendOf(spec) {
1303
1430
  switch (spec.type) {
1304
1431
  case "donut":
@@ -1321,9 +1448,6 @@ function renderChart(el, raw) {
1321
1448
  return { el, spec: {}, update: noop, on: () => noop, destroy: noop };
1322
1449
  }
1323
1450
  const root = el;
1324
- const attached = root;
1325
- const wasBound = attached.__icenChartBound === true;
1326
- attached.__icenChartBound = true;
1327
1451
  let current = normalizeChartSpec(raw);
1328
1452
  const typeOf = () => current.type ?? "vbar";
1329
1453
  const legendState = { hidden: null };
@@ -1358,10 +1482,10 @@ function renderChart(el, raw) {
1358
1482
  break;
1359
1483
  }
1360
1484
  case "heatmap":
1361
- renderHeatmap(body, { values: spec.values, data: spec.data, weeks: spec.weeks, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1485
+ renderHeatmap(body, { values: spec.values, data: spec.data, weeks: spec.weeks, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend, weekStart: spec.i18n?.weekStart, labels: spec.i18n });
1362
1486
  break;
1363
1487
  case "calendar":
1364
- renderCalendar(body, { dates: spec.dates, values: spec.values, data: spec.data, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1488
+ renderCalendar(body, { dates: spec.dates, values: spec.values, data: spec.data, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend, weekStart: spec.i18n?.weekStart, labels: spec.i18n });
1365
1489
  break;
1366
1490
  case "sparkline":
1367
1491
  renderSparkline(body, { labels: spec.labels ?? [], values: spec.values ?? [], tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
@@ -1377,63 +1501,8 @@ function renderChart(el, raw) {
1377
1501
  }
1378
1502
  });
1379
1503
  };
1380
- if (!wasBound) {
1381
- const markOf = (e) => {
1382
- const t = e.target;
1383
- return t instanceof Element ? t.closest("[data-chart-mark]") : null;
1384
- };
1385
- const tipEnabled = () => current.tooltip !== false;
1386
- let hoverMark = null;
1387
- const enterMark = (mark, x, y) => {
1388
- hoverMark = mark;
1389
- mark.classList.add("is-hover");
1390
- dispatchChartEvent(root, "hover", mark, typeOf(), "enter", { x, y });
1391
- if (tipEnabled()) {
1392
- const tip = chartTooltip();
1393
- tip.textContent = tooltipText(mark);
1394
- tip.hidden = false;
1395
- moveTooltip(x, y);
1396
- }
1397
- };
1398
- const leaveMark = (pointer) => {
1399
- if (!hoverMark) return;
1400
- hoverMark.classList.remove("is-hover");
1401
- dispatchChartEvent(root, "hover", hoverMark, typeOf(), "leave", pointer);
1402
- hoverMark = null;
1403
- chartTooltip().hidden = true;
1404
- };
1405
- root.addEventListener("pointerover", (e) => {
1406
- const mark = markOf(e);
1407
- if (!mark || mark === hoverMark) return;
1408
- leaveMark({ x: e.clientX, y: e.clientY });
1409
- enterMark(mark, e.clientX, e.clientY);
1410
- });
1411
- root.addEventListener("pointermove", (e) => {
1412
- if (hoverMark && tipEnabled()) {
1413
- dispatchChartEvent(root, "hover", hoverMark, typeOf(), "move", { x: e.clientX, y: e.clientY });
1414
- moveTooltip(e.clientX, e.clientY);
1415
- }
1416
- });
1417
- root.addEventListener("pointerout", (e) => {
1418
- const mark = markOf(e);
1419
- if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
1420
- });
1421
- root.addEventListener("pointerleave", () => leaveMark());
1422
- root.addEventListener("click", (e) => {
1423
- const mark = markOf(e);
1424
- if (mark) dispatchChartEvent(root, "click", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
1425
- });
1426
- root.addEventListener("dblclick", (e) => {
1427
- const mark = markOf(e);
1428
- if (mark) dispatchChartEvent(root, "dblclick", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
1429
- });
1430
- root.addEventListener("contextmenu", (e) => {
1431
- const mark = markOf(e);
1432
- if (!mark) return;
1433
- e.preventDefault();
1434
- dispatchChartEvent(root, "contextmenu", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
1435
- });
1436
- }
1504
+ chartDelegateMeta.set(root, { type: typeOf, tooltip: () => current.tooltip !== false });
1505
+ const unbindEvents = bindChartEvents(root);
1437
1506
  render();
1438
1507
  return {
1439
1508
  el: root,
@@ -1451,23 +1520,820 @@ function renderChart(el, raw) {
1451
1520
  },
1452
1521
  destroy() {
1453
1522
  root.textContent = "";
1454
- attached.__icenChartBound = false;
1523
+ unbindEvents();
1524
+ chartTooltip().hidden = true;
1525
+ }
1526
+ };
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();
1455
2316
  chartTooltip().hidden = true;
1456
2317
  }
1457
2318
  };
1458
2319
  }
1459
2320
  export {
2321
+ bindChartEvents,
1460
2322
  chartFormatValue,
1461
2323
  inferChartType,
2324
+ layoutGraph,
1462
2325
  normalizeChartSpec,
2326
+ registerChartTone,
1463
2327
  renderArea,
1464
2328
  renderCalendar,
1465
2329
  renderChart,
1466
2330
  renderDonut,
1467
2331
  renderGauge,
2332
+ renderGraph,
1468
2333
  renderHBar,
1469
2334
  renderHeatmap,
1470
2335
  renderLine,
2336
+ renderMap,
1471
2337
  renderRadar,
1472
2338
  renderScatter,
1473
2339
  renderSparkline,