@icen.ai/ui 0.5.0 → 0.8.1

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