@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,16 @@
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();
3
11
  function toneVar(tone = "accent") {
12
+ const registered = REGISTERED_TONES.get(tone);
13
+ if (registered) return registered;
4
14
  switch (tone) {
5
15
  case "success":
6
16
  return "var(--token-success)";
@@ -154,17 +164,14 @@ function withChrome(el, opts, hasLegend, paint, state) {
154
164
  if (state) state.hidden = hidden;
155
165
  el.setAttribute("data-legend-hidden", String(hidden));
156
166
  syncEye();
157
- el.dispatchEvent(new CustomEvent("icen:chart-legend-visibility", {
158
- bubbles: true,
159
- detail: { visible: hidden ? false : true }
160
- }));
167
+ emitIcen(el, "icen:chart-legend-visibility", { visible: hidden ? false : true });
161
168
  });
162
169
  syncEye();
163
170
  head.append(title, eye);
164
171
  el.append(head, body);
165
172
  paint(body);
166
173
  }
167
- function toggleLegend(items, scope, _opts) {
174
+ function toggleLegend(items, scope) {
168
175
  const legend = document.createElement("div");
169
176
  legend.className = "chart-legend chart-legend--toggle";
170
177
  for (const item of items) {
@@ -183,10 +190,7 @@ function toggleLegend(items, scope, _opts) {
183
190
  scope.querySelectorAll(`[data-chart-key="${CSS.escape(item.key)}"]`).forEach((m) => {
184
191
  m.classList.toggle("is-off", off);
185
192
  });
186
- legend.dispatchEvent(new CustomEvent("icen:chart-legend-toggle", {
187
- bubbles: true,
188
- detail: { key: item.key, seriesIndex: item.si, hidden: off }
189
- }));
193
+ emitIcen(legend, "icen:chart-legend-toggle", { key: item.key, seriesIndex: item.si, hidden: off });
190
194
  };
191
195
  row.addEventListener("click", toggle);
192
196
  row.addEventListener("keydown", (e) => {
@@ -205,7 +209,7 @@ function toggleLegend(items, scope, _opts) {
205
209
  return legend;
206
210
  }
207
211
  function renderVBar(el, opts) {
208
- if (typeof document === "undefined") return;
212
+ if (typeof document === "undefined") return el;
209
213
  const seriesList = normalizeSeriesInput(opts);
210
214
  const labels = opts.labels ?? [];
211
215
  const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
@@ -256,11 +260,13 @@ function renderVBar(el, opts) {
256
260
  wrap.appendChild(track);
257
261
  }
258
262
  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));
263
+ if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap));
260
264
  });
265
+ attachChartInteraction(el, "vbar");
266
+ return el;
261
267
  }
262
268
  function renderHBar(el, opts) {
263
- if (typeof document === "undefined") return;
269
+ if (typeof document === "undefined") return el;
264
270
  withChrome(el, opts, false, (body) => {
265
271
  const n = Math.min(opts.labels.length, opts.values.length);
266
272
  if (n === 0) {
@@ -294,9 +300,11 @@ function renderHBar(el, opts) {
294
300
  });
295
301
  body.appendChild(wrap);
296
302
  });
303
+ attachChartInteraction(el, "hbar");
304
+ return el;
297
305
  }
298
306
  function renderStack(el, opts) {
299
- if (typeof document === "undefined") return;
307
+ if (typeof document === "undefined") return el;
300
308
  withChrome(el, opts, opts.segments.length > 0, (body) => {
301
309
  const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
302
310
  if (opts.segments.length === 0 || total <= 0) {
@@ -305,13 +313,13 @@ function renderStack(el, opts) {
305
313
  }
306
314
  const bar = document.createElement("div");
307
315
  bar.className = "chart-stack";
308
- for (const seg of opts.segments) {
316
+ for (const [segIndex, seg] of opts.segments.entries()) {
309
317
  const s = document.createElement("div");
310
318
  s.className = "chart-stack-seg";
311
319
  s.style.width = `${seg.value / total * 100}%`;
312
320
  if (seg.tone) s.style.setProperty("--chart-tone", toneVar(seg.tone));
313
321
  s.setAttribute("title", `${seg.label}: ${fmt(opts, seg.value)}`);
314
- setMarkData(s, { index: opts.segments.indexOf(seg), label: seg.label, value: seg.value });
322
+ setMarkData(s, { index: segIndex, label: seg.label, value: seg.value });
315
323
  bar.appendChild(s);
316
324
  }
317
325
  const legend = document.createElement("div");
@@ -321,9 +329,11 @@ function renderStack(el, opts) {
321
329
  }
322
330
  body.append(bar, legend);
323
331
  });
332
+ attachChartInteraction(el, "stack");
333
+ return el;
324
334
  }
325
335
  function renderDonut(el, opts) {
326
- if (typeof document === "undefined") return;
336
+ if (typeof document === "undefined") return el;
327
337
  withChrome(el, opts, opts.segments.length > 0, (body) => {
328
338
  const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
329
339
  if (opts.segments.length === 0 || total <= 0) {
@@ -370,10 +380,12 @@ function renderDonut(el, opts) {
370
380
  wrap.append(svg, legend);
371
381
  body.appendChild(wrap);
372
382
  });
383
+ attachChartInteraction(el, "donut");
384
+ return el;
373
385
  }
374
386
  var lineGradientSeq = 0;
375
387
  function renderLine(el, opts) {
376
- if (typeof document === "undefined") return;
388
+ if (typeof document === "undefined") return el;
377
389
  const seriesList = normalizeSeriesInput(opts);
378
390
  const labels = opts.labels ?? [];
379
391
  const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
@@ -478,11 +490,13 @@ function renderLine(el, opts) {
478
490
  if (defs.firstChild) svg.insertBefore(defs, svg.firstChild);
479
491
  wrap.append(svg, labelsRow(sampleLabels(labelsUsed)));
480
492
  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));
493
+ if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone, si })), wrap));
482
494
  });
495
+ attachChartInteraction(el, "line");
496
+ return el;
483
497
  }
484
498
  function renderRadar(el, opts) {
485
- if (typeof document === "undefined") return;
499
+ if (typeof document === "undefined") return el;
486
500
  withChrome(el, opts, opts.series.length > 1, (body) => {
487
501
  const axes = opts.axes;
488
502
  const n = axes.length;
@@ -584,13 +598,12 @@ function renderRadar(el, opts) {
584
598
  }
585
599
  legendItems.push({ key: s.name || `\u7CFB\u5217${si + 1}`, label: s.name || `\u7CFB\u5217${si + 1}`, tone: s.tone, si });
586
600
  });
587
- wrap.append(svg, opts.series.length > 1 ? toggleLegend(legendItems, wrap, opts) : (() => {
588
- const l = document.createElement("div");
589
- l.className = "chart-legend";
590
- return l;
591
- })());
601
+ const legend = opts.series.length > 1 ? toggleLegend(legendItems, wrap) : null;
602
+ wrap.append(svg, ...legend ? [legend] : []);
592
603
  body.appendChild(wrap);
593
604
  });
605
+ attachChartInteraction(el, "radar");
606
+ return el;
594
607
  }
595
608
  function niceCeil(v) {
596
609
  if (v <= 0) return 1;
@@ -609,8 +622,41 @@ function isoDate(d) {
609
622
  const day = String(d.getDate()).padStart(2, "0");
610
623
  return `${d.getFullYear()}-${m}-${day}`;
611
624
  }
625
+ function weekdayLabelAt(defaults, day) {
626
+ return defaults[(day + 6) % 7] ?? "";
627
+ }
628
+ function levelOf(v, max) {
629
+ return v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
630
+ }
631
+ function padFirstWeek(grid, firstDate, weekStart) {
632
+ const first = /* @__PURE__ */ new Date(`${firstDate}T00:00:00`);
633
+ const pad = (first.getDay() + 7 - weekStart) % 7;
634
+ for (let i = 0; i < pad; i++) {
635
+ const blank = document.createElement("span");
636
+ blank.className = "chart-heatmap-cell is-blank";
637
+ grid.appendChild(blank);
638
+ }
639
+ return pad;
640
+ }
641
+ function heatmapLegend(labels) {
642
+ const legend = document.createElement("div");
643
+ legend.className = "chart-heatmap-legend";
644
+ const less = document.createElement("span");
645
+ less.textContent = labels?.less ?? "\u5C11";
646
+ const more = document.createElement("span");
647
+ more.textContent = labels?.more ?? "\u591A";
648
+ legend.appendChild(less);
649
+ for (let lv = 0; lv <= 4; lv++) {
650
+ const cell = document.createElement("span");
651
+ cell.className = "chart-heatmap-cell";
652
+ cell.dataset.level = String(lv);
653
+ legend.appendChild(cell);
654
+ }
655
+ legend.appendChild(more);
656
+ return legend;
657
+ }
612
658
  function renderHeatmap(el, opts) {
613
- if (typeof document === "undefined") return;
659
+ if (typeof document === "undefined") return el;
614
660
  withChrome(el, opts, true, (body) => {
615
661
  let entries;
616
662
  if (opts.data && opts.data.length > 0) {
@@ -629,48 +675,29 @@ function renderHeatmap(el, opts) {
629
675
  const weeks = opts.weeks ?? Math.ceil(entries.length / 7);
630
676
  if (entries.length > weeks * 7) entries = entries.slice(entries.length - weeks * 7);
631
677
  const max = maxOf(entries.map((e) => e.value));
632
- const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
633
678
  const wrap = document.createElement("div");
634
679
  wrap.className = "chart-heatmap";
635
680
  if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
636
681
  const grid = document.createElement("div");
637
682
  grid.className = "chart-heatmap-grid";
638
683
  grid.setAttribute("role", "img");
639
- const first = /* @__PURE__ */ new Date(`${entries[0].date}T00:00:00`);
640
- const pad = (first.getDay() + 6) % 7;
641
- for (let i = 0; i < pad; i++) {
642
- const blank = document.createElement("span");
643
- blank.className = "chart-heatmap-cell is-blank";
644
- grid.appendChild(blank);
645
- }
646
- for (const entry of entries) {
684
+ padFirstWeek(grid, entries[0].date, opts.weekStart === 0 ? 0 : 1);
685
+ for (const [i, entry] of entries.entries()) {
647
686
  const cell = document.createElement("span");
648
687
  cell.className = "chart-heatmap-cell";
649
- cell.dataset.level = String(levelOf(entry.value));
688
+ cell.dataset.level = String(levelOf(entry.value, max));
650
689
  cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
651
- setMarkData(cell, { index: entries.indexOf(entry), label: entry.date, value: entry.value });
690
+ setMarkData(cell, { index: i, label: entry.date, value: entry.value });
652
691
  grid.appendChild(cell);
653
692
  }
654
- const legend = document.createElement("div");
655
- legend.className = "chart-heatmap-legend";
656
- const less = document.createElement("span");
657
- less.textContent = "\u5C11";
658
- const more = document.createElement("span");
659
- more.textContent = "\u591A";
660
- legend.appendChild(less);
661
- for (let lv = 0; lv <= 4; lv++) {
662
- const cell = document.createElement("span");
663
- cell.className = "chart-heatmap-cell";
664
- cell.dataset.level = String(lv);
665
- legend.appendChild(cell);
666
- }
667
- legend.appendChild(more);
668
- wrap.append(grid, legend);
693
+ wrap.append(grid, heatmapLegend(opts.labels));
669
694
  body.appendChild(wrap);
670
695
  });
696
+ attachChartInteraction(el, "heatmap");
697
+ return el;
671
698
  }
672
699
  function renderSparkline(el, opts) {
673
- if (typeof document === "undefined") return;
700
+ if (typeof document === "undefined") return el;
674
701
  withChrome(el, opts, false, (body) => {
675
702
  const values = opts.values;
676
703
  if (values.length === 0) {
@@ -725,9 +752,11 @@ function renderSparkline(el, opts) {
725
752
  wrap.appendChild(svg);
726
753
  body.appendChild(wrap);
727
754
  });
755
+ attachChartInteraction(el, "sparkline");
756
+ return el;
728
757
  }
729
758
  function renderGauge(el, opts) {
730
- if (typeof document === "undefined") return;
759
+ if (typeof document === "undefined") return el;
731
760
  el.textContent = "";
732
761
  const max = opts.max ?? 100;
733
762
  const ratio = max > 0 ? Math.min(1, Math.max(0, opts.value / max)) : 0;
@@ -771,9 +800,11 @@ function renderGauge(el, opts) {
771
800
  }
772
801
  wrap.append(svg, center);
773
802
  el.appendChild(wrap);
803
+ attachChartInteraction(el, "gauge");
804
+ return el;
774
805
  }
775
806
  function renderCalendar(el, opts) {
776
- if (typeof document === "undefined") return;
807
+ if (typeof document === "undefined") return el;
777
808
  withChrome(el, opts, true, (body) => {
778
809
  let entries;
779
810
  if (opts.data && opts.data.length > 0) {
@@ -787,6 +818,10 @@ function renderCalendar(el, opts) {
787
818
  const wrap = document.createElement("div");
788
819
  wrap.className = "chart-calendar";
789
820
  if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
821
+ const weekStart = opts.weekStart === 0 ? 0 : 1;
822
+ const weekdayDefaults = ["\u4E00", "", "\u4E09", "", "\u4E94", "", "\u65E5"];
823
+ const weekdayLabels = opts.labels?.weekdays ?? weekdayDefaults.map((_, i) => weekdayLabelAt(weekdayDefaults, (weekStart + i) % 7));
824
+ const monthLabel = (m) => opts.labels?.months?.[m] ?? `${m + 1}\u6708`;
790
825
  const grid = document.createElement("div");
791
826
  grid.className = "chart-heatmap-grid";
792
827
  grid.setAttribute("role", "img");
@@ -794,20 +829,13 @@ function renderCalendar(el, opts) {
794
829
  months.className = "chart-calendar-months";
795
830
  const weekdayRow = document.createElement("div");
796
831
  weekdayRow.className = "chart-calendar-weekdays";
797
- ["\u4E00", "", "\u4E09", "", "\u4E94", "", "\u65E5"].forEach((w) => {
832
+ weekdayLabels.forEach((w) => {
798
833
  const s = document.createElement("span");
799
834
  s.textContent = w;
800
835
  weekdayRow.appendChild(s);
801
836
  });
802
837
  const max = maxOf(entries.map((e) => e.value));
803
- const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
804
- const first = /* @__PURE__ */ new Date(`${entries[0].date}T00:00:00`);
805
- const pad = (first.getDay() + 6) % 7;
806
- for (let i = 0; i < pad; i++) {
807
- const blank = document.createElement("span");
808
- blank.className = "chart-heatmap-cell is-blank";
809
- grid.appendChild(blank);
810
- }
838
+ const pad = padFirstWeek(grid, entries[0].date, weekStart);
811
839
  let lastMonth = -1;
812
840
  entries.forEach((entry, i) => {
813
841
  const d = /* @__PURE__ */ new Date(`${entry.date}T00:00:00`);
@@ -816,39 +844,28 @@ function renderCalendar(el, opts) {
816
844
  lastMonth = d.getMonth();
817
845
  const label = document.createElement("span");
818
846
  label.className = "chart-calendar-month";
819
- label.textContent = `${d.getMonth() + 1}\u6708`;
847
+ label.textContent = monthLabel(d.getMonth());
820
848
  label.style.setProperty("--col", String(col));
821
849
  months.appendChild(label);
822
850
  }
823
851
  const cell = document.createElement("span");
824
852
  cell.className = "chart-heatmap-cell";
825
- cell.dataset.level = String(levelOf(entry.value));
853
+ cell.dataset.level = String(levelOf(entry.value, max));
826
854
  cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
827
855
  setMarkData(cell, { index: i, label: entry.date, value: entry.value });
828
856
  grid.appendChild(cell);
829
857
  });
830
- const legend = document.createElement("div");
831
- legend.className = "chart-heatmap-legend";
832
- const less = document.createElement("span");
833
- less.textContent = "\u5C11";
834
- const more = document.createElement("span");
835
- more.textContent = "\u591A";
836
- legend.appendChild(less);
837
- for (let lv = 0; lv <= 4; lv++) {
838
- const cell = document.createElement("span");
839
- cell.className = "chart-heatmap-cell";
840
- cell.dataset.level = String(lv);
841
- legend.appendChild(cell);
842
- }
843
- legend.appendChild(more);
858
+ const legend = heatmapLegend(opts.labels);
844
859
  const columns = Math.max(1, Math.ceil((pad + entries.length) / 7));
845
860
  months.style.setProperty("--columns", String(columns));
846
861
  wrap.append(months, weekdayRow, grid, legend);
847
862
  body.appendChild(wrap);
848
863
  });
864
+ attachChartInteraction(el, "calendar");
865
+ return el;
849
866
  }
850
867
  function renderScatter(el, opts) {
851
- if (typeof document === "undefined") return;
868
+ if (typeof document === "undefined") return el;
852
869
  withChrome(el, opts, false, (body) => {
853
870
  const pts = opts.points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
854
871
  if (pts.length === 0) {
@@ -893,7 +910,7 @@ function renderScatter(el, opts) {
893
910
  "font-size": "8",
894
911
  fill: "var(--token-text-faint)"
895
912
  });
896
- txt.textContent = String(Math.round(tv * 10) / 10);
913
+ txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
897
914
  svg.appendChild(txt);
898
915
  }
899
916
  for (let i = 0; i <= 4; i++) {
@@ -915,7 +932,7 @@ function renderScatter(el, opts) {
915
932
  "font-size": "8",
916
933
  fill: "var(--token-text-faint)"
917
934
  });
918
- txt.textContent = String(Math.round(tv * 10) / 10);
935
+ txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
919
936
  svg.appendChild(txt);
920
937
  }
921
938
  if (opts.yLabel) {
@@ -939,7 +956,7 @@ function renderScatter(el, opts) {
939
956
  "stroke-width": "1"
940
957
  });
941
958
  const title = svgEl("title", {});
942
- title.textContent = `${p.label ?? `#${i + 1}`}: (${p.x}, ${p.y}${p.size != null ? `, size ${p.size}` : ""})`;
959
+ title.textContent = `${p.label ?? `#${i + 1}`}: (${fmt(opts, p.x)}, ${fmt(opts, p.y)}${p.size != null ? `, size ${fmt(opts, p.size)}` : ""})`;
943
960
  c.appendChild(title);
944
961
  setMarkData(c, { index: i, label: p.label ?? `#${i + 1}`, value: p.y });
945
962
  c.dataset.chartX = String(p.x);
@@ -948,6 +965,8 @@ function renderScatter(el, opts) {
948
965
  wrap.appendChild(svg);
949
966
  body.appendChild(wrap);
950
967
  });
968
+ attachChartInteraction(el, "scatter");
969
+ return el;
951
970
  }
952
971
  function chartFormatValue(format) {
953
972
  if (!format) return (v) => String(v);
@@ -1078,7 +1097,7 @@ function normalizeChartSpec(raw) {
1078
1097
  }
1079
1098
  }
1080
1099
  if (!src || typeof src !== "object" || Array.isArray(src)) {
1081
- if (Array.isArray(raw)) src = { data: raw };
1100
+ if (Array.isArray(src)) src = { data: src };
1082
1101
  else return {};
1083
1102
  }
1084
1103
  const s = src;
@@ -1148,6 +1167,7 @@ function normalizeChartSpec(raw) {
1148
1167
  }
1149
1168
  return spec;
1150
1169
  }
1170
+ var TEMPORAL_HIT_RATIO = 0.6;
1151
1171
  function looksTemporal(labels) {
1152
1172
  if (labels.length === 0) return false;
1153
1173
  let hits = 0;
@@ -1161,7 +1181,7 @@ function looksTemporal(labels) {
1161
1181
  else if (/^(v|ver\.?|version[ .]?)?\d+(\.\d+)+$/i.test(l)) hits++;
1162
1182
  else if (/^\d{4}$/.test(l)) hits++;
1163
1183
  }
1164
- return hits / labels.length >= 0.6;
1184
+ return hits / labels.length >= TEMPORAL_HIT_RATIO;
1165
1185
  }
1166
1186
  function inferChartType(spec) {
1167
1187
  if (spec.points && spec.points.length > 0) return "scatter";
@@ -1196,7 +1216,7 @@ function dispatchChartEvent(root, type, mark, chart, phase, pointer) {
1196
1216
  pointerX: pointer?.x,
1197
1217
  pointerY: pointer?.y
1198
1218
  };
1199
- root.dispatchEvent(new CustomEvent(`icen:chart-${type}`, { detail, bubbles: true }));
1219
+ emitIcen(root, `icen:chart-${type}`, detail);
1200
1220
  }
1201
1221
  var tooltipEl = null;
1202
1222
  function chartTooltip() {
@@ -1209,6 +1229,12 @@ function chartTooltip() {
1209
1229
  }
1210
1230
  return tooltipEl;
1211
1231
  }
1232
+ function applyTooltipSizing(host) {
1233
+ const tip = chartTooltip();
1234
+ const raw = host?.closest(".chart, [data-chart-root]")?.getAttribute("data-panel-max") ?? null;
1235
+ const n = raw != null ? Number(raw) : NaN;
1236
+ tip.style.maxWidth = Number.isFinite(n) && n > 0 ? `${n}px` : "";
1237
+ }
1212
1238
  function tooltipText(mark) {
1213
1239
  const label = mark.getAttribute("data-chart-label") ?? "";
1214
1240
  const series = mark.getAttribute("data-chart-series-name");
@@ -1228,6 +1254,101 @@ function moveTooltip(x, y) {
1228
1254
  tip.style.left = `${Math.round(left)}px`;
1229
1255
  tip.style.top = `${Math.round(top)}px`;
1230
1256
  }
1257
+ var chartDelegateMeta = /* @__PURE__ */ new WeakMap();
1258
+ var chartDelegateUnbind = /* @__PURE__ */ new WeakMap();
1259
+ function bindChartEvents(root) {
1260
+ const noop = () => void 0;
1261
+ if (typeof document === "undefined") return noop;
1262
+ const attached = root;
1263
+ if (attached.__icenChartBound) {
1264
+ const prev = chartDelegateUnbind.get(root);
1265
+ if (prev) return prev;
1266
+ }
1267
+ for (let p = root.parentElement; p; p = p.parentElement) {
1268
+ if (p.__icenChartBound) return noop;
1269
+ }
1270
+ const typeNow = () => chartDelegateMeta.get(root)?.type() ?? "vbar";
1271
+ const tipEnabled = () => chartDelegateMeta.get(root)?.tooltip() ?? true;
1272
+ const markOf = (e) => {
1273
+ const t = e.target;
1274
+ return t instanceof Element ? t.closest("[data-chart-mark]") : null;
1275
+ };
1276
+ let hoverMark = null;
1277
+ const enterMark = (mark, x, y) => {
1278
+ hoverMark = mark;
1279
+ mark.classList.add("is-hover");
1280
+ dispatchChartEvent(root, "hover", mark, typeNow(), "enter", { x, y });
1281
+ if (tipEnabled()) {
1282
+ const tip = chartTooltip();
1283
+ tip.textContent = tooltipText(mark);
1284
+ applyTooltipSizing(mark);
1285
+ tip.hidden = false;
1286
+ moveTooltip(x, y);
1287
+ }
1288
+ };
1289
+ const leaveMark = (pointer) => {
1290
+ if (!hoverMark) return;
1291
+ hoverMark.classList.remove("is-hover");
1292
+ dispatchChartEvent(root, "hover", hoverMark, typeNow(), "leave", pointer);
1293
+ hoverMark = null;
1294
+ chartTooltip().hidden = true;
1295
+ };
1296
+ const bindings = [];
1297
+ const on = (type, fn) => {
1298
+ const listener = (evt) => fn(evt);
1299
+ root.addEventListener(type, listener);
1300
+ bindings.push([type, listener]);
1301
+ };
1302
+ on("pointerover", (e) => {
1303
+ const mark = markOf(e);
1304
+ if (!mark || mark === hoverMark) return;
1305
+ leaveMark({ x: e.clientX, y: e.clientY });
1306
+ enterMark(mark, e.clientX, e.clientY);
1307
+ });
1308
+ on("pointermove", (e) => {
1309
+ if (hoverMark && tipEnabled()) {
1310
+ dispatchChartEvent(root, "hover", hoverMark, typeNow(), "move", { x: e.clientX, y: e.clientY });
1311
+ moveTooltip(e.clientX, e.clientY);
1312
+ }
1313
+ });
1314
+ on("pointerout", (e) => {
1315
+ const mark = markOf(e);
1316
+ if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
1317
+ });
1318
+ on("pointerleave", () => leaveMark());
1319
+ on("click", (e) => {
1320
+ const mark = markOf(e);
1321
+ if (mark) dispatchChartEvent(root, "click", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
1322
+ });
1323
+ on("dblclick", (e) => {
1324
+ const mark = markOf(e);
1325
+ if (mark) dispatchChartEvent(root, "dblclick", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
1326
+ });
1327
+ on("contextmenu", (e) => {
1328
+ const mark = markOf(e);
1329
+ if (!mark) return;
1330
+ e.preventDefault();
1331
+ dispatchChartEvent(root, "contextmenu", mark, typeNow(), void 0, { x: e.clientX, y: e.clientY });
1332
+ });
1333
+ const unbind = () => {
1334
+ for (const [type, listener] of bindings) root.removeEventListener(type, listener);
1335
+ bindings.length = 0;
1336
+ attached.__icenChartBound = false;
1337
+ chartDelegateUnbind.delete(root);
1338
+ if (hoverMark) {
1339
+ hoverMark.classList.remove("is-hover");
1340
+ hoverMark = null;
1341
+ }
1342
+ chartTooltip().hidden = true;
1343
+ };
1344
+ attached.__icenChartBound = true;
1345
+ chartDelegateUnbind.set(root, unbind);
1346
+ return unbind;
1347
+ }
1348
+ function attachChartInteraction(el, type) {
1349
+ chartDelegateMeta.set(el, { type: () => type, tooltip: () => true });
1350
+ bindChartEvents(el);
1351
+ }
1231
1352
  function hasLegendOf(spec) {
1232
1353
  switch (spec.type) {
1233
1354
  case "donut":
@@ -1250,9 +1371,6 @@ function renderChart(el, raw) {
1250
1371
  return { el, spec: {}, update: noop, on: () => noop, destroy: noop };
1251
1372
  }
1252
1373
  const root = el;
1253
- const attached = root;
1254
- const wasBound = attached.__icenChartBound === true;
1255
- attached.__icenChartBound = true;
1256
1374
  let current = normalizeChartSpec(raw);
1257
1375
  const typeOf = () => current.type ?? "vbar";
1258
1376
  const legendState = { hidden: null };
@@ -1287,10 +1405,10 @@ function renderChart(el, raw) {
1287
1405
  break;
1288
1406
  }
1289
1407
  case "heatmap":
1290
- renderHeatmap(body, { values: spec.values, data: spec.data, weeks: spec.weeks, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1408
+ 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 });
1291
1409
  break;
1292
1410
  case "calendar":
1293
- renderCalendar(body, { dates: spec.dates, values: spec.values, data: spec.data, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1411
+ 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 });
1294
1412
  break;
1295
1413
  case "sparkline":
1296
1414
  renderSparkline(body, { labels: spec.labels ?? [], values: spec.values ?? [], tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
@@ -1306,63 +1424,8 @@ function renderChart(el, raw) {
1306
1424
  }
1307
1425
  });
1308
1426
  };
1309
- if (!wasBound) {
1310
- const markOf = (e) => {
1311
- const t = e.target;
1312
- return t instanceof Element ? t.closest("[data-chart-mark]") : null;
1313
- };
1314
- const tipEnabled = () => current.tooltip !== false;
1315
- let hoverMark = null;
1316
- const enterMark = (mark, x, y) => {
1317
- hoverMark = mark;
1318
- mark.classList.add("is-hover");
1319
- dispatchChartEvent(root, "hover", mark, typeOf(), "enter", { x, y });
1320
- if (tipEnabled()) {
1321
- const tip = chartTooltip();
1322
- tip.textContent = tooltipText(mark);
1323
- tip.hidden = false;
1324
- moveTooltip(x, y);
1325
- }
1326
- };
1327
- const leaveMark = (pointer) => {
1328
- if (!hoverMark) return;
1329
- hoverMark.classList.remove("is-hover");
1330
- dispatchChartEvent(root, "hover", hoverMark, typeOf(), "leave", pointer);
1331
- hoverMark = null;
1332
- chartTooltip().hidden = true;
1333
- };
1334
- root.addEventListener("pointerover", (e) => {
1335
- const mark = markOf(e);
1336
- if (!mark || mark === hoverMark) return;
1337
- leaveMark({ x: e.clientX, y: e.clientY });
1338
- enterMark(mark, e.clientX, e.clientY);
1339
- });
1340
- root.addEventListener("pointermove", (e) => {
1341
- if (hoverMark && tipEnabled()) {
1342
- dispatchChartEvent(root, "hover", hoverMark, typeOf(), "move", { x: e.clientX, y: e.clientY });
1343
- moveTooltip(e.clientX, e.clientY);
1344
- }
1345
- });
1346
- root.addEventListener("pointerout", (e) => {
1347
- const mark = markOf(e);
1348
- if (mark && mark === hoverMark) leaveMark({ x: e.clientX, y: e.clientY });
1349
- });
1350
- root.addEventListener("pointerleave", () => leaveMark());
1351
- root.addEventListener("click", (e) => {
1352
- const mark = markOf(e);
1353
- if (mark) dispatchChartEvent(root, "click", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
1354
- });
1355
- root.addEventListener("dblclick", (e) => {
1356
- const mark = markOf(e);
1357
- if (mark) dispatchChartEvent(root, "dblclick", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
1358
- });
1359
- root.addEventListener("contextmenu", (e) => {
1360
- const mark = markOf(e);
1361
- if (!mark) return;
1362
- e.preventDefault();
1363
- dispatchChartEvent(root, "contextmenu", mark, typeOf(), void 0, { x: e.clientX, y: e.clientY });
1364
- });
1365
- }
1427
+ chartDelegateMeta.set(root, { type: typeOf, tooltip: () => current.tooltip !== false });
1428
+ const unbindEvents = bindChartEvents(root);
1366
1429
  render();
1367
1430
  return {
1368
1431
  el: root,
@@ -1380,14 +1443,20 @@ function renderChart(el, raw) {
1380
1443
  },
1381
1444
  destroy() {
1382
1445
  root.textContent = "";
1383
- attached.__icenChartBound = false;
1446
+ unbindEvents();
1384
1447
  chartTooltip().hidden = true;
1385
1448
  }
1386
1449
  };
1387
1450
  }
1451
+ var GRAPH_W = 360;
1452
+ var GRAPH_H = 260;
1453
+ var GRAPH_PAD = 28;
1454
+ var GRAPH_LW = GRAPH_W - GRAPH_PAD * 2;
1455
+ var GRAPH_LH = GRAPH_H - GRAPH_PAD * 2;
1388
1456
 
1389
1457
  // src/behaviors/ai-tools.ts
1390
1458
  var toolRegistry = /* @__PURE__ */ new Map();
1459
+ var LIVE_AREAS = /* @__PURE__ */ new Set();
1391
1460
  function registerAiTool(def) {
1392
1461
  if (def && typeof def.name === "string" && def.name && typeof def.run === "function") {
1393
1462
  toolRegistry.set(def.name, def);
@@ -1395,6 +1464,13 @@ function registerAiTool(def) {
1395
1464
  }
1396
1465
  function unregisterAiTool(name) {
1397
1466
  toolRegistry.delete(name);
1467
+ for (const entry of LIVE_AREAS) {
1468
+ if (!entry.el.isConnected) {
1469
+ LIVE_AREAS.delete(entry);
1470
+ continue;
1471
+ }
1472
+ entry.purge(name);
1473
+ }
1398
1474
  }
1399
1475
  function getAiTool(name) {
1400
1476
  return toolRegistry.get(name);
@@ -1493,7 +1569,7 @@ function isBrowserTools() {
1493
1569
  }
1494
1570
  function emitArea(area, name, detail) {
1495
1571
  try {
1496
- area.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
1572
+ emitIcen(area, name, detail);
1497
1573
  } catch {
1498
1574
  }
1499
1575
  }
@@ -1508,7 +1584,6 @@ function createAiToolArea(el, opts = {}) {
1508
1584
  area.classList.add("ai-tools-mount");
1509
1585
  let allow = opts.tools;
1510
1586
  let max = opts.max;
1511
- let seq = 0;
1512
1587
  const records = [];
1513
1588
  const listeners = [];
1514
1589
  const notify = () => {
@@ -1524,13 +1599,25 @@ function createAiToolArea(el, opts = {}) {
1524
1599
  emitArea(area, "icen:ai-tool-evict", { name: oldest.def.name, el: oldest.el });
1525
1600
  }
1526
1601
  };
1602
+ const purgeTool = (toolName) => {
1603
+ let removed = false;
1604
+ for (let i = records.length - 1; i >= 0; i--) {
1605
+ const r = records[i];
1606
+ if (r.def.name !== toolName) continue;
1607
+ r.el?.remove();
1608
+ records.splice(i, 1);
1609
+ removed = true;
1610
+ }
1611
+ if (removed) notify();
1612
+ };
1613
+ LIVE_AREAS.add({ el: area, purge: purgeTool });
1527
1614
  const areaApi = {
1528
1615
  el: area,
1529
1616
  get count() {
1530
1617
  return records.filter((r) => r.el).length;
1531
1618
  },
1532
1619
  can(name) {
1533
- return toolAllowed(name, allow) && toolRegistry.has(name);
1620
+ return toolAllowed(name, allow) && getAiTool(name) != null;
1534
1621
  },
1535
1622
  setTools(tools) {
1536
1623
  allow = tools;
@@ -1552,9 +1639,24 @@ function createAiToolArea(el, opts = {}) {
1552
1639
  };
1553
1640
  },
1554
1641
  call(name, input) {
1555
- const def = toolRegistry.get(name);
1642
+ const def = getAiTool(name);
1556
1643
  if (!def || !toolAllowed(name, allow)) return null;
1557
- void seq;
1644
+ if (def.present === "data") {
1645
+ const record2 = { def, ts: Date.now(), input, result: void 0 };
1646
+ emitArea(area, "icen:ai-tool-call", { name, input, el: area });
1647
+ let ok2 = true;
1648
+ let error2;
1649
+ try {
1650
+ record2.result = def.run(input, document.createElement("div"));
1651
+ } catch (err) {
1652
+ ok2 = false;
1653
+ error2 = err instanceof Error ? err.message : String(err);
1654
+ }
1655
+ records.push(record2);
1656
+ emitArea(area, "icen:ai-tool-result", { name, ok: ok2, el: area, error: error2, count: records.length });
1657
+ notify();
1658
+ return record2;
1659
+ }
1558
1660
  const item = document.createElement("div");
1559
1661
  item.className = "ai-tools-item";
1560
1662
  const head = document.createElement("button");
@@ -1574,6 +1676,7 @@ function createAiToolArea(el, opts = {}) {
1574
1676
  const remove = document.createElement("span");
1575
1677
  remove.className = "ai-tools-item-remove";
1576
1678
  remove.setAttribute("role", "button");
1679
+ remove.tabIndex = 0;
1577
1680
  remove.setAttribute("aria-label", `\u79FB\u9664 ${def.name} \u6302\u8F7D`);
1578
1681
  remove.textContent = "\xD7";
1579
1682
  const mount = document.createElement("div");
@@ -1589,6 +1692,13 @@ function createAiToolArea(el, opts = {}) {
1589
1692
  mount.hidden = open;
1590
1693
  item.classList.toggle("is-collapsed", open);
1591
1694
  });
1695
+ head.addEventListener("keydown", (e) => {
1696
+ if (e.key !== "Enter" && e.key !== " ") return;
1697
+ if (!(e.target instanceof Element) || !e.target.closest(".ai-tools-item-remove")) return;
1698
+ e.preventDefault();
1699
+ e.stopPropagation();
1700
+ remove.click();
1701
+ });
1592
1702
  const record = { def, el: item, ts: Date.now(), input, result: void 0 };
1593
1703
  remove.addEventListener("click", (e) => {
1594
1704
  e.stopPropagation();
@@ -1602,7 +1712,6 @@ function createAiToolArea(el, opts = {}) {
1602
1712
  let error;
1603
1713
  try {
1604
1714
  record.result = def.run(input, mount);
1605
- record.handle = record.result;
1606
1715
  } catch (err) {
1607
1716
  ok = false;
1608
1717
  error = err instanceof Error ? err.message : String(err);