@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.
- package/AGENTS.md +15 -9
- package/README.md +8 -3
- package/dist/base.css +4 -3
- package/dist/behaviors/accordion.d.mts +5 -2
- package/dist/behaviors/accordion.mjs +36 -11
- package/dist/behaviors/ai-chat.d.mts +3 -1
- package/dist/behaviors/ai-chat.mjs +66 -43
- package/dist/behaviors/ai-composer.d.mts +28 -3
- package/dist/behaviors/ai-composer.mjs +291 -209
- package/dist/behaviors/ai-core.d.mts +9 -4
- package/dist/behaviors/ai-core.mjs +21 -8
- package/dist/behaviors/ai-diff.d.mts +2 -2
- package/dist/behaviors/ai-diff.mjs +172 -41
- package/dist/behaviors/ai-panel.d.mts +11 -8
- package/dist/behaviors/ai-panel.mjs +185 -110
- package/dist/behaviors/ai-provider.mjs +54 -48
- package/dist/behaviors/ai-tool.d.mts +12 -4
- package/dist/behaviors/ai-tool.mjs +311 -263
- package/dist/behaviors/ai-tools.d.mts +9 -6
- package/dist/behaviors/ai-tools.mjs +259 -155
- package/dist/behaviors/back-top.mjs +16 -1
- package/dist/behaviors/carousel.d.mts +5 -2
- package/dist/behaviors/carousel.mjs +53 -26
- package/dist/behaviors/charts.d.mts +68 -22
- package/dist/behaviors/charts.mjs +221 -150
- package/dist/behaviors/command-palette.d.mts +40 -6
- package/dist/behaviors/command-palette.mjs +143 -15
- package/dist/behaviors/context-menu.d.mts +21 -4
- package/dist/behaviors/context-menu.mjs +122 -38
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +5 -2
- package/dist/behaviors/copy.mjs +37 -12
- package/dist/behaviors/datatable.d.mts +12 -0
- package/dist/behaviors/datatable.mjs +226 -43
- package/dist/behaviors/date-picker.d.mts +2 -2
- package/dist/behaviors/date-picker.mjs +171 -32
- package/dist/behaviors/dropdown.d.mts +6 -2
- package/dist/behaviors/dropdown.mjs +164 -25
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.mjs +25 -12
- package/dist/behaviors/modal.d.mts +24 -5
- package/dist/behaviors/modal.mjs +76 -10
- package/dist/behaviors/nav.d.mts +1 -1
- package/dist/behaviors/nav.mjs +33 -7
- package/dist/behaviors/notification.d.mts +20 -4
- package/dist/behaviors/notification.mjs +53 -73
- package/dist/behaviors/popover.d.mts +39 -3
- package/dist/behaviors/popover.mjs +90 -4
- package/dist/behaviors/select.d.mts +4 -2
- package/dist/behaviors/select.mjs +161 -24
- package/dist/behaviors/sidebar.d.mts +5 -2
- package/dist/behaviors/sidebar.mjs +43 -13
- package/dist/behaviors/slider.d.mts +5 -2
- package/dist/behaviors/slider.mjs +54 -43
- package/dist/behaviors/split-pane.d.mts +6 -2
- package/dist/behaviors/split-pane.mjs +62 -21
- package/dist/behaviors/tabs.d.mts +3 -2
- package/dist/behaviors/tabs.mjs +65 -14
- package/dist/behaviors/tag-input.d.mts +5 -2
- package/dist/behaviors/tag-input.mjs +61 -21
- package/dist/behaviors/theme.d.mts +13 -2
- package/dist/behaviors/theme.mjs +48 -4
- package/dist/behaviors/toast.d.mts +30 -8
- package/dist/behaviors/toast.mjs +74 -7
- package/dist/behaviors/tree.d.mts +5 -2
- package/dist/behaviors/tree.mjs +38 -15
- package/dist/behaviors/upload.mjs +21 -23
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-panel.css +1 -1
- package/dist/components/ai-tool-call.mjs +1 -1
- package/dist/components/badge.css +1 -1
- package/dist/components/breadcrumb.css +0 -32
- package/dist/components/chart-calendar.mjs +1 -2
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/charts.css +1 -1
- package/dist/components/content.css +3 -16
- package/dist/components/datatable.css +1 -1
- package/dist/components/date-picker.css +1 -1
- package/dist/components/notification.css +2 -3
- package/dist/components/pagination.css +3 -1
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +9 -1
- package/dist/components/segmented.mjs +1 -1
- package/dist/components/select.css +67 -4
- package/dist/components/steps.css +13 -3
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +3 -1
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +23 -0
- package/dist/components/tag-input.css +1 -6
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +12 -10
- package/dist/index.mjs +2700 -1460
- package/dist/registry.json +42 -13
- package/dist/retro-effects.css +3 -16
- package/dist/tokens/retro-effects.css +3 -16
- package/dist/tokens/style-profiles.css +3 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +5 -2
- package/dist/ui.css +138 -98
- package/docs/spec/ai-native.md +7 -4
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
588
|
-
|
|
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
|
-
|
|
640
|
-
const
|
|
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:
|
|
690
|
+
setMarkData(cell, { index: i, label: entry.date, value: entry.value });
|
|
652
691
|
grid.appendChild(cell);
|
|
653
692
|
}
|
|
654
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 >=
|
|
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
|
|
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
|
-
|
|
1310
|
-
|
|
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,7 +1443,7 @@ function renderChart(el, raw) {
|
|
|
1380
1443
|
},
|
|
1381
1444
|
destroy() {
|
|
1382
1445
|
root.textContent = "";
|
|
1383
|
-
|
|
1446
|
+
unbindEvents();
|
|
1384
1447
|
chartTooltip().hidden = true;
|
|
1385
1448
|
}
|
|
1386
1449
|
};
|
|
@@ -1388,6 +1451,7 @@ function renderChart(el, raw) {
|
|
|
1388
1451
|
|
|
1389
1452
|
// src/behaviors/ai-tools.ts
|
|
1390
1453
|
var toolRegistry = /* @__PURE__ */ new Map();
|
|
1454
|
+
var LIVE_AREAS = /* @__PURE__ */ new Set();
|
|
1391
1455
|
function registerAiTool(def) {
|
|
1392
1456
|
if (def && typeof def.name === "string" && def.name && typeof def.run === "function") {
|
|
1393
1457
|
toolRegistry.set(def.name, def);
|
|
@@ -1395,6 +1459,13 @@ function registerAiTool(def) {
|
|
|
1395
1459
|
}
|
|
1396
1460
|
function unregisterAiTool(name) {
|
|
1397
1461
|
toolRegistry.delete(name);
|
|
1462
|
+
for (const entry of LIVE_AREAS) {
|
|
1463
|
+
if (!entry.el.isConnected) {
|
|
1464
|
+
LIVE_AREAS.delete(entry);
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
entry.purge(name);
|
|
1468
|
+
}
|
|
1398
1469
|
}
|
|
1399
1470
|
function getAiTool(name) {
|
|
1400
1471
|
return toolRegistry.get(name);
|
|
@@ -1493,7 +1564,7 @@ function isBrowserTools() {
|
|
|
1493
1564
|
}
|
|
1494
1565
|
function emitArea(area, name, detail) {
|
|
1495
1566
|
try {
|
|
1496
|
-
area
|
|
1567
|
+
emitIcen(area, name, detail);
|
|
1497
1568
|
} catch {
|
|
1498
1569
|
}
|
|
1499
1570
|
}
|
|
@@ -1508,7 +1579,6 @@ function createAiToolArea(el, opts = {}) {
|
|
|
1508
1579
|
area.classList.add("ai-tools-mount");
|
|
1509
1580
|
let allow = opts.tools;
|
|
1510
1581
|
let max = opts.max;
|
|
1511
|
-
let seq = 0;
|
|
1512
1582
|
const records = [];
|
|
1513
1583
|
const listeners = [];
|
|
1514
1584
|
const notify = () => {
|
|
@@ -1524,13 +1594,25 @@ function createAiToolArea(el, opts = {}) {
|
|
|
1524
1594
|
emitArea(area, "icen:ai-tool-evict", { name: oldest.def.name, el: oldest.el });
|
|
1525
1595
|
}
|
|
1526
1596
|
};
|
|
1597
|
+
const purgeTool = (toolName) => {
|
|
1598
|
+
let removed = false;
|
|
1599
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
1600
|
+
const r = records[i];
|
|
1601
|
+
if (r.def.name !== toolName) continue;
|
|
1602
|
+
r.el?.remove();
|
|
1603
|
+
records.splice(i, 1);
|
|
1604
|
+
removed = true;
|
|
1605
|
+
}
|
|
1606
|
+
if (removed) notify();
|
|
1607
|
+
};
|
|
1608
|
+
LIVE_AREAS.add({ el: area, purge: purgeTool });
|
|
1527
1609
|
const areaApi = {
|
|
1528
1610
|
el: area,
|
|
1529
1611
|
get count() {
|
|
1530
1612
|
return records.filter((r) => r.el).length;
|
|
1531
1613
|
},
|
|
1532
1614
|
can(name) {
|
|
1533
|
-
return toolAllowed(name, allow) &&
|
|
1615
|
+
return toolAllowed(name, allow) && getAiTool(name) != null;
|
|
1534
1616
|
},
|
|
1535
1617
|
setTools(tools) {
|
|
1536
1618
|
allow = tools;
|
|
@@ -1552,9 +1634,24 @@ function createAiToolArea(el, opts = {}) {
|
|
|
1552
1634
|
};
|
|
1553
1635
|
},
|
|
1554
1636
|
call(name, input) {
|
|
1555
|
-
const def =
|
|
1637
|
+
const def = getAiTool(name);
|
|
1556
1638
|
if (!def || !toolAllowed(name, allow)) return null;
|
|
1557
|
-
|
|
1639
|
+
if (def.present === "data") {
|
|
1640
|
+
const record2 = { def, ts: Date.now(), input, result: void 0 };
|
|
1641
|
+
emitArea(area, "icen:ai-tool-call", { name, input, el: area });
|
|
1642
|
+
let ok2 = true;
|
|
1643
|
+
let error2;
|
|
1644
|
+
try {
|
|
1645
|
+
record2.result = def.run(input, document.createElement("div"));
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
ok2 = false;
|
|
1648
|
+
error2 = err instanceof Error ? err.message : String(err);
|
|
1649
|
+
}
|
|
1650
|
+
records.push(record2);
|
|
1651
|
+
emitArea(area, "icen:ai-tool-result", { name, ok: ok2, el: area, error: error2, count: records.length });
|
|
1652
|
+
notify();
|
|
1653
|
+
return record2;
|
|
1654
|
+
}
|
|
1558
1655
|
const item = document.createElement("div");
|
|
1559
1656
|
item.className = "ai-tools-item";
|
|
1560
1657
|
const head = document.createElement("button");
|
|
@@ -1574,6 +1671,7 @@ function createAiToolArea(el, opts = {}) {
|
|
|
1574
1671
|
const remove = document.createElement("span");
|
|
1575
1672
|
remove.className = "ai-tools-item-remove";
|
|
1576
1673
|
remove.setAttribute("role", "button");
|
|
1674
|
+
remove.tabIndex = 0;
|
|
1577
1675
|
remove.setAttribute("aria-label", `\u79FB\u9664 ${def.name} \u6302\u8F7D`);
|
|
1578
1676
|
remove.textContent = "\xD7";
|
|
1579
1677
|
const mount = document.createElement("div");
|
|
@@ -1589,6 +1687,13 @@ function createAiToolArea(el, opts = {}) {
|
|
|
1589
1687
|
mount.hidden = open;
|
|
1590
1688
|
item.classList.toggle("is-collapsed", open);
|
|
1591
1689
|
});
|
|
1690
|
+
head.addEventListener("keydown", (e) => {
|
|
1691
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
1692
|
+
if (!(e.target instanceof Element) || !e.target.closest(".ai-tools-item-remove")) return;
|
|
1693
|
+
e.preventDefault();
|
|
1694
|
+
e.stopPropagation();
|
|
1695
|
+
remove.click();
|
|
1696
|
+
});
|
|
1592
1697
|
const record = { def, el: item, ts: Date.now(), input, result: void 0 };
|
|
1593
1698
|
remove.addEventListener("click", (e) => {
|
|
1594
1699
|
e.stopPropagation();
|
|
@@ -1602,7 +1707,6 @@ function createAiToolArea(el, opts = {}) {
|
|
|
1602
1707
|
let error;
|
|
1603
1708
|
try {
|
|
1604
1709
|
record.result = def.run(input, mount);
|
|
1605
|
-
record.handle = record.result;
|
|
1606
1710
|
} catch (err) {
|
|
1607
1711
|
ok = false;
|
|
1608
1712
|
error = err instanceof Error ? err.message : String(err);
|