@icen.ai/ui 0.4.0 → 0.8.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 (155) hide show
  1. package/AGENTS.md +256 -0
  2. package/README.md +47 -13
  3. package/dist/base.css +31 -4
  4. package/dist/behaviors/accordion.d.mts +4 -0
  5. package/dist/behaviors/accordion.mjs +4 -3
  6. package/dist/behaviors/ai-chat.d.mts +57 -0
  7. package/dist/behaviors/ai-chat.mjs +557 -0
  8. package/dist/behaviors/ai-composer.d.mts +112 -0
  9. package/dist/behaviors/ai-composer.mjs +2032 -0
  10. package/dist/behaviors/ai-core.d.mts +151 -0
  11. package/dist/behaviors/ai-core.mjs +491 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +282 -0
  14. package/dist/behaviors/ai-panel.d.mts +98 -0
  15. package/dist/behaviors/ai-panel.mjs +1012 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1055 -0
  18. package/dist/behaviors/ai-tool.d.mts +18 -0
  19. package/dist/behaviors/ai-tool.mjs +2125 -0
  20. package/dist/behaviors/ai-tools.d.mts +81 -0
  21. package/dist/behaviors/ai-tools.mjs +1658 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +101 -0
  24. package/dist/behaviors/carousel.d.mts +4 -0
  25. package/dist/behaviors/carousel.mjs +34 -16
  26. package/dist/behaviors/charts.d.mts +250 -0
  27. package/dist/behaviors/charts.mjs +1301 -448
  28. package/dist/behaviors/command-palette.d.mts +8 -0
  29. package/dist/behaviors/command-palette.mjs +189 -0
  30. package/dist/behaviors/context-menu.d.mts +36 -0
  31. package/dist/behaviors/context-menu.mjs +6 -2
  32. package/dist/behaviors/copy.d.mts +4 -0
  33. package/dist/behaviors/copy.mjs +7 -6
  34. package/dist/behaviors/datatable.d.mts +78 -0
  35. package/dist/behaviors/datatable.mjs +54 -11
  36. package/dist/behaviors/date-picker.d.mts +4 -0
  37. package/dist/behaviors/date-picker.mjs +296 -0
  38. package/dist/behaviors/dropdown.d.mts +4 -0
  39. package/dist/behaviors/dropdown.mjs +7 -3
  40. package/dist/behaviors/input.d.mts +4 -0
  41. package/dist/behaviors/input.mjs +184 -8
  42. package/dist/behaviors/modal.d.mts +11 -0
  43. package/dist/behaviors/modal.mjs +11 -6
  44. package/dist/behaviors/nav.d.mts +7 -0
  45. package/dist/behaviors/nav.mjs +25 -9
  46. package/dist/behaviors/notification.d.mts +172 -0
  47. package/dist/behaviors/notification.mjs +908 -0
  48. package/dist/behaviors/popover.d.mts +46 -0
  49. package/dist/behaviors/popover.mjs +2 -1
  50. package/dist/behaviors/select.d.mts +4 -0
  51. package/dist/behaviors/select.mjs +268 -34
  52. package/dist/behaviors/sidebar.d.mts +4 -0
  53. package/dist/behaviors/sidebar.mjs +4 -3
  54. package/dist/behaviors/slider.d.mts +4 -0
  55. package/dist/behaviors/slider.mjs +4 -3
  56. package/dist/behaviors/split-pane.d.mts +4 -0
  57. package/dist/behaviors/split-pane.mjs +102 -0
  58. package/dist/behaviors/tabs.d.mts +8 -0
  59. package/dist/behaviors/tabs.mjs +44 -19
  60. package/dist/behaviors/tag-input.d.mts +4 -0
  61. package/dist/behaviors/tag-input.mjs +12 -3
  62. package/dist/behaviors/theme.d.mts +25 -0
  63. package/dist/behaviors/theme.mjs +3 -1
  64. package/dist/behaviors/toast.d.mts +26 -0
  65. package/dist/behaviors/toast.mjs +24 -18
  66. package/dist/behaviors/tree.d.mts +4 -0
  67. package/dist/behaviors/tree.mjs +4 -3
  68. package/dist/behaviors/upload.d.mts +4 -0
  69. package/dist/behaviors/upload.mjs +205 -17
  70. package/dist/components/ai-chat.css +1037 -0
  71. package/dist/components/ai-chat.mjs +3 -0
  72. package/dist/components/ai-composer.mjs +3 -0
  73. package/dist/components/ai-context.mjs +2 -0
  74. package/dist/components/ai-diff.css +311 -0
  75. package/dist/components/ai-diff.mjs +3 -0
  76. package/dist/components/ai-files.mjs +3 -0
  77. package/dist/components/ai-message.mjs +3 -0
  78. package/dist/components/ai-panel.css +682 -0
  79. package/dist/components/ai-reasoning.mjs +3 -0
  80. package/dist/components/ai-subagent.mjs +2 -0
  81. package/dist/components/ai-todo.mjs +2 -0
  82. package/dist/components/ai-tool-call.mjs +2 -0
  83. package/dist/components/ai-tool.css +532 -0
  84. package/dist/components/ai-usage.mjs +3 -0
  85. package/dist/components/back-top.css +88 -0
  86. package/dist/components/back-top.mjs +3 -0
  87. package/dist/components/badge.css +127 -0
  88. package/dist/components/badge.mjs +2 -0
  89. package/dist/components/breadcrumb.css +53 -0
  90. package/dist/components/btn.css +4 -7
  91. package/dist/components/card.css +66 -1
  92. package/dist/components/chart-bar.css +10 -0
  93. package/dist/components/chart-calendar.mjs +5 -0
  94. package/dist/components/chart-gauge.css +1 -1
  95. package/dist/components/chart-heatmap.css +40 -0
  96. package/dist/components/chart-scatter.css +24 -0
  97. package/dist/components/chart-scatter.mjs +4 -0
  98. package/dist/components/charts.css +86 -1
  99. package/dist/components/charts.mjs +1 -0
  100. package/dist/components/command-palette.css +246 -0
  101. package/dist/components/command-palette.mjs +3 -0
  102. package/dist/components/content.css +221 -8
  103. package/dist/components/copy.css +93 -0
  104. package/dist/components/copy.mjs +3 -0
  105. package/dist/components/datatable.css +4 -8
  106. package/dist/components/date-picker.css +240 -0
  107. package/dist/components/date-picker.mjs +3 -0
  108. package/dist/components/empty.css +69 -3
  109. package/dist/components/feedback.css +241 -10
  110. package/dist/components/form.css +129 -11
  111. package/dist/components/form.mjs +2 -0
  112. package/dist/components/input.css +25 -2
  113. package/dist/components/layout.css +36 -36
  114. package/dist/components/media.css +153 -6
  115. package/dist/components/menu.css +2 -2
  116. package/dist/components/modal.css +1 -1
  117. package/dist/components/nav.css +106 -2
  118. package/dist/components/notification.css +387 -0
  119. package/dist/components/notification.mjs +3 -0
  120. package/dist/components/pagination.css +1 -1
  121. package/dist/components/panel.css +104 -9
  122. package/dist/components/pill.css +82 -12
  123. package/dist/components/scroll-area.css +112 -0
  124. package/dist/components/scroll-area.mjs +2 -0
  125. package/dist/components/segmented.css +80 -5
  126. package/dist/components/segmented.mjs +1 -0
  127. package/dist/components/select.css +104 -17
  128. package/dist/components/sidebar.css +119 -1
  129. package/dist/components/slider.css +113 -0
  130. package/dist/components/split-pane.css +111 -0
  131. package/dist/components/split-pane.mjs +3 -0
  132. package/dist/components/stat.css +131 -12
  133. package/dist/components/steps.css +98 -1
  134. package/dist/components/switch.css +145 -7
  135. package/dist/components/table.css +111 -29
  136. package/dist/components/tabs.css +132 -22
  137. package/dist/components/tag-input.css +4 -4
  138. package/dist/components/tag.css +164 -0
  139. package/dist/components/tag.mjs +2 -0
  140. package/dist/components/toast.css +122 -5
  141. package/dist/components/toolbar.css +101 -0
  142. package/dist/components/toolbar.mjs +2 -0
  143. package/dist/components/tooltip.css +2 -2
  144. package/dist/components/upload.css +61 -4
  145. package/dist/index.d.mts +37 -0
  146. package/dist/index.mjs +9409 -929
  147. package/dist/registry.json +176 -4
  148. package/dist/retro-effects.css +21 -17
  149. package/dist/tokens/colors.css +4 -1
  150. package/dist/tokens/retro-effects.css +21 -17
  151. package/dist/tokens/style-profiles.css +21 -0
  152. package/dist/tokens.css +25 -1
  153. package/dist/ui.css +9616 -2839
  154. package/docs/spec/ai-native.md +659 -0
  155. package/package.json +15 -5
@@ -0,0 +1,1658 @@
1
+ // src/behaviors/charts.ts
2
+ var SVG_NS = "http://www.w3.org/2000/svg";
3
+ function toneVar(tone = "accent") {
4
+ switch (tone) {
5
+ case "success":
6
+ return "var(--token-success)";
7
+ case "warning":
8
+ return "var(--token-warning)";
9
+ case "error":
10
+ return "var(--token-error)";
11
+ case "muted":
12
+ return "var(--token-text-faint)";
13
+ case "accent":
14
+ default:
15
+ return "var(--token-accent)";
16
+ }
17
+ }
18
+ function fmt(opts, value) {
19
+ return opts.formatValue ? opts.formatValue(value) : String(value);
20
+ }
21
+ function maxOf(values) {
22
+ let max = 1;
23
+ for (const v of values) if (v > max) max = v;
24
+ return max;
25
+ }
26
+ function sampleLabels(labels, max = 7) {
27
+ if (labels.length <= max) return labels;
28
+ const out = [];
29
+ for (let i = 0; i < max; i++) {
30
+ out.push(labels[Math.round(i * (labels.length - 1) / (max - 1))] ?? "");
31
+ }
32
+ return out;
33
+ }
34
+ function svgEl(tag, attrs) {
35
+ const el = document.createElementNS(SVG_NS, tag);
36
+ for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
37
+ return el;
38
+ }
39
+ function renderEmpty(el, label) {
40
+ const box = document.createElement("div");
41
+ box.className = "chart-empty";
42
+ box.textContent = label ?? "\u6682\u65E0\u6570\u636E";
43
+ el.appendChild(box);
44
+ }
45
+ function legendItem(label, valueText, tone) {
46
+ const row = document.createElement("div");
47
+ row.className = "chart-legend-item";
48
+ const dot = document.createElement("span");
49
+ dot.className = "chart-legend-dot";
50
+ if (tone) dot.style.setProperty("--chart-tone", toneVar(tone));
51
+ const name = document.createElement("span");
52
+ name.className = "chart-legend-label";
53
+ name.textContent = label;
54
+ const val = document.createElement("span");
55
+ val.className = "chart-legend-value";
56
+ val.textContent = valueText;
57
+ row.append(dot, name, val);
58
+ return row;
59
+ }
60
+ function labelsRow(labels) {
61
+ const row = document.createElement("div");
62
+ row.className = "chart-labels";
63
+ for (const label of labels) {
64
+ const span = document.createElement("span");
65
+ span.textContent = label;
66
+ row.appendChild(span);
67
+ }
68
+ return row;
69
+ }
70
+ function normalizeSeriesInput(opts) {
71
+ if (opts.series && opts.series.length > 0) return opts.series;
72
+ return [{ name: "", values: opts.values ?? [], tone: opts.tone }];
73
+ }
74
+ function toneOf(tone, seriesIndex) {
75
+ if (typeof tone === "string") return toneVar(tone);
76
+ if (typeof tone === "number") return paletteVar(tone);
77
+ return seriesIndex > 0 ? paletteVar((seriesIndex - 1) % 6 + 1) : void 0;
78
+ }
79
+ var PALETTE_VARS = [
80
+ "var(--token-accent)",
81
+ "var(--token-accent-cold)",
82
+ "var(--token-success)",
83
+ "var(--token-warning)",
84
+ "var(--token-info)",
85
+ "color-mix(in srgb, var(--token-text) 50%, transparent)"
86
+ ];
87
+ function paletteVar(index) {
88
+ const i = ((Math.floor(index) - 1) % 6 + 6) % 6;
89
+ return PALETTE_VARS[i];
90
+ }
91
+ function setMarkData(node, data) {
92
+ node.setAttribute("data-chart-mark", "1");
93
+ if (data.index != null) node.setAttribute("data-chart-index", String(data.index));
94
+ if (data.seriesIndex != null) node.setAttribute("data-chart-series", String(data.seriesIndex));
95
+ if (data.seriesName != null) node.setAttribute("data-chart-series-name", String(data.seriesName));
96
+ if (data.label != null) node.setAttribute("data-chart-label", String(data.label));
97
+ if (data.value != null) node.setAttribute("data-chart-value", String(data.value));
98
+ }
99
+ function copyMarkData(target, source) {
100
+ for (const attr of Array.from(source.attributes)) {
101
+ if (attr.name.startsWith("data-chart-")) target.setAttribute(attr.name, attr.value);
102
+ }
103
+ }
104
+ var CHART_EYE_SVG = '<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" aria-hidden="true"><path d="M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0"/><circle cx="12" cy="12" r="3"/></svg>';
105
+ var CHART_EYE_OFF_SVG = '<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" aria-hidden="true"><path d="M10.73 5.08A10.4 10.4 0 0 1 12 5c7 0 10 7 10 7a13.2 13.2 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.5 13.5 0 0 0 2 12s3 7 10 7a9.7 9.7 0 0 0 5.39-1.61"/><path d="M2 2l20 20"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2"/></svg>';
106
+ function parseChartSvg(svg) {
107
+ try {
108
+ const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
109
+ const el = doc.documentElement;
110
+ return el && el.tagName.toLowerCase() === "svg" && !doc.querySelector("parsererror") ? document.importNode(el, true) : null;
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ function withChrome(el, opts, hasLegend, paint, state) {
116
+ if (el.dataset.chartBody != null) {
117
+ el.textContent = "";
118
+ paint(el);
119
+ return;
120
+ }
121
+ el.textContent = "";
122
+ const body = document.createElement("div");
123
+ body.className = "chart-body";
124
+ body.dataset.chartBody = "";
125
+ if (!opts.title && !hasLegend) {
126
+ el.appendChild(body);
127
+ paint(body);
128
+ return;
129
+ }
130
+ let hidden = state && state.hidden != null ? state.hidden : opts.legend === false;
131
+ if (state) state.hidden = hidden;
132
+ el.setAttribute("data-legend-hidden", String(hidden));
133
+ const head = document.createElement("div");
134
+ head.className = "chart-head";
135
+ const title = document.createElement("div");
136
+ title.className = "chart-title";
137
+ title.textContent = opts.title ?? "";
138
+ if (!opts.title) title.setAttribute("aria-hidden", "true");
139
+ const eye = document.createElement("button");
140
+ eye.type = "button";
141
+ eye.className = "chart-legend-eye";
142
+ eye.dataset.chartLegendEye = "";
143
+ const syncEye = () => {
144
+ const nowHidden = el.getAttribute("data-legend-hidden") === "true";
145
+ eye.setAttribute("aria-pressed", String(!nowHidden));
146
+ eye.setAttribute("aria-label", nowHidden ? "\u663E\u793A\u56FE\u4F8B" : "\u9690\u85CF\u56FE\u4F8B");
147
+ eye.setAttribute("title", nowHidden ? "\u663E\u793A\u56FE\u4F8B" : "\u9690\u85CF\u56FE\u4F8B");
148
+ const icon = parseChartSvg(nowHidden ? CHART_EYE_OFF_SVG : CHART_EYE_SVG);
149
+ if (icon) eye.replaceChildren(icon);
150
+ };
151
+ eye.addEventListener("click", () => {
152
+ const nowHidden = el.getAttribute("data-legend-hidden") === "true";
153
+ hidden = !nowHidden;
154
+ if (state) state.hidden = hidden;
155
+ el.setAttribute("data-legend-hidden", String(hidden));
156
+ syncEye();
157
+ el.dispatchEvent(new CustomEvent("icen:chart-legend-visibility", {
158
+ bubbles: true,
159
+ detail: { visible: hidden ? false : true }
160
+ }));
161
+ });
162
+ syncEye();
163
+ head.append(title, eye);
164
+ el.append(head, body);
165
+ paint(body);
166
+ }
167
+ function toggleLegend(items, scope, _opts) {
168
+ const legend = document.createElement("div");
169
+ legend.className = "chart-legend chart-legend--toggle";
170
+ for (const item of items) {
171
+ const row = legendItem(item.label, "", item.tone);
172
+ row.classList.add("chart-legend-toggle-item");
173
+ row.setAttribute("role", "button");
174
+ row.tabIndex = 0;
175
+ row.setAttribute("aria-pressed", "true");
176
+ row.dataset.chartKey = item.key;
177
+ const dot = row.querySelector(".chart-legend-dot");
178
+ const toneColor = toneOf(item.tone, item.si + 1);
179
+ if (dot && toneColor) dot.style.setProperty("--chart-tone", toneColor);
180
+ const toggle = () => {
181
+ const off = row.classList.toggle("is-off");
182
+ row.setAttribute("aria-pressed", String(!off));
183
+ scope.querySelectorAll(`[data-chart-key="${CSS.escape(item.key)}"]`).forEach((m) => {
184
+ m.classList.toggle("is-off", off);
185
+ });
186
+ legend.dispatchEvent(new CustomEvent("icen:chart-legend-toggle", {
187
+ bubbles: true,
188
+ detail: { key: item.key, seriesIndex: item.si, hidden: off }
189
+ }));
190
+ };
191
+ row.addEventListener("click", toggle);
192
+ row.addEventListener("keydown", (e) => {
193
+ if (e.key === "Enter" || e.key === " ") {
194
+ e.preventDefault();
195
+ toggle();
196
+ }
197
+ });
198
+ legend.appendChild(row);
199
+ }
200
+ for (const item of items) {
201
+ scope.querySelectorAll(`[data-chart-series="${item.si}"]`).forEach((m) => {
202
+ if (!m.hasAttribute("data-chart-key")) m.setAttribute("data-chart-key", item.key);
203
+ });
204
+ }
205
+ return legend;
206
+ }
207
+ function renderVBar(el, opts) {
208
+ if (typeof document === "undefined") return;
209
+ const seriesList = normalizeSeriesInput(opts);
210
+ const labels = opts.labels ?? [];
211
+ const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
212
+ withChrome(el, opts, seriesList.length > 1, (body) => {
213
+ if (n === 0 || seriesList.length === 0) {
214
+ renderEmpty(body, opts.emptyLabel);
215
+ return;
216
+ }
217
+ const labelsUsed = labels.slice(0, n);
218
+ let max = 1;
219
+ if (opts.stacked && seriesList.length > 1) {
220
+ for (let i = 0; i < n; i++) {
221
+ let sum = 0;
222
+ for (const s of seriesList) sum += Math.max(0, s.values[i] ?? 0);
223
+ if (sum > max) max = sum;
224
+ }
225
+ } else {
226
+ for (const s of seriesList) max = Math.max(max, maxOf(s.values.slice(0, n)));
227
+ }
228
+ const wrap = document.createElement("div");
229
+ wrap.className = "chart-vbar";
230
+ if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
231
+ for (let i = 0; i < n; i++) {
232
+ const track = document.createElement("div");
233
+ track.className = "chart-vbar-track";
234
+ if (seriesList.length > 1) {
235
+ track.classList.add("chart-vbar-track--multi");
236
+ if (opts.stacked) track.classList.add("chart-vbar-track--stacked");
237
+ }
238
+ seriesList.forEach((s, si) => {
239
+ const value = s.values[i] ?? 0;
240
+ const bar = document.createElement("div");
241
+ bar.className = "chart-vbar-bar";
242
+ const height = value > 0 ? Math.max(6, value / max * 100) : 0;
243
+ bar.style.height = `${height}%`;
244
+ const toneColor = toneOf(s.tone, si);
245
+ if (toneColor) bar.style.setProperty("--chart-tone", toneColor);
246
+ bar.setAttribute("title", seriesList.length > 1 ? `${labelsUsed[i]} \xB7 ${s.name}: ${fmt(opts, value)}` : `${labelsUsed[i]}: ${fmt(opts, value)}`);
247
+ setMarkData(bar, {
248
+ index: i,
249
+ seriesIndex: seriesList.length > 1 ? si : void 0,
250
+ seriesName: seriesList.length > 1 ? s.name : void 0,
251
+ label: labelsUsed[i] ?? "",
252
+ value
253
+ });
254
+ track.appendChild(bar);
255
+ });
256
+ wrap.appendChild(track);
257
+ }
258
+ 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));
260
+ });
261
+ }
262
+ function renderHBar(el, opts) {
263
+ if (typeof document === "undefined") return;
264
+ withChrome(el, opts, false, (body) => {
265
+ const n = Math.min(opts.labels.length, opts.values.length);
266
+ if (n === 0) {
267
+ renderEmpty(body, opts.emptyLabel);
268
+ return;
269
+ }
270
+ const labels = opts.labels.slice(0, n);
271
+ const values = opts.values.slice(0, n);
272
+ const max = maxOf(values);
273
+ const wrap = document.createElement("div");
274
+ wrap.className = "chart-hbar";
275
+ if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
276
+ values.forEach((value, i) => {
277
+ const row = document.createElement("div");
278
+ row.className = "chart-hbar-row";
279
+ const label = document.createElement("span");
280
+ label.className = "chart-hbar-label";
281
+ label.textContent = labels[i] ?? "";
282
+ const track = document.createElement("div");
283
+ track.className = "chart-hbar-track";
284
+ const bar = document.createElement("div");
285
+ bar.className = "chart-hbar-bar";
286
+ bar.style.width = value > 0 ? `${Math.max(2, value / max * 100)}%` : "0%";
287
+ setMarkData(bar, { index: i, label: labels[i] ?? "", value });
288
+ const val = document.createElement("span");
289
+ val.className = "chart-hbar-value";
290
+ val.textContent = fmt(opts, value);
291
+ track.appendChild(bar);
292
+ row.append(label, track, val);
293
+ wrap.appendChild(row);
294
+ });
295
+ body.appendChild(wrap);
296
+ });
297
+ }
298
+ function renderStack(el, opts) {
299
+ if (typeof document === "undefined") return;
300
+ withChrome(el, opts, opts.segments.length > 0, (body) => {
301
+ const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
302
+ if (opts.segments.length === 0 || total <= 0) {
303
+ renderEmpty(body, opts.emptyLabel);
304
+ return;
305
+ }
306
+ const bar = document.createElement("div");
307
+ bar.className = "chart-stack";
308
+ for (const seg of opts.segments) {
309
+ const s = document.createElement("div");
310
+ s.className = "chart-stack-seg";
311
+ s.style.width = `${seg.value / total * 100}%`;
312
+ if (seg.tone) s.style.setProperty("--chart-tone", toneVar(seg.tone));
313
+ s.setAttribute("title", `${seg.label}: ${fmt(opts, seg.value)}`);
314
+ setMarkData(s, { index: opts.segments.indexOf(seg), label: seg.label, value: seg.value });
315
+ bar.appendChild(s);
316
+ }
317
+ const legend = document.createElement("div");
318
+ legend.className = "chart-legend";
319
+ for (const seg of opts.segments) {
320
+ legend.appendChild(legendItem(seg.label, fmt(opts, seg.value), seg.tone));
321
+ }
322
+ body.append(bar, legend);
323
+ });
324
+ }
325
+ function renderDonut(el, opts) {
326
+ if (typeof document === "undefined") return;
327
+ withChrome(el, opts, opts.segments.length > 0, (body) => {
328
+ const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
329
+ if (opts.segments.length === 0 || total <= 0) {
330
+ renderEmpty(body, opts.emptyLabel);
331
+ return;
332
+ }
333
+ const radius = 42;
334
+ const circumference = 2 * Math.PI * radius;
335
+ const wrap = document.createElement("div");
336
+ wrap.className = "chart-donut";
337
+ const svg = svgEl("svg", { viewBox: "0 0 120 120", role: "img" });
338
+ svg.style.transform = "rotate(-90deg)";
339
+ svg.appendChild(svgEl("circle", {
340
+ cx: "60",
341
+ cy: "60",
342
+ r: String(radius),
343
+ fill: "none",
344
+ stroke: "var(--token-line-soft)",
345
+ "stroke-width": "14"
346
+ }));
347
+ let offset = 0;
348
+ opts.segments.forEach((seg, segIndex) => {
349
+ const dash = seg.value / total * circumference;
350
+ const arc = svgEl("circle", {
351
+ cx: "60",
352
+ cy: "60",
353
+ r: String(radius),
354
+ fill: "none",
355
+ stroke: toneVar(seg.tone),
356
+ "stroke-width": "14",
357
+ "stroke-dasharray": `${dash} ${circumference - dash}`,
358
+ "stroke-dashoffset": String(-offset),
359
+ "stroke-linecap": "butt"
360
+ });
361
+ setMarkData(arc, { index: segIndex, label: seg.label, value: seg.value });
362
+ svg.appendChild(arc);
363
+ offset += dash;
364
+ });
365
+ const legend = document.createElement("div");
366
+ legend.className = "chart-legend";
367
+ for (const seg of opts.segments) {
368
+ legend.appendChild(legendItem(seg.label, `${Math.round(seg.value / total * 100)}%`, seg.tone));
369
+ }
370
+ wrap.append(svg, legend);
371
+ body.appendChild(wrap);
372
+ });
373
+ }
374
+ var lineGradientSeq = 0;
375
+ function renderLine(el, opts) {
376
+ if (typeof document === "undefined") return;
377
+ const seriesList = normalizeSeriesInput(opts);
378
+ const labels = opts.labels ?? [];
379
+ const n = Math.min(labels.length, ...seriesList.map((s) => s.values.length));
380
+ withChrome(el, opts, seriesList.length > 1, (body) => {
381
+ if (n === 0 || seriesList.length === 0) {
382
+ renderEmpty(body, opts.emptyLabel);
383
+ return;
384
+ }
385
+ const labelsUsed = labels.slice(0, n);
386
+ const width = 360;
387
+ const height = 150;
388
+ const pad = 14;
389
+ let max = 1;
390
+ for (const s of seriesList) max = Math.max(max, maxOf(s.values.slice(0, n)));
391
+ const wrap = document.createElement("div");
392
+ wrap.className = "chart-line";
393
+ const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
394
+ const defs = svgEl("defs", {});
395
+ for (let i = 0; i < 4; i++) {
396
+ const y = pad + i * (height - pad * 2) / 3;
397
+ svg.appendChild(svgEl("line", {
398
+ x1: String(pad),
399
+ x2: String(width - pad),
400
+ y1: String(y),
401
+ y2: String(y),
402
+ stroke: "var(--token-line-soft)",
403
+ "stroke-width": "1"
404
+ }));
405
+ }
406
+ seriesList.forEach((s, si) => {
407
+ const tone = toneOf(s.tone, si) ?? toneVar(opts.tone);
408
+ const values = s.values.slice(0, n);
409
+ const points = values.map((value, i) => ({
410
+ i,
411
+ x: n <= 1 ? width / 2 : pad + i / (n - 1) * (width - pad * 2),
412
+ y: height - pad - Math.max(0, value) / max * (height - pad * 2),
413
+ value,
414
+ label: labelsUsed[i] ?? ""
415
+ }));
416
+ const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
417
+ const last = points[points.length - 1];
418
+ const first = points[0];
419
+ const fillArea = seriesList.length === 1 || opts.fill;
420
+ if (fillArea) {
421
+ const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
422
+ const gradientId = `icen-chart-line-${++lineGradientSeq}`;
423
+ const gradient = svgEl("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1" });
424
+ const strong = seriesList.length > 1 ? "0.20" : "0.32";
425
+ gradient.appendChild(svgEl("stop", { offset: "0%", "stop-color": tone, "stop-opacity": strong }));
426
+ gradient.appendChild(svgEl("stop", { offset: "100%", "stop-color": tone, "stop-opacity": "0.02" }));
427
+ defs.appendChild(gradient);
428
+ const areaPath = svgEl("path", { d: area, fill: `url(#${gradientId})` });
429
+ if (seriesList.length > 1) {
430
+ setMarkData(areaPath, { seriesIndex: si, seriesName: s.name, label: s.name, value: values.reduce((a, b) => a + b, 0) });
431
+ }
432
+ svg.appendChild(areaPath);
433
+ }
434
+ const linePath = svgEl("path", {
435
+ d: line,
436
+ fill: "none",
437
+ stroke: tone,
438
+ "stroke-width": seriesList.length > 1 ? "2" : "2.5",
439
+ "stroke-linecap": "round",
440
+ "stroke-linejoin": "round"
441
+ });
442
+ const lineTitle = svgEl("title", {});
443
+ lineTitle.textContent = s.name || "\u6570\u503C";
444
+ linePath.appendChild(lineTitle);
445
+ setMarkData(linePath, { seriesIndex: si, seriesName: s.name, label: s.name, value: values[values.length - 1] ?? 0 });
446
+ svg.appendChild(linePath);
447
+ for (const p of points) {
448
+ const dot = svgEl("circle", {
449
+ cx: p.x.toFixed(1),
450
+ cy: p.y.toFixed(1),
451
+ r: "3",
452
+ fill: "var(--token-card)",
453
+ stroke: tone,
454
+ "stroke-width": "2"
455
+ });
456
+ const title = svgEl("title", {});
457
+ title.textContent = seriesList.length > 1 ? `${p.label} \xB7 ${s.name}: ${fmt(opts, p.value)}` : `${p.label}: ${fmt(opts, p.value)}`;
458
+ dot.appendChild(title);
459
+ setMarkData(dot, {
460
+ index: p.i,
461
+ seriesIndex: seriesList.length > 1 ? si : void 0,
462
+ seriesName: seriesList.length > 1 ? s.name : void 0,
463
+ label: p.label,
464
+ value: p.value
465
+ });
466
+ svg.appendChild(dot);
467
+ const hit = svgEl("circle", {
468
+ cx: p.x.toFixed(1),
469
+ cy: p.y.toFixed(1),
470
+ r: "8",
471
+ fill: "transparent",
472
+ "data-chart-hit": "1"
473
+ });
474
+ copyMarkData(hit, dot);
475
+ svg.appendChild(hit);
476
+ }
477
+ });
478
+ if (defs.firstChild) svg.insertBefore(defs, svg.firstChild);
479
+ wrap.append(svg, labelsRow(sampleLabels(labelsUsed)));
480
+ 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));
482
+ });
483
+ }
484
+ function renderRadar(el, opts) {
485
+ if (typeof document === "undefined") return;
486
+ withChrome(el, opts, opts.series.length > 1, (body) => {
487
+ const axes = opts.axes;
488
+ const n = axes.length;
489
+ if (n < 3 || opts.series.length === 0) {
490
+ renderEmpty(body, opts.emptyLabel);
491
+ return;
492
+ }
493
+ let maxVal = opts.max ?? 0;
494
+ if (opts.max == null) {
495
+ for (const s of opts.series) {
496
+ for (let i = 0; i < n; i++) maxVal = Math.max(maxVal, s.values[i] ?? 0);
497
+ }
498
+ maxVal = niceCeil(maxVal);
499
+ }
500
+ if (maxVal <= 0) maxVal = 1;
501
+ const size = 200;
502
+ const cx = size / 2;
503
+ const cy = size / 2;
504
+ const radius = 78;
505
+ const levels = Math.max(2, opts.levels ?? 4);
506
+ const angleOf = (i) => -Math.PI / 2 + i * 2 * Math.PI / n;
507
+ const pointAt = (i, r) => ({
508
+ x: cx + r * Math.cos(angleOf(i)),
509
+ y: cy + r * Math.sin(angleOf(i))
510
+ });
511
+ const wrap = document.createElement("div");
512
+ wrap.className = "chart-radar";
513
+ const svg = svgEl("svg", { viewBox: `0 0 ${size} ${size}`, role: "img" });
514
+ for (let lv = 1; lv <= levels; lv++) {
515
+ const r = radius * lv / levels;
516
+ const pts = Array.from({ length: n }, (_, i) => {
517
+ const p = pointAt(i, r);
518
+ return `${p.x.toFixed(1)},${p.y.toFixed(1)}`;
519
+ }).join(" ");
520
+ svg.appendChild(svgEl("polygon", {
521
+ points: pts,
522
+ fill: "none",
523
+ stroke: "var(--token-line-soft)",
524
+ "stroke-width": "1",
525
+ "stroke-dasharray": lv === levels ? "0" : "3 2"
526
+ }));
527
+ }
528
+ for (let i = 0; i < n; i++) {
529
+ const edge = pointAt(i, radius);
530
+ svg.appendChild(svgEl("line", {
531
+ x1: String(cx),
532
+ y1: String(cy),
533
+ x2: edge.x.toFixed(1),
534
+ y2: edge.y.toFixed(1),
535
+ stroke: "var(--token-line-soft)",
536
+ "stroke-width": "1"
537
+ }));
538
+ const labelP = pointAt(i, radius + 14);
539
+ const txt = svgEl("text", {
540
+ x: labelP.x.toFixed(1),
541
+ y: labelP.y.toFixed(1),
542
+ "text-anchor": "middle",
543
+ "dominant-baseline": "middle",
544
+ "font-size": "9",
545
+ fill: "var(--token-text-muted)"
546
+ });
547
+ txt.textContent = axes[i] ?? "";
548
+ svg.appendChild(txt);
549
+ }
550
+ const legendItems = [];
551
+ opts.series.forEach((s, si) => {
552
+ const tone = toneVar(s.tone);
553
+ const pts = Array.from({ length: n }, (_, i) => {
554
+ const v = Math.max(0, Math.min(maxVal, s.values[i] ?? 0));
555
+ const p = pointAt(i, v / maxVal * radius);
556
+ return `${p.x.toFixed(1)},${p.y.toFixed(1)}`;
557
+ }).join(" ");
558
+ const polyFill = svgEl("polygon", {
559
+ points: pts,
560
+ fill: tone,
561
+ "fill-opacity": "0.16",
562
+ stroke: tone,
563
+ "stroke-width": "2",
564
+ "stroke-linejoin": "round"
565
+ });
566
+ const title = svgEl("title", {});
567
+ title.textContent = `${s.name}: ${s.values.map((v) => fmt(opts, v)).join(" / ")}`;
568
+ polyFill.appendChild(title);
569
+ setMarkData(polyFill, { seriesIndex: si, seriesName: s.name || void 0, label: s.name, value: s.values.reduce((a, b) => a + (b || 0), 0) });
570
+ svg.appendChild(polyFill);
571
+ for (let i = 0; i < n; i++) {
572
+ const v = Math.max(0, Math.min(maxVal, s.values[i] ?? 0));
573
+ const p = pointAt(i, v / maxVal * radius);
574
+ const dot = svgEl("circle", {
575
+ cx: p.x.toFixed(1),
576
+ cy: p.y.toFixed(1),
577
+ r: "2.5",
578
+ fill: "var(--token-card)",
579
+ stroke: tone,
580
+ "stroke-width": "1.5"
581
+ });
582
+ setMarkData(dot, { index: i, seriesIndex: si, seriesName: s.name || void 0, label: axes[i] ?? "", value: s.values[i] ?? 0 });
583
+ svg.appendChild(dot);
584
+ }
585
+ legendItems.push({ key: s.name || `\u7CFB\u5217${si + 1}`, label: s.name || `\u7CFB\u5217${si + 1}`, tone: s.tone, si });
586
+ });
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
+ })());
592
+ body.appendChild(wrap);
593
+ });
594
+ }
595
+ function niceCeil(v) {
596
+ if (v <= 0) return 1;
597
+ const exp = Math.floor(Math.log10(v));
598
+ const base = Math.pow(10, exp);
599
+ const frac = v / base;
600
+ let nice;
601
+ if (frac <= 1) nice = 1;
602
+ else if (frac <= 2) nice = 2;
603
+ else if (frac <= 5) nice = 5;
604
+ else nice = 10;
605
+ return nice * base;
606
+ }
607
+ function isoDate(d) {
608
+ const m = String(d.getMonth() + 1).padStart(2, "0");
609
+ const day = String(d.getDate()).padStart(2, "0");
610
+ return `${d.getFullYear()}-${m}-${day}`;
611
+ }
612
+ function renderHeatmap(el, opts) {
613
+ if (typeof document === "undefined") return;
614
+ withChrome(el, opts, true, (body) => {
615
+ let entries;
616
+ if (opts.data && opts.data.length > 0) {
617
+ entries = opts.data;
618
+ } else if (opts.values && opts.values.length > 0) {
619
+ const today = /* @__PURE__ */ new Date();
620
+ entries = opts.values.map((value, i) => {
621
+ const d = new Date(today);
622
+ d.setDate(d.getDate() - (opts.values.length - 1 - i));
623
+ return { date: isoDate(d), value };
624
+ });
625
+ } else {
626
+ renderEmpty(body, opts.emptyLabel);
627
+ return;
628
+ }
629
+ const weeks = opts.weeks ?? Math.ceil(entries.length / 7);
630
+ if (entries.length > weeks * 7) entries = entries.slice(entries.length - weeks * 7);
631
+ const max = maxOf(entries.map((e) => e.value));
632
+ const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
633
+ const wrap = document.createElement("div");
634
+ wrap.className = "chart-heatmap";
635
+ if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
636
+ const grid = document.createElement("div");
637
+ grid.className = "chart-heatmap-grid";
638
+ 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) {
647
+ const cell = document.createElement("span");
648
+ cell.className = "chart-heatmap-cell";
649
+ cell.dataset.level = String(levelOf(entry.value));
650
+ cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
651
+ setMarkData(cell, { index: entries.indexOf(entry), label: entry.date, value: entry.value });
652
+ grid.appendChild(cell);
653
+ }
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);
669
+ body.appendChild(wrap);
670
+ });
671
+ }
672
+ function renderSparkline(el, opts) {
673
+ if (typeof document === "undefined") return;
674
+ withChrome(el, opts, false, (body) => {
675
+ const values = opts.values;
676
+ if (values.length === 0) {
677
+ renderEmpty(body, opts.emptyLabel);
678
+ return;
679
+ }
680
+ const width = 96;
681
+ const height = 28;
682
+ const pad = 3;
683
+ const max = maxOf(values);
684
+ const tone = toneVar(opts.tone);
685
+ const points = values.map((value, i) => ({
686
+ x: values.length <= 1 ? width / 2 : pad + i / (values.length - 1) * (width - pad * 2),
687
+ y: height - pad - value / max * (height - pad * 2),
688
+ value
689
+ }));
690
+ const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
691
+ const last = points[points.length - 1];
692
+ const first = points[0];
693
+ const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
694
+ const gradientId = `icen-chart-line-${++lineGradientSeq}`;
695
+ const wrap = document.createElement("div");
696
+ wrap.className = "chart-sparkline";
697
+ const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
698
+ const defs = svgEl("defs", {});
699
+ const gradient = svgEl("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1" });
700
+ gradient.appendChild(svgEl("stop", { offset: "0%", "stop-color": tone, "stop-opacity": "0.28" }));
701
+ gradient.appendChild(svgEl("stop", { offset: "100%", "stop-color": tone, "stop-opacity": "0.02" }));
702
+ defs.appendChild(gradient);
703
+ svg.appendChild(defs);
704
+ svg.appendChild(svgEl("path", { d: area, fill: `url(#${gradientId})` }));
705
+ svg.appendChild(svgEl("path", {
706
+ d: line,
707
+ fill: "none",
708
+ stroke: tone,
709
+ "stroke-width": "1.8",
710
+ "stroke-linecap": "round",
711
+ "stroke-linejoin": "round"
712
+ }));
713
+ const dot = svgEl("circle", {
714
+ cx: last.x.toFixed(1),
715
+ cy: last.y.toFixed(1),
716
+ r: "2.4",
717
+ fill: "var(--token-card)",
718
+ stroke: tone,
719
+ "stroke-width": "1.6"
720
+ });
721
+ const title = svgEl("title", {});
722
+ title.textContent = fmt(opts, last.value);
723
+ dot.appendChild(title);
724
+ svg.appendChild(dot);
725
+ wrap.appendChild(svg);
726
+ body.appendChild(wrap);
727
+ });
728
+ }
729
+ function renderGauge(el, opts) {
730
+ if (typeof document === "undefined") return;
731
+ el.textContent = "";
732
+ const max = opts.max ?? 100;
733
+ const ratio = max > 0 ? Math.min(1, Math.max(0, opts.value / max)) : 0;
734
+ const radius = 42;
735
+ const circumference = 2 * Math.PI * radius;
736
+ const wrap = document.createElement("div");
737
+ wrap.className = "chart-gauge";
738
+ if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
739
+ const svg = svgEl("svg", { viewBox: "0 0 120 120", role: "img" });
740
+ svg.style.transform = "rotate(-90deg)";
741
+ svg.appendChild(svgEl("circle", {
742
+ cx: "60",
743
+ cy: "60",
744
+ r: String(radius),
745
+ fill: "none",
746
+ stroke: "var(--token-line-soft)",
747
+ "stroke-width": "10"
748
+ }));
749
+ const dash = ratio * circumference;
750
+ svg.appendChild(svgEl("circle", {
751
+ cx: "60",
752
+ cy: "60",
753
+ r: String(radius),
754
+ fill: "none",
755
+ stroke: toneVar(opts.tone),
756
+ "stroke-width": "10",
757
+ "stroke-dasharray": `${dash} ${circumference - dash}`,
758
+ "stroke-linecap": "round"
759
+ }));
760
+ const center = document.createElement("div");
761
+ center.className = "chart-gauge-center";
762
+ const val = document.createElement("span");
763
+ val.className = "chart-gauge-value";
764
+ val.textContent = opts.formatValue ? opts.formatValue(opts.value) : `${Math.round(ratio * 100)}%`;
765
+ center.appendChild(val);
766
+ if (opts.label) {
767
+ const lab = document.createElement("span");
768
+ lab.className = "chart-gauge-label";
769
+ lab.textContent = opts.label;
770
+ center.appendChild(lab);
771
+ }
772
+ wrap.append(svg, center);
773
+ el.appendChild(wrap);
774
+ }
775
+ function renderCalendar(el, opts) {
776
+ if (typeof document === "undefined") return;
777
+ withChrome(el, opts, true, (body) => {
778
+ let entries;
779
+ if (opts.data && opts.data.length > 0) {
780
+ entries = opts.data;
781
+ } else if (opts.dates && opts.dates.length > 0 && opts.values) {
782
+ entries = opts.dates.slice(0, opts.values.length).map((date, i) => ({ date, value: opts.values[i] ?? 0 }));
783
+ } else {
784
+ renderEmpty(body, opts.emptyLabel);
785
+ return;
786
+ }
787
+ const wrap = document.createElement("div");
788
+ wrap.className = "chart-calendar";
789
+ if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
790
+ const grid = document.createElement("div");
791
+ grid.className = "chart-heatmap-grid";
792
+ grid.setAttribute("role", "img");
793
+ const months = document.createElement("div");
794
+ months.className = "chart-calendar-months";
795
+ const weekdayRow = document.createElement("div");
796
+ weekdayRow.className = "chart-calendar-weekdays";
797
+ ["\u4E00", "", "\u4E09", "", "\u4E94", "", "\u65E5"].forEach((w) => {
798
+ const s = document.createElement("span");
799
+ s.textContent = w;
800
+ weekdayRow.appendChild(s);
801
+ });
802
+ 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
+ }
811
+ let lastMonth = -1;
812
+ entries.forEach((entry, i) => {
813
+ const d = /* @__PURE__ */ new Date(`${entry.date}T00:00:00`);
814
+ const col = Math.floor((pad + i) / 7);
815
+ if (d.getMonth() !== lastMonth) {
816
+ lastMonth = d.getMonth();
817
+ const label = document.createElement("span");
818
+ label.className = "chart-calendar-month";
819
+ label.textContent = `${d.getMonth() + 1}\u6708`;
820
+ label.style.setProperty("--col", String(col));
821
+ months.appendChild(label);
822
+ }
823
+ const cell = document.createElement("span");
824
+ cell.className = "chart-heatmap-cell";
825
+ cell.dataset.level = String(levelOf(entry.value));
826
+ cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
827
+ setMarkData(cell, { index: i, label: entry.date, value: entry.value });
828
+ grid.appendChild(cell);
829
+ });
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);
844
+ const columns = Math.max(1, Math.ceil((pad + entries.length) / 7));
845
+ months.style.setProperty("--columns", String(columns));
846
+ wrap.append(months, weekdayRow, grid, legend);
847
+ body.appendChild(wrap);
848
+ });
849
+ }
850
+ function renderScatter(el, opts) {
851
+ if (typeof document === "undefined") return;
852
+ withChrome(el, opts, false, (body) => {
853
+ const pts = opts.points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
854
+ if (pts.length === 0) {
855
+ renderEmpty(body, opts.emptyLabel);
856
+ return;
857
+ }
858
+ const width = 360;
859
+ const height = 220;
860
+ const pad = 30;
861
+ let xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, sMax = 0;
862
+ for (const p of pts) {
863
+ if (p.x < xMin) xMin = p.x;
864
+ if (p.x > xMax) xMax = p.x;
865
+ if (p.y < yMin) yMin = p.y;
866
+ if (p.y > yMax) yMax = p.y;
867
+ if ((p.size ?? 0) > sMax) sMax = p.size ?? 0;
868
+ }
869
+ if (xMax === xMin) xMax = xMin + 1;
870
+ if (yMax === yMin) yMax = yMin + 1;
871
+ const rOf = (size) => size && sMax > 0 ? 3 + size / sMax * 7 : 4;
872
+ const wrap = document.createElement("div");
873
+ wrap.className = "chart-scatter";
874
+ const tone = toneVar(opts.tone);
875
+ const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
876
+ const xAt = (x) => pad + (x - xMin) / (xMax - xMin) * (width - pad * 2);
877
+ const yAt = (y) => height - pad - (y - yMin) / (yMax - yMin) * (height - pad * 2);
878
+ for (let i = 0; i <= 3; i++) {
879
+ const y = pad + i * (height - pad * 2) / 3;
880
+ svg.appendChild(svgEl("line", {
881
+ x1: String(pad),
882
+ x2: String(width - pad),
883
+ y1: String(y),
884
+ y2: String(y),
885
+ stroke: "var(--token-line-soft)",
886
+ "stroke-width": "1"
887
+ }));
888
+ const tv = yMin + (yMax - yMin) * (3 - i) / 3;
889
+ const txt = svgEl("text", {
890
+ x: String(pad - 4),
891
+ y: String(y + 3),
892
+ "text-anchor": "end",
893
+ "font-size": "8",
894
+ fill: "var(--token-text-faint)"
895
+ });
896
+ txt.textContent = String(Math.round(tv * 10) / 10);
897
+ svg.appendChild(txt);
898
+ }
899
+ for (let i = 0; i <= 4; i++) {
900
+ const x = pad + i * (width - pad * 2) / 4;
901
+ svg.appendChild(svgEl("line", {
902
+ x1: String(x),
903
+ x2: String(x),
904
+ y1: String(pad),
905
+ y2: String(height - pad),
906
+ stroke: "var(--token-line-soft)",
907
+ "stroke-width": "1",
908
+ "stroke-dasharray": "2 3"
909
+ }));
910
+ const tv = xMin + (xMax - xMin) * i / 4;
911
+ const txt = svgEl("text", {
912
+ x: String(x),
913
+ y: String(height - pad + 12),
914
+ "text-anchor": "middle",
915
+ "font-size": "8",
916
+ fill: "var(--token-text-faint)"
917
+ });
918
+ txt.textContent = String(Math.round(tv * 10) / 10);
919
+ svg.appendChild(txt);
920
+ }
921
+ if (opts.yLabel) {
922
+ const t = svgEl("text", { x: "10", y: String(pad - 10), "font-size": "8", fill: "var(--token-text-faint)" });
923
+ t.textContent = opts.yLabel;
924
+ svg.appendChild(t);
925
+ }
926
+ if (opts.xLabel) {
927
+ const t = svgEl("text", { x: String(width - pad), y: String(pad - 10), "text-anchor": "end", "font-size": "8", fill: "var(--token-text-faint)" });
928
+ t.textContent = opts.xLabel;
929
+ svg.appendChild(t);
930
+ }
931
+ pts.forEach((p, i) => {
932
+ const c = svgEl("circle", {
933
+ cx: xAt(p.x).toFixed(1),
934
+ cy: yAt(p.y).toFixed(1),
935
+ r: rOf(p.size).toFixed(1),
936
+ fill: tone,
937
+ "fill-opacity": "0.55",
938
+ stroke: tone,
939
+ "stroke-width": "1"
940
+ });
941
+ const title = svgEl("title", {});
942
+ title.textContent = `${p.label ?? `#${i + 1}`}: (${p.x}, ${p.y}${p.size != null ? `, size ${p.size}` : ""})`;
943
+ c.appendChild(title);
944
+ setMarkData(c, { index: i, label: p.label ?? `#${i + 1}`, value: p.y });
945
+ c.dataset.chartX = String(p.x);
946
+ svg.appendChild(c);
947
+ });
948
+ wrap.appendChild(svg);
949
+ body.appendChild(wrap);
950
+ });
951
+ }
952
+ function chartFormatValue(format) {
953
+ if (!format) return (v) => String(v);
954
+ const notation = format.notation ?? "raw";
955
+ return (v) => {
956
+ let text;
957
+ if (notation === "compact") {
958
+ const abs = Math.abs(v);
959
+ text = abs >= 1e6 ? `${Math.round(v / 1e5) / 10}M` : abs >= 1e3 ? `${Math.round(v / 100) / 10}k` : String(v);
960
+ } else if (notation === "percent") {
961
+ text = `${Math.round(v * 10) / 10}%`;
962
+ } else if (notation === "ms") {
963
+ if (v <= 0) text = "0ms";
964
+ else if (v < 1e3) text = `${Math.round(v)}ms`;
965
+ else if (v < 6e4) text = `${Math.round(v / 100) / 10}s`;
966
+ else text = `${Math.floor(v / 6e4)}m`;
967
+ } else {
968
+ text = String(Math.round(v * 100) / 100);
969
+ }
970
+ return format.unit ? `${text} ${format.unit}` : text;
971
+ };
972
+ }
973
+ function pivotData(spec) {
974
+ const data = spec.data;
975
+ if (!Array.isArray(data) || data.length === 0) return {};
976
+ const d = spec.dims ?? {};
977
+ const labelField = d.label ?? "label";
978
+ const seriesField = d.series ?? "series";
979
+ const valueField = d.value ?? "value";
980
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : Number(v) || 0;
981
+ const str = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
982
+ if (d.x || data.some((r) => typeof r.x === "number")) {
983
+ const xf = d.x ?? "x";
984
+ const yf = d.y ?? "y";
985
+ const sf = d.size ?? "size";
986
+ const lf = d.label ?? "label";
987
+ const points = data.filter((r) => r[xf] != null && r[yf] != null).map((r) => ({
988
+ x: num(r[xf]),
989
+ y: num(r[yf]),
990
+ size: r[sf] != null ? num(r[sf]) : void 0,
991
+ label: r[lf] != null ? str(r[lf]) : void 0
992
+ }));
993
+ return { points };
994
+ }
995
+ const labels = [];
996
+ const seenLabel = /* @__PURE__ */ new Set();
997
+ const hasSeries = data.some((r) => r[seriesField] != null && r[seriesField] !== "");
998
+ const seriesNames = [];
999
+ const seenSeries = /* @__PURE__ */ new Set();
1000
+ for (const r of data) {
1001
+ const l = str(r[labelField]);
1002
+ if (!seenLabel.has(l)) {
1003
+ seenLabel.add(l);
1004
+ labels.push(l);
1005
+ }
1006
+ if (hasSeries) {
1007
+ const sn = str(r[seriesField]);
1008
+ if (!seenSeries.has(sn)) {
1009
+ seenSeries.add(sn);
1010
+ seriesNames.push(sn);
1011
+ }
1012
+ }
1013
+ }
1014
+ const labelIndex = new Map(labels.map((l, i) => [l, i]));
1015
+ const seriesIndex = new Map(seriesNames.map((s, i) => [s, i]));
1016
+ const matrix = seriesNames.map(() => labels.map(() => NaN));
1017
+ for (const r of data) {
1018
+ const li = labelIndex.get(str(r[labelField])) ?? 0;
1019
+ const si = hasSeries ? seriesIndex.get(str(r[seriesField])) ?? 0 : 0;
1020
+ (matrix[si] ?? [])[li] = num(r[valueField]);
1021
+ }
1022
+ for (const row of matrix) for (let i = 0; i < row.length; i++) if (Number.isNaN(row[i])) row[i] = 0;
1023
+ if (hasSeries) {
1024
+ return {
1025
+ labels,
1026
+ series: seriesNames.map((name, si) => ({ name, values: matrix[si] ?? [] }))
1027
+ };
1028
+ }
1029
+ return { labels, values: matrix[0] ?? [] };
1030
+ }
1031
+ var CHART_TYPE_ALIASES = {
1032
+ bar: "vbar",
1033
+ column: "vbar",
1034
+ "vertical-bar": "vbar",
1035
+ "bar-vertical": "vbar",
1036
+ "horizontal-bar": "hbar",
1037
+ "bar-horizontal": "hbar",
1038
+ ranking: "hbar",
1039
+ pie: "donut",
1040
+ ring: "donut",
1041
+ "pie-chart": "donut",
1042
+ "stacked-bar": "stack",
1043
+ "stacked-bar-100": "stack",
1044
+ ratio: "stack",
1045
+ time: "line",
1046
+ timeline: "line",
1047
+ trend: "line",
1048
+ "trend-line": "line",
1049
+ contribution: "calendar",
1050
+ "contribution-graph": "calendar",
1051
+ "calendar-heatmap": "calendar",
1052
+ heat: "heatmap",
1053
+ matrix: "heatmap",
1054
+ bubble: "scatter",
1055
+ "scatter-plot": "scatter",
1056
+ dial: "gauge",
1057
+ progress: "gauge",
1058
+ mini: "sparkline",
1059
+ "trend-sparkline": "sparkline",
1060
+ spider: "radar",
1061
+ "radar-chart": "radar"
1062
+ };
1063
+ function toNum(v) {
1064
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1065
+ if (typeof v === "string" && v.trim() !== "") {
1066
+ const n = Number(v.replace(/[,\s%]/g, ""));
1067
+ if (Number.isFinite(n)) return n;
1068
+ }
1069
+ return void 0;
1070
+ }
1071
+ function normalizeChartSpec(raw) {
1072
+ let src = raw;
1073
+ if (typeof src === "string") {
1074
+ try {
1075
+ src = JSON.parse(src);
1076
+ } catch {
1077
+ return {};
1078
+ }
1079
+ }
1080
+ if (!src || typeof src !== "object" || Array.isArray(src)) {
1081
+ if (Array.isArray(raw)) src = { data: raw };
1082
+ else return {};
1083
+ }
1084
+ const s = src;
1085
+ const spec = {};
1086
+ if (typeof s.type === "string") {
1087
+ const t = s.type.trim().toLowerCase();
1088
+ spec.type = CHART_TYPE_ALIASES[t] ?? t;
1089
+ }
1090
+ for (const k of ["title", "tone", "emptyLabel"]) {
1091
+ if (typeof s[k] === "string") spec[k] = s[k];
1092
+ }
1093
+ for (const k of ["stacked", "fill", "tooltip", "legend"]) {
1094
+ if (typeof s[k] === "boolean") spec[k] = s[k];
1095
+ }
1096
+ for (const k of ["weeks", "max", "levels"]) {
1097
+ const n = toNum(s[k]);
1098
+ if (n != null) spec[k] = n;
1099
+ }
1100
+ if (Array.isArray(s.labels)) spec.labels = s.labels.map((v) => String(v ?? ""));
1101
+ if (Array.isArray(s.values)) {
1102
+ const values = s.values.map(toNum).filter((v) => v != null);
1103
+ if (values.length > 0) spec.values = values;
1104
+ }
1105
+ if (Array.isArray(s.dates)) spec.dates = s.dates.map((v) => String(v ?? ""));
1106
+ if (Array.isArray(s.axes)) spec.axes = s.axes.map((v) => String(v ?? ""));
1107
+ if (Array.isArray(s.series)) {
1108
+ const series = s.series.filter((v) => !!v && typeof v === "object").map((v, i) => {
1109
+ const values = Array.isArray(v.values) ? v.values.map(toNum).filter((n) => n != null) : [];
1110
+ const sv = { name: String(v.name ?? `\u7CFB\u5217${i + 1}`), values };
1111
+ const tone = v.tone;
1112
+ if (typeof tone === "string" || typeof tone === "number") sv.tone = tone;
1113
+ return sv;
1114
+ }).filter((v) => v.values.length > 0);
1115
+ if (series.length > 0) spec.series = series;
1116
+ }
1117
+ if (Array.isArray(s.segments)) {
1118
+ const segments = s.segments.filter((v) => !!v && typeof v === "object").map((v) => {
1119
+ const seg = { label: String(v.label ?? v.name ?? ""), value: toNum(v.value) ?? 0 };
1120
+ if (typeof v.tone === "string") seg.tone = v.tone;
1121
+ return seg;
1122
+ }).filter((v) => v.label !== "" || v.value !== 0);
1123
+ if (segments.length > 0) spec.segments = segments;
1124
+ }
1125
+ if (Array.isArray(s.points)) {
1126
+ const points = s.points.filter((v) => !!v && typeof v === "object").map((v) => ({
1127
+ x: toNum(v.x) ?? 0,
1128
+ y: toNum(v.y) ?? 0,
1129
+ size: toNum(v.size),
1130
+ label: typeof v.label === "string" ? v.label : void 0
1131
+ }));
1132
+ if (points.length > 0) spec.points = points;
1133
+ }
1134
+ if (Array.isArray(s.data)) spec.data = s.data.filter((v) => !!v && typeof v === "object");
1135
+ if (s.dims && typeof s.dims === "object" && !Array.isArray(s.dims)) spec.dims = s.dims;
1136
+ if (s.format && typeof s.format === "object") {
1137
+ const f = s.format;
1138
+ const format = {};
1139
+ if (typeof f.unit === "string") format.unit = f.unit;
1140
+ if (typeof f.notation === "string" && ["compact", "percent", "ms", "raw"].includes(f.notation)) {
1141
+ format.notation = f.notation;
1142
+ }
1143
+ spec.format = format;
1144
+ }
1145
+ const isDateRecords = spec.data?.length && spec.data[0] && "date" in spec.data[0];
1146
+ if (spec.data && spec.data.length > 0 && !isDateRecords && !spec.series && !spec.points && !spec.values && !spec.segments) {
1147
+ Object.assign(spec, pivotData(spec));
1148
+ }
1149
+ return spec;
1150
+ }
1151
+ function looksTemporal(labels) {
1152
+ if (labels.length === 0) return false;
1153
+ let hits = 0;
1154
+ for (const l of labels) {
1155
+ if (/^\d{4}-\d{2}(-\d{2})?$/.test(l)) hits++;
1156
+ else if (/^\d{4}\/\d{1,2}(\/\d{1,2})?$/.test(l)) hits++;
1157
+ else if (/^\d{4}年/.test(l)) hits++;
1158
+ else if (/^[Qq][1-4]$/.test(l)) hits++;
1159
+ else if (/^(周一|周二|周三|周四|周五|周六|周日|Mon|Tue|Wed|Thu|Fri|Sat|Sun)/i.test(l)) hits++;
1160
+ else if (/^第?\d+[日时周月]/.test(l)) hits++;
1161
+ else if (/^(v|ver\.?|version[ .]?)?\d+(\.\d+)+$/i.test(l)) hits++;
1162
+ else if (/^\d{4}$/.test(l)) hits++;
1163
+ }
1164
+ return hits / labels.length >= 0.6;
1165
+ }
1166
+ function inferChartType(spec) {
1167
+ if (spec.points && spec.points.length > 0) return "scatter";
1168
+ if (spec.dates && spec.dates.length > 0) return "calendar";
1169
+ if (spec.segments && spec.segments.length > 0) return "donut";
1170
+ if (spec.axes && spec.axes.length >= 3) return "radar";
1171
+ const labels = spec.labels ?? [];
1172
+ const multiSeries = (spec.series?.length ?? 0) > 1;
1173
+ if (labels.length > 0) {
1174
+ if (looksTemporal(labels)) return "line";
1175
+ if (multiSeries) return "vbar";
1176
+ return labels.length > 8 ? "hbar" : "vbar";
1177
+ }
1178
+ if (multiSeries) return "line";
1179
+ if (spec.values && spec.values.length > 1) return "line";
1180
+ if (typeof spec.max === "number" && spec.values && spec.values.length === 1) return "gauge";
1181
+ return "vbar";
1182
+ }
1183
+ function dispatchChartEvent(root, type, mark, chart, phase, pointer) {
1184
+ const read = (name) => mark.getAttribute(name) ?? void 0;
1185
+ const numOr = (v) => v == null ? void 0 : Number(v);
1186
+ const detail = {
1187
+ type,
1188
+ phase,
1189
+ chart,
1190
+ index: numOr(read("data-chart-index")),
1191
+ seriesIndex: numOr(read("data-chart-series")),
1192
+ seriesName: read("data-chart-series-name"),
1193
+ label: read("data-chart-label"),
1194
+ value: numOr(read("data-chart-value")),
1195
+ target: mark,
1196
+ pointerX: pointer?.x,
1197
+ pointerY: pointer?.y
1198
+ };
1199
+ root.dispatchEvent(new CustomEvent(`icen:chart-${type}`, { detail, bubbles: true }));
1200
+ }
1201
+ var tooltipEl = null;
1202
+ function chartTooltip() {
1203
+ if (!tooltipEl) {
1204
+ tooltipEl = document.createElement("div");
1205
+ tooltipEl.className = "chart-tooltip";
1206
+ tooltipEl.setAttribute("role", "tooltip");
1207
+ tooltipEl.hidden = true;
1208
+ document.body.appendChild(tooltipEl);
1209
+ }
1210
+ return tooltipEl;
1211
+ }
1212
+ function tooltipText(mark) {
1213
+ const label = mark.getAttribute("data-chart-label") ?? "";
1214
+ const series = mark.getAttribute("data-chart-series-name");
1215
+ const value = mark.getAttribute("data-chart-value");
1216
+ const head = series ? label ? `${series} \xB7 ${label}` : series : label;
1217
+ return value != null ? `${head}\uFF1A${value}` : head;
1218
+ }
1219
+ function moveTooltip(x, y) {
1220
+ const tip = chartTooltip();
1221
+ const pad = 12;
1222
+ const w = tip.offsetWidth || 120;
1223
+ const h = tip.offsetHeight || 28;
1224
+ let left = x + pad;
1225
+ let top = y - h - pad;
1226
+ if (left + w > window.innerWidth - 8) left = x - w - pad;
1227
+ if (top < 8) top = y + pad;
1228
+ tip.style.left = `${Math.round(left)}px`;
1229
+ tip.style.top = `${Math.round(top)}px`;
1230
+ }
1231
+ function hasLegendOf(spec) {
1232
+ switch (spec.type) {
1233
+ case "donut":
1234
+ case "stack":
1235
+ return (spec.segments?.length ?? 0) > 0;
1236
+ case "heatmap":
1237
+ case "calendar":
1238
+ return true;
1239
+ case "line":
1240
+ case "vbar":
1241
+ case "radar":
1242
+ return (spec.series?.length ?? 0) > 1;
1243
+ default:
1244
+ return false;
1245
+ }
1246
+ }
1247
+ function renderChart(el, raw) {
1248
+ const noop = () => void 0;
1249
+ if (typeof document === "undefined") {
1250
+ return { el, spec: {}, update: noop, on: () => noop, destroy: noop };
1251
+ }
1252
+ const root = el;
1253
+ const attached = root;
1254
+ const wasBound = attached.__icenChartBound === true;
1255
+ attached.__icenChartBound = true;
1256
+ let current = normalizeChartSpec(raw);
1257
+ const typeOf = () => current.type ?? "vbar";
1258
+ const legendState = { hidden: null };
1259
+ const render = () => {
1260
+ if (!current.type) current = { ...current, type: inferChartType(current) };
1261
+ const spec = current;
1262
+ const formatValue = chartFormatValue(spec.format);
1263
+ if (spec.legend !== void 0) legendState.hidden = spec.legend === false;
1264
+ withChrome(root, spec, hasLegendOf(spec), (body) => {
1265
+ switch (spec.type) {
1266
+ case "line":
1267
+ renderLine(body, { labels: spec.labels ?? [], values: spec.values ?? [], series: spec.series, tone: spec.tone, fill: spec.fill, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1268
+ break;
1269
+ case "area":
1270
+ renderLine(body, { labels: spec.labels ?? [], values: spec.values ?? [], series: spec.series, tone: spec.tone, fill: true, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1271
+ break;
1272
+ case "vbar":
1273
+ renderVBar(body, { labels: spec.labels ?? [], values: spec.values ?? [], series: spec.series, stacked: spec.stacked, tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1274
+ break;
1275
+ case "hbar":
1276
+ renderHBar(body, { labels: spec.labels ?? [], values: spec.values ?? [], tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1277
+ break;
1278
+ case "stack":
1279
+ renderStack(body, { segments: spec.segments ?? [], formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1280
+ break;
1281
+ case "donut":
1282
+ renderDonut(body, { segments: spec.segments ?? [], formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1283
+ break;
1284
+ case "radar": {
1285
+ const series = spec.series ? spec.series.map((s) => ({ name: s.name, values: s.values, tone: typeof s.tone === "string" ? s.tone : void 0 })) : spec.values ? [{ name: "", values: spec.values }] : [];
1286
+ renderRadar(body, { axes: spec.axes ?? spec.labels ?? [], series, max: spec.max, levels: spec.levels, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1287
+ break;
1288
+ }
1289
+ 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 });
1291
+ break;
1292
+ 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 });
1294
+ break;
1295
+ case "sparkline":
1296
+ renderSparkline(body, { labels: spec.labels ?? [], values: spec.values ?? [], tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1297
+ break;
1298
+ case "gauge":
1299
+ renderGauge(body, { value: spec.values?.[0] ?? 0, max: spec.max, tone: spec.tone, label: spec.labels?.[0], formatValue });
1300
+ break;
1301
+ case "scatter":
1302
+ renderScatter(body, { points: spec.points ?? [], tone: spec.tone, formatValue, emptyLabel: spec.emptyLabel, title: spec.title, legend: spec.legend });
1303
+ break;
1304
+ default:
1305
+ renderEmpty(body, spec.emptyLabel);
1306
+ }
1307
+ });
1308
+ };
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
+ }
1366
+ render();
1367
+ return {
1368
+ el: root,
1369
+ spec: current,
1370
+ update(next) {
1371
+ current = normalizeChartSpec(next);
1372
+ render();
1373
+ },
1374
+ on(name, listener) {
1375
+ const handler = (event) => {
1376
+ listener(event.detail, event);
1377
+ };
1378
+ root.addEventListener(`icen:chart-${name}`, handler);
1379
+ return () => root.removeEventListener(`icen:chart-${name}`, handler);
1380
+ },
1381
+ destroy() {
1382
+ root.textContent = "";
1383
+ attached.__icenChartBound = false;
1384
+ chartTooltip().hidden = true;
1385
+ }
1386
+ };
1387
+ }
1388
+
1389
+ // src/behaviors/ai-tools.ts
1390
+ var toolRegistry = /* @__PURE__ */ new Map();
1391
+ function registerAiTool(def) {
1392
+ if (def && typeof def.name === "string" && def.name && typeof def.run === "function") {
1393
+ toolRegistry.set(def.name, def);
1394
+ }
1395
+ }
1396
+ function unregisterAiTool(name) {
1397
+ toolRegistry.delete(name);
1398
+ }
1399
+ function getAiTool(name) {
1400
+ return toolRegistry.get(name);
1401
+ }
1402
+ function listAiTools() {
1403
+ return Array.from(toolRegistry.values());
1404
+ }
1405
+ function toolAllowed(name, allow) {
1406
+ if (!allow || allow.length === 0) return true;
1407
+ return allow.some((pat) => pat.endsWith("*") ? name.startsWith(pat.slice(0, -1)) : pat === name);
1408
+ }
1409
+ var CHART_SPEC_SCHEMA = {
1410
+ type: "object",
1411
+ properties: {
1412
+ type: {
1413
+ type: "string",
1414
+ description: "\u56FE\u8868\u7C7B\u578B\uFF0C\u7F3A\u7701\u81EA\u52A8\u63A8\u65AD",
1415
+ enum: ["line", "area", "vbar", "hbar", "stack", "donut", "radar", "heatmap", "calendar", "sparkline", "gauge", "scatter"]
1416
+ },
1417
+ title: { type: "string", description: "\u56FE\u8868\u6807\u9898\uFF08\u6E32\u67D3\u5728\u5934\u90E8\u884C\u5DE6\u4FA7\uFF09" },
1418
+ labels: { type: "array", items: { type: "string" }, description: "\u7C7B\u76EE\u8F74\uFF08line/vbar/hbar\uFF09" },
1419
+ values: { type: "array", items: { type: "number" }, description: "\u5355\u7CFB\u5217\u6570\u503C\uFF08\u4E0E labels \u914D\u5BF9\uFF09" },
1420
+ series: {
1421
+ type: "array",
1422
+ description: "\u591A\u7CFB\u5217\uFF08line/vbar/radar\uFF09",
1423
+ items: {
1424
+ type: "object",
1425
+ properties: {
1426
+ name: { type: "string" },
1427
+ values: { type: "array", items: { type: "number" } }
1428
+ },
1429
+ required: ["name", "values"]
1430
+ }
1431
+ },
1432
+ segments: {
1433
+ type: "array",
1434
+ description: "\u5360\u6BD4\u578B\uFF08donut/stack\uFF09\uFF1A[{label,value}]",
1435
+ items: {
1436
+ type: "object",
1437
+ properties: { label: { type: "string" }, value: { type: "number" } },
1438
+ required: ["label", "value"]
1439
+ }
1440
+ },
1441
+ axes: { type: "array", items: { type: "string" }, description: "\u96F7\u8FBE\u8F74\u540D" },
1442
+ points: {
1443
+ type: "array",
1444
+ description: "\u6563\u70B9/\u6C14\u6CE1\uFF1A[{x,y,size?,label?}]",
1445
+ items: {
1446
+ type: "object",
1447
+ properties: { x: { type: "number" }, y: { type: "number" }, size: { type: "number" }, label: { type: "string" } }
1448
+ }
1449
+ },
1450
+ dates: { type: "array", items: { type: "string" }, description: "\u8D21\u732E\u65E5\u5386 ISO \u65E5\u671F\uFF08\u4E0E values \u914D\u5BF9\uFF09" },
1451
+ data: {
1452
+ type: "array",
1453
+ description: "\u4EFB\u610F\u7EF4\u5EA6\u539F\u59CB\u8BB0\u5F55 + dims \u5B57\u6BB5\u6620\u5C04\uFF08\u81EA\u52A8\u900F\u89C6\u6210 \u7C7B\u76EE \xD7 \u7CFB\u5217\uFF09",
1454
+ items: { type: "object" }
1455
+ },
1456
+ dims: {
1457
+ type: "object",
1458
+ description: "data[] \u7684\u5B57\u6BB5\u6620\u5C04\uFF1A{label?, series?, value?, x?, y?}",
1459
+ properties: {
1460
+ label: { type: "string" },
1461
+ series: { type: "string" },
1462
+ value: { type: "string" },
1463
+ x: { type: "string" },
1464
+ y: { type: "string" }
1465
+ }
1466
+ },
1467
+ tone: { type: "string", enum: ["accent", "success", "warning", "error", "muted"] },
1468
+ stacked: { type: "boolean", description: "vbar \u591A\u7CFB\u5217\u5806\u53E0\uFF08\u9ED8\u8BA4\u5206\u7EC4\uFF09" },
1469
+ fill: { type: "boolean", description: "line \u591A\u7CFB\u5217\u586B\u5145\u9762\u79EF" },
1470
+ format: {
1471
+ type: "object",
1472
+ description: "\u6570\u503C\u683C\u5F0F\u5316\uFF1A{notation?: compact|percent|ms, unit?}",
1473
+ properties: {
1474
+ notation: { type: "string", enum: ["compact", "percent", "ms", "raw"] },
1475
+ unit: { type: "string" }
1476
+ }
1477
+ },
1478
+ legend: { type: "boolean", description: "false = \u521D\u59CB\u9690\u85CF\u56FE\u4F8B" },
1479
+ tooltip: { type: "boolean", description: "false = \u5173\u95ED\u5185\u7F6E\u63D0\u793A" }
1480
+ }
1481
+ };
1482
+ registerAiTool({
1483
+ name: "render_chart",
1484
+ description: "\u5728\u7528\u6237\u754C\u9762\u6E32\u67D3\u4E00\u5F20\u56FE\u8868\u53EF\u89C6\u5316\u3002\u8F93\u5165\u4E00\u4EFD\u56FE\u8868\u89C4\u683C\uFF08\u7EAF JSON\uFF09\uFF1Alabels+values\uFF08\u7C7B\u76EE\u6570\u503C\uFF09\u3001series\uFF08\u591A\u7CFB\u5217\uFF09\u3001segments\uFF08\u5360\u6BD4\uFF09\u3001points\uFF08\u6563\u70B9\u6C14\u6CE1\uFF09\u3001dates\uFF08\u8D21\u732E\u65E5\u5386\uFF09\u4EFB\u7ED9\u5176\u4E00\uFF1Btype \u7F3A\u7701\u65F6\u6309\u6570\u636E\u5F62\u6001\u81EA\u52A8\u9009\u578B\uFF08\u65F6\u95F4\u5E8F\u5217\u2192\u6298\u7EBF\u3001\u5360\u6BD4\u2192\u73AF\u5F62\u3001\u65E5\u671F\u2192\u8D21\u732E\u65E5\u5386\u3001\u5750\u6807\u70B9\u2192\u6563\u70B9\uFF09\u3002\u56FE\u8868\u5E26\u4EA4\u4E92\uFF08\u60AC\u505C\u63D0\u793A/\u70B9\u51FB/\u53F3\u952E\uFF09\u4E0E\u53EF\u9690\u85CF\u56FE\u4F8B\u3002",
1485
+ inputSchema: CHART_SPEC_SCHEMA,
1486
+ run: (input, mount) => {
1487
+ const handle = renderChart(mount, input);
1488
+ return { mounted: true, chart: handle.spec.type ?? "auto" };
1489
+ }
1490
+ });
1491
+ function isBrowserTools() {
1492
+ return typeof document !== "undefined" && typeof window !== "undefined";
1493
+ }
1494
+ function emitArea(area, name, detail) {
1495
+ try {
1496
+ area.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
1497
+ } catch {
1498
+ }
1499
+ }
1500
+ function createAiToolArea(el, opts = {}) {
1501
+ const noop = () => void 0;
1502
+ if (!isBrowserTools()) {
1503
+ return { el, get count() {
1504
+ return 0;
1505
+ }, call: () => null, can: () => false, setTools: noop, setMax: noop, clear: noop, onChange: () => noop };
1506
+ }
1507
+ const area = el;
1508
+ area.classList.add("ai-tools-mount");
1509
+ let allow = opts.tools;
1510
+ let max = opts.max;
1511
+ let seq = 0;
1512
+ const records = [];
1513
+ const listeners = [];
1514
+ const notify = () => {
1515
+ for (const fn of listeners) fn(records.slice());
1516
+ };
1517
+ const evictOldest = () => {
1518
+ const oldest = records.find((r) => r.el);
1519
+ if (!oldest || !oldest.el) return;
1520
+ oldest.el.remove();
1521
+ const idx = records.indexOf(oldest);
1522
+ if (idx >= 0) {
1523
+ records.splice(idx, 1);
1524
+ emitArea(area, "icen:ai-tool-evict", { name: oldest.def.name, el: oldest.el });
1525
+ }
1526
+ };
1527
+ const areaApi = {
1528
+ el: area,
1529
+ get count() {
1530
+ return records.filter((r) => r.el).length;
1531
+ },
1532
+ can(name) {
1533
+ return toolAllowed(name, allow) && toolRegistry.has(name);
1534
+ },
1535
+ setTools(tools) {
1536
+ allow = tools;
1537
+ },
1538
+ setMax(next) {
1539
+ max = next;
1540
+ while (max != null && records.filter((r) => r.el).length > max) evictOldest();
1541
+ },
1542
+ clear() {
1543
+ for (const r of records) r.el?.remove();
1544
+ records.length = 0;
1545
+ notify();
1546
+ },
1547
+ onChange(fn) {
1548
+ listeners.push(fn);
1549
+ return () => {
1550
+ const i = listeners.indexOf(fn);
1551
+ if (i >= 0) listeners.splice(i, 1);
1552
+ };
1553
+ },
1554
+ call(name, input) {
1555
+ const def = toolRegistry.get(name);
1556
+ if (!def || !toolAllowed(name, allow)) return null;
1557
+ void seq;
1558
+ const item = document.createElement("div");
1559
+ item.className = "ai-tools-item";
1560
+ const head = document.createElement("button");
1561
+ head.type = "button";
1562
+ head.className = "ai-tools-item-head";
1563
+ head.setAttribute("aria-expanded", "true");
1564
+ const chevron = document.createElement("span");
1565
+ chevron.className = "ai-tools-item-chevron";
1566
+ chevron.setAttribute("aria-hidden", "true");
1567
+ chevron.textContent = "\u25BE";
1568
+ const label = document.createElement("span");
1569
+ label.className = "ai-tools-item-name";
1570
+ label.textContent = def.name;
1571
+ const meta = document.createElement("span");
1572
+ meta.className = "ai-tools-item-meta";
1573
+ meta.textContent = (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
1574
+ const remove = document.createElement("span");
1575
+ remove.className = "ai-tools-item-remove";
1576
+ remove.setAttribute("role", "button");
1577
+ remove.setAttribute("aria-label", `\u79FB\u9664 ${def.name} \u6302\u8F7D`);
1578
+ remove.textContent = "\xD7";
1579
+ const mount = document.createElement("div");
1580
+ mount.className = "ai-tools-item-body";
1581
+ mount.style.minHeight = `${opts.itemMinHeight ?? 240}px`;
1582
+ head.append(chevron, label, meta, remove);
1583
+ item.append(head, mount);
1584
+ area.appendChild(item);
1585
+ head.addEventListener("click", (e) => {
1586
+ if (e.target instanceof Element && e.target.closest(".ai-tools-item-remove")) return;
1587
+ const open = head.getAttribute("aria-expanded") === "true";
1588
+ head.setAttribute("aria-expanded", String(!open));
1589
+ mount.hidden = open;
1590
+ item.classList.toggle("is-collapsed", open);
1591
+ });
1592
+ const record = { def, el: item, ts: Date.now(), input, result: void 0 };
1593
+ remove.addEventListener("click", (e) => {
1594
+ e.stopPropagation();
1595
+ item.remove();
1596
+ const i = records.indexOf(record);
1597
+ if (i >= 0) records.splice(i, 1);
1598
+ notify();
1599
+ });
1600
+ emitArea(area, "icen:ai-tool-call", { name, input, el: item });
1601
+ let ok = true;
1602
+ let error;
1603
+ try {
1604
+ record.result = def.run(input, mount);
1605
+ record.handle = record.result;
1606
+ } catch (err) {
1607
+ ok = false;
1608
+ error = err instanceof Error ? err.message : String(err);
1609
+ mount.textContent = `\u5DE5\u5177\u6267\u884C\u5931\u8D25\uFF1A${error}`;
1610
+ }
1611
+ records.push(record);
1612
+ emitArea(area, "icen:ai-tool-result", { name, ok, el: item, error, count: records.length });
1613
+ while (max != null && records.filter((r) => r.el).length > max) evictOldest();
1614
+ notify();
1615
+ return record;
1616
+ }
1617
+ };
1618
+ return areaApi;
1619
+ }
1620
+ function aiToolsToOpenAI(names) {
1621
+ return listAiTools().filter((d) => toolAllowed(d.name, names)).map((d) => ({
1622
+ type: "function",
1623
+ function: { name: d.name, description: d.description, parameters: d.inputSchema }
1624
+ }));
1625
+ }
1626
+ function aiToolsToMcp(names) {
1627
+ return listAiTools().filter((d) => toolAllowed(d.name, names)).map((d) => ({ name: d.name, description: d.description, inputSchema: d.inputSchema }));
1628
+ }
1629
+ function aiToolsManifest(names) {
1630
+ const tools = listAiTools().filter((d) => toolAllowed(d.name, names));
1631
+ if (tools.length === 0) return "\uFF08\u65E0\u5DF2\u6CE8\u518C\u7684 UI \u5DE5\u5177\uFF09";
1632
+ return [
1633
+ "\u53EF\u7528 UI \u5DE5\u5177\uFF08\u8C03\u7528\u540E\u5728\u7528\u6237\u754C\u9762\u6302\u8F7D\u53EF\u89C6\u5316\u7ED3\u679C\uFF09\uFF1A",
1634
+ ...tools.map((d) => `- ${d.name}\uFF1A${d.description}`)
1635
+ ].join("\n");
1636
+ }
1637
+ function parseAiToolArgs(raw) {
1638
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
1639
+ if (typeof raw === "string") {
1640
+ try {
1641
+ const parsed = JSON.parse(raw);
1642
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
1643
+ } catch {
1644
+ }
1645
+ }
1646
+ return {};
1647
+ }
1648
+ export {
1649
+ aiToolsManifest,
1650
+ aiToolsToMcp,
1651
+ aiToolsToOpenAI,
1652
+ createAiToolArea,
1653
+ getAiTool,
1654
+ listAiTools,
1655
+ parseAiToolArgs,
1656
+ registerAiTool,
1657
+ unregisterAiTool
1658
+ };