@icen.ai/ui 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/AGENTS.md +15 -9
  2. package/README.md +8 -3
  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-chat.d.mts +3 -1
  7. package/dist/behaviors/ai-chat.mjs +66 -43
  8. package/dist/behaviors/ai-composer.d.mts +28 -3
  9. package/dist/behaviors/ai-composer.mjs +291 -209
  10. package/dist/behaviors/ai-core.d.mts +9 -4
  11. package/dist/behaviors/ai-core.mjs +21 -8
  12. package/dist/behaviors/ai-diff.d.mts +2 -2
  13. package/dist/behaviors/ai-diff.mjs +172 -41
  14. package/dist/behaviors/ai-panel.d.mts +11 -8
  15. package/dist/behaviors/ai-panel.mjs +185 -110
  16. package/dist/behaviors/ai-provider.mjs +54 -48
  17. package/dist/behaviors/ai-tool.d.mts +12 -4
  18. package/dist/behaviors/ai-tool.mjs +311 -263
  19. package/dist/behaviors/ai-tools.d.mts +9 -6
  20. package/dist/behaviors/ai-tools.mjs +259 -155
  21. package/dist/behaviors/back-top.mjs +16 -1
  22. package/dist/behaviors/carousel.d.mts +5 -2
  23. package/dist/behaviors/carousel.mjs +53 -26
  24. package/dist/behaviors/charts.d.mts +68 -22
  25. package/dist/behaviors/charts.mjs +221 -150
  26. package/dist/behaviors/command-palette.d.mts +40 -6
  27. package/dist/behaviors/command-palette.mjs +143 -15
  28. package/dist/behaviors/context-menu.d.mts +21 -4
  29. package/dist/behaviors/context-menu.mjs +122 -38
  30. package/dist/behaviors/controls.d.mts +59 -0
  31. package/dist/behaviors/controls.mjs +601 -0
  32. package/dist/behaviors/copy.d.mts +5 -2
  33. package/dist/behaviors/copy.mjs +37 -12
  34. package/dist/behaviors/datatable.d.mts +12 -0
  35. package/dist/behaviors/datatable.mjs +226 -43
  36. package/dist/behaviors/date-picker.d.mts +2 -2
  37. package/dist/behaviors/date-picker.mjs +171 -32
  38. package/dist/behaviors/dropdown.d.mts +6 -2
  39. package/dist/behaviors/dropdown.mjs +164 -25
  40. package/dist/behaviors/events.d.mts +330 -0
  41. package/dist/behaviors/events.mjs +208 -0
  42. package/dist/behaviors/input.mjs +25 -12
  43. package/dist/behaviors/modal.d.mts +24 -5
  44. package/dist/behaviors/modal.mjs +76 -10
  45. package/dist/behaviors/nav.d.mts +1 -1
  46. package/dist/behaviors/nav.mjs +33 -7
  47. package/dist/behaviors/notification.d.mts +20 -4
  48. package/dist/behaviors/notification.mjs +53 -73
  49. package/dist/behaviors/popover.d.mts +39 -3
  50. package/dist/behaviors/popover.mjs +90 -4
  51. package/dist/behaviors/select.d.mts +4 -2
  52. package/dist/behaviors/select.mjs +161 -24
  53. package/dist/behaviors/sidebar.d.mts +5 -2
  54. package/dist/behaviors/sidebar.mjs +43 -13
  55. package/dist/behaviors/slider.d.mts +5 -2
  56. package/dist/behaviors/slider.mjs +54 -43
  57. package/dist/behaviors/split-pane.d.mts +6 -2
  58. package/dist/behaviors/split-pane.mjs +62 -21
  59. package/dist/behaviors/tabs.d.mts +3 -2
  60. package/dist/behaviors/tabs.mjs +65 -14
  61. package/dist/behaviors/tag-input.d.mts +5 -2
  62. package/dist/behaviors/tag-input.mjs +61 -21
  63. package/dist/behaviors/theme.d.mts +13 -2
  64. package/dist/behaviors/theme.mjs +48 -4
  65. package/dist/behaviors/toast.d.mts +30 -8
  66. package/dist/behaviors/toast.mjs +74 -7
  67. package/dist/behaviors/tree.d.mts +5 -2
  68. package/dist/behaviors/tree.mjs +38 -15
  69. package/dist/behaviors/upload.mjs +21 -23
  70. package/dist/cli.mjs +1 -1
  71. package/dist/components/ai-panel.css +1 -1
  72. package/dist/components/ai-tool-call.mjs +1 -1
  73. package/dist/components/badge.css +1 -1
  74. package/dist/components/breadcrumb.css +0 -32
  75. package/dist/components/chart-calendar.mjs +1 -2
  76. package/dist/components/chart-pie.css +0 -6
  77. package/dist/components/charts.css +1 -1
  78. package/dist/components/content.css +3 -16
  79. package/dist/components/datatable.css +1 -1
  80. package/dist/components/date-picker.css +1 -1
  81. package/dist/components/notification.css +2 -3
  82. package/dist/components/pagination.css +3 -1
  83. package/dist/components/pagination.mjs +1 -0
  84. package/dist/components/rating.mjs +1 -0
  85. package/dist/components/segmented.css +9 -1
  86. package/dist/components/segmented.mjs +1 -1
  87. package/dist/components/select.css +67 -4
  88. package/dist/components/steps.css +13 -3
  89. package/dist/components/steps.mjs +1 -0
  90. package/dist/components/switch.css +3 -1
  91. package/dist/components/switch.mjs +1 -0
  92. package/dist/components/table.mjs +1 -0
  93. package/dist/components/tabs.css +23 -0
  94. package/dist/components/tag-input.css +1 -6
  95. package/dist/components/upload.css +0 -15
  96. package/dist/index.d.mts +12 -10
  97. package/dist/index.mjs +2700 -1460
  98. package/dist/registry.json +42 -13
  99. package/dist/retro-effects.css +3 -16
  100. package/dist/tokens/retro-effects.css +3 -16
  101. package/dist/tokens/style-profiles.css +3 -2
  102. package/dist/tokens/typography.css +2 -0
  103. package/dist/tokens.css +5 -2
  104. package/dist/ui.css +138 -98
  105. package/docs/spec/ai-native.md +7 -4
  106. package/package.json +1 -1
@@ -1,4 +1,13 @@
1
1
  // src/behaviors/ai-core.ts
2
+ var AI_STATUSES = [
3
+ "pending",
4
+ "running",
5
+ "streaming",
6
+ "approval",
7
+ "done",
8
+ "error",
9
+ "cancelled"
10
+ ];
2
11
  function aiStatusLabel(s) {
3
12
  const labels = {
4
13
  pending: "\u7B49\u5F85",
@@ -171,9 +180,15 @@ function formatDuration(ms) {
171
180
  const m = Math.floor(ms / 6e4);
172
181
  const s = Math.round(ms % 6e4 / 1e3);
173
182
  if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
174
- const h = Math.floor(ms / 36e5);
183
+ const h2 = Math.floor(ms / 36e5);
175
184
  const rm = Math.round(ms % 36e5 / 6e4);
176
- return rm > 0 ? `${h}h${rm}m` : `${h}h`;
185
+ return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
186
+ }
187
+ function h(tag, className, text) {
188
+ const node = document.createElement(tag);
189
+ if (className) node.className = className;
190
+ if (text != null) node.textContent = text;
191
+ return node;
177
192
  }
178
193
  function svgIcon(svg2) {
179
194
  if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
@@ -187,15 +202,15 @@ function svgIcon(svg2) {
187
202
  if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
188
203
  return null;
189
204
  }
190
- const sanitize = (el2) => {
191
- for (const attr of Array.from(el2.attributes)) {
205
+ const sanitize = (el) => {
206
+ for (const attr of Array.from(el.attributes)) {
192
207
  const name = attr.name.toLowerCase();
193
- if (name.startsWith("on")) el2.removeAttribute(attr.name);
208
+ if (name.startsWith("on")) el.removeAttribute(attr.name);
194
209
  if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
195
- el2.removeAttribute(attr.name);
210
+ el.removeAttribute(attr.name);
196
211
  }
197
212
  }
198
- for (const child of Array.from(el2.children)) {
213
+ for (const child of Array.from(el.children)) {
199
214
  const tag = child.tagName.toLowerCase();
200
215
  if (tag === "script" || tag === "foreignobject") {
201
216
  child.remove();
@@ -209,6 +224,44 @@ function svgIcon(svg2) {
209
224
  }
210
225
 
211
226
  // src/behaviors/popover.ts
227
+ var PANEL_SIZING_ATTRS = {
228
+ width: "data-panel-width",
229
+ minWidth: "data-panel-min",
230
+ maxWidth: "data-panel-max",
231
+ minHeight: "data-panel-min-height",
232
+ maxHeight: "data-panel-max-height"
233
+ };
234
+ function readPanelSizing(el) {
235
+ const out = {};
236
+ if (!el) return out;
237
+ for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
238
+ const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
239
+ if (raw == null) continue;
240
+ const n = Number(raw);
241
+ if (Number.isFinite(n) && n > 0) out[key] = n;
242
+ }
243
+ return out;
244
+ }
245
+ function resolvePanelSizing(el, overrides) {
246
+ const base = readPanelSizing(el);
247
+ if (!overrides) return base;
248
+ for (const key of Object.keys(overrides)) {
249
+ const v = overrides[key];
250
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) base[key] = v;
251
+ }
252
+ return base;
253
+ }
254
+ function sizingToLayout(sizing, opts) {
255
+ const out = { ...opts };
256
+ if (sizing.minWidth != null) out.minWidth = sizing.minWidth;
257
+ if (sizing.maxWidth != null) out.maxWidth = sizing.maxWidth;
258
+ if (sizing.width != null) {
259
+ out.minWidth = sizing.width;
260
+ out.maxWidth = sizing.width;
261
+ }
262
+ if (sizing.maxHeight != null) out.maxHeight = sizing.maxHeight;
263
+ return out;
264
+ }
212
265
  var DEFAULT_MIN_WIDTH = 220;
213
266
  var DEFAULT_MAX_WIDTH = 420;
214
267
  var DEFAULT_MIN_HEIGHT = 120;
@@ -233,13 +286,35 @@ function horizontalAnchor(anchor, width, align) {
233
286
  function computePopoverLayout(anchor, viewport, opts = {}) {
234
287
  const margin = opts.margin ?? 12;
235
288
  const offset = opts.offset ?? 8;
289
+ const preferredSide = opts.side ?? "top";
290
+ const align = opts.align ?? "start";
291
+ if (preferredSide === "left" || preferredSide === "right") {
292
+ const availableLeft = Math.max(0, anchor.left - margin - offset);
293
+ const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
294
+ const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
295
+ const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
296
+ const maxWidth2 = Math.min(
297
+ Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
298
+ side2 === "left" ? availableLeft : availableRight
299
+ );
300
+ const width2 = minWidth2;
301
+ const left2 = clamp(
302
+ side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
303
+ margin,
304
+ viewport.width - margin - width2
305
+ );
306
+ const availableHeight2 = Math.max(80, viewport.height - margin * 2);
307
+ const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
308
+ const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
309
+ const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
310
+ const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
311
+ return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
312
+ }
236
313
  const availableWidth = Math.max(80, viewport.width - margin * 2);
237
314
  const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
238
315
  const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
239
316
  const maxWidth = Math.min(requestedMaxWidth, availableWidth);
240
317
  const minWidth = Math.min(requestedMinWidth, maxWidth);
241
- const preferredSide = opts.side ?? "top";
242
- const align = opts.align ?? "start";
243
318
  const availableTop = Math.max(0, anchor.top - margin - offset);
244
319
  const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
245
320
  const availableHeight = Math.max(80, viewport.height - margin * 2);
@@ -263,8 +338,8 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
263
338
  return { side, left, top, width, maxHeight };
264
339
  }
265
340
  var openPanels = /* @__PURE__ */ new Map();
266
- function rectFromElement(el2) {
267
- const r = el2.getBoundingClientRect();
341
+ function rectFromElement(el) {
342
+ const r = el.getBoundingClientRect();
268
343
  return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
269
344
  }
270
345
  function resolveAnchor(state) {
@@ -317,7 +392,8 @@ function onKeyDown(e) {
317
392
  function openPopover(panel, options) {
318
393
  if (typeof document === "undefined") return;
319
394
  if (openPanels.has(panel)) closePopover(panel);
320
- const { anchor, onClose, ...layoutOpts } = options;
395
+ const { anchor, onClose, sizing, ...rest } = options;
396
+ const layoutOpts = sizingToLayout(resolvePanelSizing(panel, sizing), rest);
321
397
  const placeholder = document.createComment("popover-anchor");
322
398
  panel.parentNode?.insertBefore(placeholder, panel);
323
399
  const wasHidden = panel.hidden;
@@ -369,32 +445,24 @@ function closePopover(panel) {
369
445
  state.onClose?.();
370
446
  }
371
447
 
372
- // src/behaviors/ai-panel.ts
373
- function el(tag, className, text) {
374
- const node = document.createElement(tag);
375
- if (className) node.className = className;
376
- if (text !== void 0) node.textContent = text;
377
- return node;
448
+ // src/behaviors/events.ts
449
+ function emitIcen(source, type, detail) {
450
+ return source.dispatchEvent(
451
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
452
+ );
378
453
  }
454
+
455
+ // src/behaviors/ai-panel.ts
379
456
  function dispatch(node, name, detail) {
380
- node.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
457
+ emitIcen(node, name, detail);
381
458
  }
382
- var STATUS_CLASSES = [
383
- "pending",
384
- "running",
385
- "streaming",
386
- "approval",
387
- "done",
388
- "error",
389
- "cancelled"
390
- ];
391
459
  function stripStatusClasses(node) {
392
- for (const s of STATUS_CLASSES) node.classList.remove(`is-${s}`);
460
+ for (const s of AI_STATUSES) node.classList.remove(`is-${s}`);
393
461
  }
394
462
  function buildTodoHead(done, total) {
395
- const head = el("div", "ai-todo-head");
396
- head.appendChild(el("span", "ai-todo-progress", `${done}/${total}`));
397
- const bar = el("div", "ai-todo-bar");
463
+ const head = h("div", "ai-todo-head");
464
+ head.appendChild(h("span", "ai-todo-progress", `${done}/${total}`));
465
+ const bar = h("div", "ai-todo-bar");
398
466
  bar.setAttribute("role", "progressbar");
399
467
  bar.setAttribute("aria-valuemin", "0");
400
468
  bar.setAttribute("aria-valuemax", String(total));
@@ -407,23 +475,24 @@ function buildTodoHead(done, total) {
407
475
  return head;
408
476
  }
409
477
  function buildTodoItem(item, index) {
410
- const row = el("div", `ai-todo-item is-${item.status}`);
478
+ const row = h("div", `ai-todo-item is-${item.status}`);
411
479
  row.dataset.index = String(index);
412
- row.appendChild(el("span", "ai-item-status"));
413
- row.appendChild(el("span", "ai-todo-text", item.content));
414
- row.appendChild(el("span", "ai-todo-active", item.activeForm ?? item.content));
480
+ row.appendChild(h("span", "ai-item-status"));
481
+ row.appendChild(h("span", "ai-todo-text", item.content));
482
+ row.appendChild(h("span", "ai-todo-active", item.activeForm ?? item.content));
415
483
  return row;
416
484
  }
417
- function renderAiTodo(elm, items) {
418
- if (typeof document === "undefined") return;
419
- elm.textContent = "";
420
- const root = el("div", "ai-todo");
485
+ function renderAiTodo(el, items) {
486
+ if (typeof document === "undefined") return el;
487
+ el.textContent = "";
488
+ const root = h("div", "ai-todo");
421
489
  const list = Array.isArray(items) ? items : [];
422
490
  const done = list.filter((i) => i.status === "done").length;
423
491
  root.appendChild(buildTodoHead(done, list.length));
424
492
  list.forEach((item, i) => root.appendChild(buildTodoItem(item, i)));
425
493
  if (list.length > 0 && done === list.length) root.classList.add("is-complete");
426
- elm.appendChild(root);
494
+ el.appendChild(root);
495
+ return el;
427
496
  }
428
497
  var TODO_NEXT = {
429
498
  pending: "running",
@@ -431,7 +500,7 @@ var TODO_NEXT = {
431
500
  done: "pending"
432
501
  };
433
502
  function todoItemStatus(item) {
434
- for (const s of STATUS_CLASSES) {
503
+ for (const s of AI_STATUSES) {
435
504
  if (item.classList.contains(`is-${s}`)) return s;
436
505
  }
437
506
  return "pending";
@@ -512,8 +581,8 @@ function initAiTodo(root) {
512
581
  marked.__icenAiTodoInit = false;
513
582
  };
514
583
  }
515
- function renderAiUsage(elm, usage, opts = {}) {
516
- if (typeof document === "undefined") return;
584
+ function renderAiUsage(el, usage, opts = {}) {
585
+ if (typeof document === "undefined") return el;
517
586
  const u = normalizeUsage(usage);
518
587
  const segs = [
519
588
  { key: "input", className: "ai-usage-seg--input", label: "\u8F93\u5165", value: u.input ?? 0 },
@@ -525,9 +594,9 @@ function renderAiUsage(elm, usage, opts = {}) {
525
594
  const activeSegs = segs.filter((s) => s.value > 0);
526
595
  const sum2 = activeSegs.reduce((acc, s) => acc + s.value, 0);
527
596
  const total = u.total ?? sum2;
528
- elm.textContent = "";
529
- const root = el("div", "ai-usage");
530
- const bar = el("div", "ai-usage-bar");
597
+ el.textContent = "";
598
+ const root = h("div", "ai-usage");
599
+ const bar = h("div", "ai-usage-bar");
531
600
  const summary = activeSegs.length > 0 ? activeSegs.map((s) => `${s.label} ${formatTokens(s.value)}`).join("\uFF0C") : "\u6682\u65E0\u7528\u91CF";
532
601
  bar.setAttribute("role", "img");
533
602
  bar.setAttribute("aria-label", `\u7528\u91CF\uFF1A${summary}`);
@@ -539,15 +608,15 @@ function renderAiUsage(elm, usage, opts = {}) {
539
608
  }
540
609
  root.appendChild(bar);
541
610
  if (activeSegs.length > 0) {
542
- const legend = el("div", "ai-usage-legend");
611
+ const legend = h("div", "ai-usage-legend");
543
612
  for (const s of activeSegs) {
544
- const item = el("span", "ai-usage-legend-item");
613
+ const item = h("span", "ai-usage-legend-item");
545
614
  const dot = document.createElement("i");
546
615
  dot.className = `ai-usage-dot ${s.className}`;
547
616
  dot.setAttribute("aria-hidden", "true");
548
617
  item.appendChild(dot);
549
- item.appendChild(el("span", "ai-usage-legend-label", s.label));
550
- item.appendChild(el("span", "ai-usage-legend-value", formatTokens(s.value)));
618
+ item.appendChild(h("span", "ai-usage-legend-label", s.label));
619
+ item.appendChild(h("span", "ai-usage-legend-value", formatTokens(s.value)));
551
620
  legend.appendChild(item);
552
621
  }
553
622
  root.appendChild(legend);
@@ -560,17 +629,18 @@ function renderAiUsage(elm, usage, opts = {}) {
560
629
  if (typeof opts.cost === "number" && Number.isFinite(opts.cost)) {
561
630
  totalParts.push(` \xB7 $${opts.cost.toFixed(4)}`);
562
631
  }
563
- root.appendChild(el("div", "ai-usage-total", totalParts.join("")));
564
- elm.appendChild(root);
632
+ root.appendChild(h("div", "ai-usage-total", totalParts.join("")));
633
+ el.appendChild(root);
634
+ return el;
565
635
  }
566
- function renderAiUsageRing(elm, usage, opts = {}) {
567
- if (typeof document === "undefined") return;
636
+ function renderAiUsageRing(el, usage, opts = {}) {
637
+ if (typeof document === "undefined") return el;
568
638
  const u = normalizeUsage(usage);
569
639
  const used = u.total ?? (u.input ?? 0) + (u.output ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.reasoning ?? 0);
570
640
  const hasLimit = typeof opts.total === "number" && opts.total > 0;
571
641
  const pct = hasLimit ? Math.min(100, Math.max(0, Math.round(used / opts.total * 100))) : 0;
572
- elm.textContent = "";
573
- const btn = el("button", "ai-usage-ring");
642
+ el.textContent = "";
643
+ const btn = h("button", "ai-usage-ring");
574
644
  btn.type = "button";
575
645
  btn.classList.add(pct >= 85 ? "is-hot" : pct >= 60 ? "is-warn" : "is-ok");
576
646
  btn.setAttribute("aria-haspopup", "true");
@@ -594,7 +664,7 @@ function renderAiUsageRing(elm, usage, opts = {}) {
594
664
  svg2.appendChild(c);
595
665
  }
596
666
  btn.appendChild(svg2);
597
- btn.appendChild(el("span", "ai-usage-ring-pct", hasLimit ? `${pct}%` : formatTokens(used)));
667
+ btn.appendChild(h("span", "ai-usage-ring-pct", hasLimit ? `${pct}%` : formatTokens(used)));
598
668
  let panel = null;
599
669
  const closePanel = () => {
600
670
  if (!panel) return;
@@ -608,10 +678,10 @@ function renderAiUsageRing(elm, usage, opts = {}) {
608
678
  closePanel();
609
679
  return;
610
680
  }
611
- const p = el("div", "popover ai-usage-popover");
681
+ const p = h("div", "popover ai-usage-popover");
612
682
  p.hidden = true;
613
- p.appendChild(el("div", "ai-usage-popover-title", opts.title ?? "\u4E0A\u4E0B\u6587\u7A97\u53E3"));
614
- const body = el("div", "ai-usage-popover-body");
683
+ p.appendChild(h("div", "ai-usage-popover-title", opts.title ?? "\u4E0A\u4E0B\u6587\u7A97\u53E3"));
684
+ const body = h("div", "ai-usage-popover-body");
615
685
  renderAiUsage(body, usage, opts);
616
686
  p.appendChild(body);
617
687
  openPopover(p, {
@@ -625,7 +695,8 @@ function renderAiUsageRing(elm, usage, opts = {}) {
625
695
  });
626
696
  panel = p;
627
697
  });
628
- elm.appendChild(btn);
698
+ el.appendChild(btn);
699
+ return el;
629
700
  }
630
701
  var svgWrap = (inner) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
631
702
  var FILE_OUTLINE = '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>';
@@ -673,8 +744,8 @@ function fileKindIcon(kind) {
673
744
  }
674
745
  function renderFileChip(file) {
675
746
  const kind = fileKind(file.path);
676
- const chip = el("span", `ai-file-chip ai-file-chip--${kind} is-${file.status ?? "modified"}`);
677
- const icon = el("span", "ai-file-icon");
747
+ const chip = h("span", `ai-file-chip ai-file-chip--${kind} is-${file.status ?? "modified"}`);
748
+ const icon = h("span", "ai-file-icon");
678
749
  const svg2 = svgIcon(fileKindIcon(kind));
679
750
  if (svg2) icon.appendChild(svg2);
680
751
  chip.appendChild(icon);
@@ -682,21 +753,21 @@ function renderFileChip(file) {
682
753
  return chip;
683
754
  }
684
755
  function renderSection(title) {
685
- const section = el("section", "ai-context-section");
686
- section.appendChild(el("h3", "", title));
687
- const body = el("div", "ai-context-section-body");
756
+ const section = h("section", "ai-context-section");
757
+ section.appendChild(h("h3", "", title));
758
+ const body = h("div", "ai-context-section-body");
688
759
  section.appendChild(body);
689
760
  return { section, body };
690
761
  }
691
- function renderAiContext(elm, model) {
692
- if (typeof document === "undefined") return;
693
- elm.textContent = "";
762
+ function renderAiContext(el, model) {
763
+ if (typeof document === "undefined") return el;
764
+ el.textContent = "";
694
765
  const m = model ?? {};
695
- const aside = el("aside", "ai-context");
766
+ const aside = h("aside", "ai-context");
696
767
  aside.hidden = true;
697
- const head = el("div", "ai-context-head");
698
- head.appendChild(el("span", "", "\u4E0A\u4E0B\u6587"));
699
- const closeBtn = el("button", "", "\xD7");
768
+ const head = h("div", "ai-context-head");
769
+ head.appendChild(h("span", "", "\u4E0A\u4E0B\u6587"));
770
+ const closeBtn = h("button", "", "\xD7");
700
771
  closeBtn.type = "button";
701
772
  closeBtn.setAttribute("data-ai-context-close", "");
702
773
  closeBtn.setAttribute("aria-label", "\u5173\u95ED");
@@ -716,7 +787,7 @@ function renderAiContext(elm, model) {
716
787
  const files = Array.isArray(m.files) ? m.files : [];
717
788
  if (files.length > 0) {
718
789
  const { section, body } = renderSection(`\u6587\u4EF6 (${files.length})`);
719
- const chips = el("div", "ai-files");
790
+ const chips = h("div", "ai-files");
720
791
  for (const f of files) chips.appendChild(renderFileChip(f));
721
792
  body.appendChild(chips);
722
793
  aside.appendChild(section);
@@ -726,17 +797,17 @@ function renderAiContext(elm, model) {
726
797
  const { section, body } = renderSection(`MCP (${servers.length})`);
727
798
  for (const s of servers) {
728
799
  const ok = s.status !== "disconnected";
729
- const row = el("div", `ai-item is-${ok ? "done" : "error"}`);
730
- const icon = el("span", "ai-item-icon");
800
+ const row = h("div", `ai-item is-${ok ? "done" : "error"}`);
801
+ const icon = h("span", "ai-item-icon");
731
802
  const svg2 = svgIcon(ICON_SERVER);
732
803
  if (svg2) icon.appendChild(svg2);
733
804
  row.appendChild(icon);
734
- const main = el("span", "ai-item-main");
735
- main.appendChild(el("span", "ai-item-title", s.name));
805
+ const main = h("span", "ai-item-main");
806
+ main.appendChild(h("span", "ai-item-title", s.name));
736
807
  row.appendChild(main);
737
- const side = el("span", "ai-item-side");
738
- side.appendChild(el("span", "ai-item-status"));
739
- side.appendChild(el("span", "ai-item-meta", typeof s.tools === "number" ? `${s.tools} \u5DE5\u5177` : ""));
808
+ const side = h("span", "ai-item-side");
809
+ side.appendChild(h("span", "ai-item-status"));
810
+ side.appendChild(h("span", "ai-item-meta", typeof s.tools === "number" ? `${s.tools} \u5DE5\u5177` : ""));
740
811
  row.appendChild(side);
741
812
  body.appendChild(row);
742
813
  }
@@ -746,20 +817,21 @@ function renderAiContext(elm, model) {
746
817
  if (skills.length > 0) {
747
818
  const { section, body } = renderSection("Skills");
748
819
  for (const s of skills) {
749
- const row = el("div", "ai-item");
750
- const icon = el("span", "ai-item-icon");
820
+ const row = h("div", "ai-item");
821
+ const icon = h("span", "ai-item-icon");
751
822
  const svg2 = svgIcon(ICON_SPARKLES);
752
823
  if (svg2) icon.appendChild(svg2);
753
824
  row.appendChild(icon);
754
- const main = el("span", "ai-item-main");
755
- main.appendChild(el("span", "ai-item-title", s.name));
756
- if (s.description) main.appendChild(el("span", "ai-item-sub", s.description));
825
+ const main = h("span", "ai-item-main");
826
+ main.appendChild(h("span", "ai-item-title", s.name));
827
+ if (s.description) main.appendChild(h("span", "ai-item-sub", s.description));
757
828
  row.appendChild(main);
758
829
  body.appendChild(row);
759
830
  }
760
831
  aside.appendChild(section);
761
832
  }
762
- elm.appendChild(aside);
833
+ el.appendChild(aside);
834
+ return el;
763
835
  }
764
836
  function isInline(drawer) {
765
837
  return drawer.classList.contains("ai-context--inline");
@@ -805,6 +877,7 @@ function initAiContext(root) {
805
877
  void drawer.offsetWidth;
806
878
  drawer.classList.add("is-open");
807
879
  syncTriggers(drawer, true);
880
+ dispatch(drawer, "icen:ai-context-open", {});
808
881
  const closeBtn = drawer.querySelector("[data-ai-context-close]");
809
882
  (closeBtn ?? drawer).focus?.();
810
883
  }
@@ -812,6 +885,7 @@ function initAiContext(root) {
812
885
  if (drawer.hidden && !drawer.classList.contains("is-open")) return;
813
886
  drawer.classList.remove("is-open");
814
887
  syncTriggers(drawer, false);
888
+ dispatch(drawer, "icen:ai-context-close", {});
815
889
  const finish = () => {
816
890
  drawer.hidden = true;
817
891
  if (restoreFocus && lastTrigger && document.contains(lastTrigger)) {
@@ -943,55 +1017,55 @@ function buildAuditBody(body, entries, opts = {}) {
943
1017
  g.requests++;
944
1018
  groups.set(key, g);
945
1019
  }
946
- const totals = el("div", "ai-audit-totals");
1020
+ const totals = h("div", "ai-audit-totals");
947
1021
  const bits = [`${entries.length} \u6B21\u8BF7\u6C42`];
948
1022
  if (errors > 0) bits.push(`${errors} \u5931\u8D25`);
949
1023
  bits.push(`${formatTokens(tokens)} tok`);
950
1024
  if (cost > 0) bits.push(`$${(Math.round(cost * 1e6) / 1e6).toFixed(4)}`);
951
1025
  if (ttftN > 0) bits.push(`TTFT ${formatDuration(Math.round(ttftSum / ttftN))}`);
952
- totals.appendChild(el("span", "ai-audit-totals-text", bits.join(" \xB7 ")));
1026
+ totals.appendChild(h("span", "ai-audit-totals-text", bits.join(" \xB7 ")));
953
1027
  body.appendChild(totals);
954
1028
  const sorted = Array.from(groups.values()).sort((a, b) => b.tokens - a.tokens);
955
1029
  if (sorted.length > 0) {
956
- const list2 = el("div", "ai-audit-models");
1030
+ const list2 = h("div", "ai-audit-models");
957
1031
  for (const g of sorted) {
958
- const row = el("div", "ai-audit-model-row");
959
- row.appendChild(el("span", "ai-audit-model-name", g.key));
960
- row.appendChild(el("span", "ai-audit-model-meta", `${g.requests} \u6B21 \xB7 ${formatTokens(g.tokens)} tok${g.cost > 0 ? ` \xB7 $${g.cost.toFixed(4)}` : ""}`));
1032
+ const row = h("div", "ai-audit-model-row");
1033
+ row.appendChild(h("span", "ai-audit-model-name", g.key));
1034
+ row.appendChild(h("span", "ai-audit-model-meta", `${g.requests} \u6B21 \xB7 ${formatTokens(g.tokens)} tok${g.cost > 0 ? ` \xB7 $${g.cost.toFixed(4)}` : ""}`));
961
1035
  list2.appendChild(row);
962
1036
  }
963
1037
  body.appendChild(list2);
964
1038
  }
965
1039
  const recent = entries.slice(-limit).reverse();
966
- const list = el("div", "ai-audit-list");
1040
+ const list = h("div", "ai-audit-list");
967
1041
  for (const e of recent) {
968
- const item = el("div", `ai-audit-item is-${e.status === "ok" ? "done" : "error"}`);
969
- item.appendChild(el("span", "ai-audit-dot"));
970
- const main = el("span", "ai-audit-item-main");
971
- main.appendChild(el("span", "ai-audit-item-model", `${e.provider} \xB7 ${e.model}`));
1042
+ const item = h("div", `ai-audit-item is-${e.status === "ok" ? "done" : "error"}`);
1043
+ item.appendChild(h("span", "ai-audit-dot"));
1044
+ const main = h("span", "ai-audit-item-main");
1045
+ main.appendChild(h("span", "ai-audit-item-model", `${e.provider} \xB7 ${e.model}`));
972
1046
  const metaBits = [timeOf(e.ts), formatDuration(e.durationMs)];
973
1047
  if (typeof e.ttftMs === "number") metaBits.push(`TTFT ${formatDuration(e.ttftMs)}`);
974
1048
  if ((e.usage?.total ?? 0) > 0) metaBits.push(`${formatTokens(e.usage?.total ?? 0)} tok`);
975
- main.appendChild(el("span", "ai-audit-item-meta", metaBits.filter(Boolean).join(" \xB7 ")));
1049
+ main.appendChild(h("span", "ai-audit-item-meta", metaBits.filter(Boolean).join(" \xB7 ")));
976
1050
  item.appendChild(main);
977
- if (e.error) item.appendChild(el("span", "ai-audit-item-error", e.error));
1051
+ if (e.error) item.appendChild(h("span", "ai-audit-item-error", e.error));
978
1052
  list.appendChild(item);
979
1053
  }
980
1054
  body.appendChild(list);
981
1055
  }
982
- function renderAiAudit(elm, source, opts = {}) {
983
- if (typeof document === "undefined") return;
984
- elm.textContent = "";
985
- const root = el("div", "ai-audit");
1056
+ function renderAiAudit(el, source, opts = {}) {
1057
+ if (typeof document === "undefined") return el;
1058
+ el.textContent = "";
1059
+ const root = h("div", "ai-audit");
986
1060
  const entries = auditEntriesOf(source);
987
1061
  if (entries.length === 0) {
988
- root.appendChild(el("div", "ai-audit-empty", "\u6682\u65E0\u5BA1\u8BA1\u8BB0\u5F55"));
989
- elm.appendChild(root);
990
- return;
1062
+ root.appendChild(h("div", "ai-audit-empty", "\u6682\u65E0\u5BA1\u8BA1\u8BB0\u5F55"));
1063
+ el.appendChild(root);
1064
+ return el;
991
1065
  }
992
1066
  buildAuditBody(root, entries, { limit: opts.limit });
993
1067
  if (opts.clearButton !== false) {
994
- const clear = el("button", "btn btn-sm ai-audit-clear", "\u6E05\u9664\u5BA1\u8BA1");
1068
+ const clear = h("button", "btn btn-sm ai-audit-clear", "\u6E05\u9664\u5BA1\u8BA1");
995
1069
  clear.type = "button";
996
1070
  clear.addEventListener("click", () => {
997
1071
  if (opts.onClear) opts.onClear();
@@ -999,7 +1073,8 @@ function renderAiAudit(elm, source, opts = {}) {
999
1073
  });
1000
1074
  root.appendChild(clear);
1001
1075
  }
1002
- elm.appendChild(root);
1076
+ el.appendChild(root);
1077
+ return el;
1003
1078
  }
1004
1079
  export {
1005
1080
  initAiContext,