@aparte/plugin-echarts 0.0.0-stage → 0.29.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.
package/dist/core.js ADDED
@@ -0,0 +1,974 @@
1
+ import { aparteGlobalConfig, escapeAttr } from "@aparte/core";
2
+ const MAX_DEPTH = 64;
3
+ const MAX_ROWS = 1e3;
4
+ const MAX_COLUMNS = 50;
5
+ const MAX_CELLS = 1e4;
6
+ const PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
7
+ const FONT_KEYS = /* @__PURE__ */ new Set(["font", "fontFamily", "fontStyle", "fontWeight", "fontSize"]);
8
+ const SAFE_FONT = /^[\p{L}\p{N}\s,'"._/%-]*$/u;
9
+ const IMAGE_SCHEME = /^\s*image:\/\//i;
10
+ const CSS_URL = /(^|[^\w-])(url|src)\s*\(/i;
11
+ const CSS_ESCAPE = /\\(?:([0-9a-f]{1,6})[ \t\n\r\f]?|([^\n\r\f]))/gi;
12
+ const SAFE_COLOUR = /^\s*(?:[a-z]+|#[0-9a-f]{3,8}|(?:rgba?|hsla?)\(\s*[\d.,%\s/+-]*(?:deg)?[\d.,%\s/+-]*\))\s*$/i;
13
+ const isColourKey = (key) => key === "fill" || key === "stroke" || /(^c|C)olor/.test(key);
14
+ function cssUnescape(s) {
15
+ return s.replace(CSS_ESCAPE, (_all, hex, other) => {
16
+ if (other !== void 0) return other;
17
+ const code = parseInt(hex, 16);
18
+ return code === 0 || code > 1114111 || code >= 55296 && code <= 57343 ? "�" : String.fromCodePoint(code);
19
+ });
20
+ }
21
+ const COMPILED_KEYS = /* @__PURE__ */ new Set(["reg"]);
22
+ const HTML_TOOLTIP_KEYS = ["extraCssText", "className", "appendTo", "appendToBody"];
23
+ const HTML_FEATURES = ["saveAsImage", "dataView"];
24
+ const isObject$1 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
25
+ function sanitizeEChartsOption(option) {
26
+ if (!isObject$1(option)) return null;
27
+ const clean = walk(option, "", 0);
28
+ return isObject$1(clean) ? clean : null;
29
+ }
30
+ function walk(value, key, depth) {
31
+ if (depth > MAX_DEPTH) return void 0;
32
+ if (typeof value === "string") {
33
+ if (IMAGE_SCHEME.test(value) || CSS_URL.test(value) || CSS_URL.test(cssUnescape(value))) return void 0;
34
+ if (value.includes("\\") && value.includes("(")) return void 0;
35
+ if (isColourKey(key) && !SAFE_COLOUR.test(value)) return void 0;
36
+ if (FONT_KEYS.has(key) && !SAFE_FONT.test(value)) return void 0;
37
+ if (key === "link" || key === "sublink") return void 0;
38
+ return value;
39
+ }
40
+ if (Array.isArray(value)) {
41
+ const out2 = [];
42
+ for (const item of value) {
43
+ const clean = walk(item, key, depth + 1);
44
+ if (clean !== void 0) out2.push(clean);
45
+ else if (!isObject$1(item)) out2.push(null);
46
+ }
47
+ return out2;
48
+ }
49
+ if (!isObject$1(value)) return value;
50
+ if ("image" in value || "svgElement" in value || value["type"] === "image") return void 0;
51
+ const out = {};
52
+ for (const [k, v] of Object.entries(value)) {
53
+ if (PROTO_KEYS.has(k) || COMPILED_KEYS.has(k)) continue;
54
+ const clean = walk(v, k, depth + 1);
55
+ if (clean !== void 0) out[k] = clean;
56
+ }
57
+ if (key === "tooltip") {
58
+ out["renderMode"] = "richText";
59
+ for (const k of HTML_TOOLTIP_KEYS) delete out[k];
60
+ }
61
+ if (key === "feature") for (const k of HTML_FEATURES) delete out[k];
62
+ return out;
63
+ }
64
+ function parseOption(source, open = false) {
65
+ try {
66
+ return JSON.parse(source);
67
+ } catch {
68
+ }
69
+ return open ? parsePrefix(source) : void 0;
70
+ }
71
+ function parsePrefix(source) {
72
+ const stack = [];
73
+ const cuts = [];
74
+ const cut = (at) => {
75
+ cuts.push({ at, closers: stack.slice().reverse().join("") });
76
+ if (cuts.length > 8) cuts.shift();
77
+ };
78
+ let inString = false;
79
+ let escaped = false;
80
+ for (let i = 0; i < source.length; i++) {
81
+ const ch = source[i];
82
+ if (inString) {
83
+ if (escaped) escaped = false;
84
+ else if (ch === "\\") escaped = true;
85
+ else if (ch === '"') inString = false;
86
+ continue;
87
+ }
88
+ if (ch === '"') inString = true;
89
+ else if (ch === "{" || ch === "[") {
90
+ stack.push(ch === "{" ? "}" : "]");
91
+ cut(i + 1);
92
+ } else if (ch === "}" || ch === "]") {
93
+ stack.pop();
94
+ cut(i + 1);
95
+ } else if (ch === ",") cut(i);
96
+ }
97
+ for (let i = cuts.length - 1; i >= 0; i--) {
98
+ const { at, closers } = cuts[i];
99
+ try {
100
+ return JSON.parse(source.slice(0, at) + closers);
101
+ } catch {
102
+ }
103
+ }
104
+ return void 0;
105
+ }
106
+ const list = (v) => Array.isArray(v) ? v : v === void 0 ? [] : [v];
107
+ function drawable(option) {
108
+ const has = (o) => {
109
+ if (!isObject$1(o)) return false;
110
+ const series = o["series"];
111
+ if (Array.isArray(series) ? series.some(isObject$1) : isObject$1(series)) return true;
112
+ return o["dataset"] !== void 0;
113
+ };
114
+ return has(option) || has(option["baseOption"]) || list(option["options"]).some(has);
115
+ }
116
+ function titleOf(option) {
117
+ const title = list(option["title"])[0];
118
+ const text = isObject$1(title) ? title["text"] : void 0;
119
+ return typeof text === "string" && text.trim() ? text.trim() : void 0;
120
+ }
121
+ function cell(v) {
122
+ if (v === null || v === void 0) return "";
123
+ if (typeof v === "string") return v;
124
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
125
+ if (Array.isArray(v)) return v.map(cell).join(", ");
126
+ if (isObject$1(v)) return cell(v["value"]);
127
+ return "";
128
+ }
129
+ function grid(width, height, headAt, cellAt) {
130
+ const columns = Math.min(width, MAX_COLUMNS);
131
+ if (columns <= 0) return null;
132
+ const rows = Math.max(0, Math.min(height, MAX_ROWS, Math.floor(MAX_CELLS / columns) - 1));
133
+ const table = {
134
+ head: Array.from({ length: columns }, (_, c) => headAt(c)),
135
+ rows: Array.from({ length: rows }, (_, r) => Array.from({ length: columns }, (_2, c) => cellAt(r, c)))
136
+ };
137
+ if (columns < width || rows < height) table.cut = true;
138
+ return table;
139
+ }
140
+ function headerRows(dataset, first) {
141
+ const option = dataset["sourceHeader"];
142
+ if (typeof option === "number") return Number.isFinite(option) && option > 0 ? Math.floor(option) : 0;
143
+ if (typeof option === "boolean") return option ? 1 : 0;
144
+ let rows;
145
+ for (const v of first.slice(0, 10)) {
146
+ if (v === null || v === void 0 || v === "-") continue;
147
+ if (typeof v === "string") rows ??= 1;
148
+ else rows = 0;
149
+ }
150
+ return rows ?? 0;
151
+ }
152
+ function tableOf(option) {
153
+ const dataset = list(option["dataset"])[0];
154
+ const source = isObject$1(dataset) ? dataset["source"] : void 0;
155
+ if (isObject$1(dataset) && Array.isArray(source) && source.length > 0) {
156
+ const named2 = Array.isArray(dataset["dimensions"]) ? dataset["dimensions"].map((d) => isObject$1(d) ? cell(d["name"]) : cell(d)) : void 0;
157
+ if (Array.isArray(source[0])) {
158
+ const skip = headerRows(dataset, source[0]);
159
+ const rows = source.slice(skip);
160
+ const head = named2 ?? (skip > 0 ? source[0].map(cell) : []);
161
+ const width = Math.max(head.length, ...rows.slice(0, MAX_ROWS).map((r) => list(r).length));
162
+ return grid(width, rows.length, (c) => head[c] ?? "", (r, c) => cell(list(rows[r])[c]));
163
+ }
164
+ if (isObject$1(source[0])) {
165
+ const dims = named2 ?? Object.keys(source[0]);
166
+ return grid(dims.length, source.length, (c) => dims[c], (r, c) => {
167
+ const row = source[r];
168
+ return cell(isObject$1(row) ? row[dims[c]] : void 0);
169
+ });
170
+ }
171
+ }
172
+ const series = list(option["series"]).map((s, i) => ({ s, i })).filter((e) => isObject$1(e.s) && Array.isArray(e.s["data"]) && e.s["data"].length > 0);
173
+ if (series.length === 0) return null;
174
+ const axis = [...list(option["xAxis"]), ...list(option["yAxis"])].find((a) => isObject$1(a) && Array.isArray(a["data"]));
175
+ const name = ({ s, i }) => cell(s["name"]) || `${i + 1}`;
176
+ const dataOf = (c) => series[c].s["data"];
177
+ if (axis) {
178
+ const categories = axis["data"];
179
+ return grid(
180
+ series.length + 1,
181
+ categories.length,
182
+ (c) => c === 0 ? cell(axis["name"]) : name(series[c - 1]),
183
+ (r, c) => cell(c === 0 ? categories[r] : dataOf(c - 1)[r])
184
+ );
185
+ }
186
+ const named = series.find(({ s }) => s["data"].some((d) => isObject$1(d) && "name" in d));
187
+ if (named) {
188
+ const data = named.s["data"];
189
+ return grid(2, data.length, (c) => c === 0 ? "" : name(named), (r, c) => {
190
+ const d = data[r];
191
+ return c === 0 ? cell(isObject$1(d) ? d["name"] : "") : cell(d);
192
+ });
193
+ }
194
+ return null;
195
+ }
196
+ const ECHARTS_SERIES_HUES = {
197
+ light: ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", "#e87ba4", "#008300", "#6250d6", "#e34948"],
198
+ dark: ["#3987e5", "#d95926", "#199e70", "#c98500", "#d55181", "#008300", "#9085e9", "#e66767"]
199
+ };
200
+ function rgbOf(color) {
201
+ const c = color.trim();
202
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})(?:[0-9a-f]{2})?$/i.exec(c)?.[1];
203
+ if (hex) {
204
+ const full = hex.length === 3 ? hex.split("").map((h) => h + h).join("") : hex;
205
+ return [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16));
206
+ }
207
+ const fn = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(c);
208
+ return fn ? [Number(fn[1]), Number(fn[2]), Number(fn[3])] : null;
209
+ }
210
+ function oklab([r, g, b]) {
211
+ const lin = (v) => {
212
+ const s2 = v / 255;
213
+ return s2 <= 0.04045 ? s2 / 12.92 : ((s2 + 0.055) / 1.055) ** 2.4;
214
+ };
215
+ const [lr, lg, lb] = [lin(r), lin(g), lin(b)];
216
+ const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
217
+ const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
218
+ const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
219
+ return [
220
+ 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
221
+ 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
222
+ 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
223
+ ];
224
+ }
225
+ function isDark(color) {
226
+ const rgb = rgbOf(color);
227
+ return rgb ? oklab(rgb)[0] < 0.6 : false;
228
+ }
229
+ function echartsPalette(primary, dark = false) {
230
+ const ring = [...dark ? ECHARTS_SERIES_HUES.dark : ECHARTS_SERIES_HUES.light];
231
+ const rgb = rgbOf(primary);
232
+ if (!rgb) return [primary, ...ring];
233
+ const p = oklab(rgb);
234
+ const grey = Math.hypot(p[1], p[2]) < 0.04;
235
+ const hueOf = (c) => Math.atan2(c[2], c[1]);
236
+ const distance = (q) => {
237
+ if (grey) return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);
238
+ const d = Math.abs(hueOf(p) - hueOf(q));
239
+ return Math.min(d, 2 * Math.PI - d);
240
+ };
241
+ let nearest = 0;
242
+ let best = Infinity;
243
+ ring.forEach((hue, i) => {
244
+ const d = distance(oklab(rgbOf(hue)));
245
+ if (d < best) {
246
+ best = d;
247
+ nearest = i;
248
+ }
249
+ });
250
+ return [primary, ...ring.slice(nearest + 1), ...ring.slice(0, nearest)];
251
+ }
252
+ function echartsTheme(tokens) {
253
+ const { text, muted, border, surface, background } = tokens;
254
+ const dark = tokens.dark ?? isDark(background);
255
+ const font = tokens.fontFamily && SAFE_FONT.test(tokens.fontFamily) ? { fontFamily: tokens.fontFamily } : {};
256
+ const axis = (splitLine) => ({
257
+ axisLine: { lineStyle: { color: border } },
258
+ axisTick: { lineStyle: { color: border } },
259
+ axisLabel: { color: muted },
260
+ nameTextStyle: { color: muted },
261
+ splitLine: { show: splitLine, lineStyle: { color: border } },
262
+ minorSplitLine: { lineStyle: { color: border } },
263
+ splitArea: { areaStyle: { color: ["transparent", surface] } }
264
+ });
265
+ return {
266
+ darkMode: dark,
267
+ color: echartsPalette(tokens.primary, dark),
268
+ backgroundColor: "transparent",
269
+ textStyle: { color: text, ...font },
270
+ title: { textStyle: { color: text }, subtextStyle: { color: muted } },
271
+ legend: {
272
+ textStyle: { color: text },
273
+ pageTextStyle: { color: muted },
274
+ pageIconColor: text,
275
+ pageIconInactiveColor: border,
276
+ inactiveColor: border
277
+ },
278
+ tooltip: { backgroundColor: surface, borderColor: border, textStyle: { color: text } },
279
+ axisPointer: {
280
+ lineStyle: { color: muted },
281
+ crossStyle: { color: muted },
282
+ label: { color: surface, backgroundColor: muted }
283
+ },
284
+ // A category axis draws no grid by default, in ECharts as here.
285
+ categoryAxis: axis(false),
286
+ valueAxis: axis(true),
287
+ logAxis: axis(true),
288
+ timeAxis: axis(true),
289
+ radar: {
290
+ axisName: { color: muted },
291
+ axisLine: { lineStyle: { color: border } },
292
+ splitLine: { lineStyle: { color: border } },
293
+ splitArea: { areaStyle: { color: ["transparent", surface] } }
294
+ },
295
+ dataZoom: {
296
+ borderColor: border,
297
+ textStyle: { color: muted },
298
+ handleStyle: { color: surface, borderColor: muted },
299
+ moveHandleStyle: { color: muted },
300
+ dataBackground: { lineStyle: { color: muted }, areaStyle: { color: border } }
301
+ },
302
+ visualMap: { textStyle: { color: text } },
303
+ timeline: {
304
+ lineStyle: { color: border },
305
+ label: { color: muted },
306
+ controlStyle: { color: muted, borderColor: muted }
307
+ },
308
+ toolbox: { iconStyle: { borderColor: muted } },
309
+ calendar: {
310
+ itemStyle: { color: "transparent", borderColor: border },
311
+ splitLine: { lineStyle: { color: muted } },
312
+ dayLabel: { color: muted },
313
+ monthLabel: { color: muted },
314
+ yearLabel: { color: text }
315
+ },
316
+ gauge: { title: { color: muted }, detail: { color: text }, axisLabel: { color: muted } },
317
+ // The gap between two slices is the ground showing through.
318
+ pie: { itemStyle: { borderColor: background } },
319
+ funnel: { itemStyle: { borderColor: background } },
320
+ sunburst: { itemStyle: { borderColor: background } },
321
+ map: { label: { color: text }, itemStyle: { areaColor: surface, borderColor: border } },
322
+ geo: { label: { color: text }, itemStyle: { areaColor: surface, borderColor: border } }
323
+ };
324
+ }
325
+ const TOKENS = {
326
+ text: ["--aparte-text", "#1a1a1a"],
327
+ muted: ["--aparte-text-muted", "#666666"],
328
+ border: ["--aparte-border", "#d0d0d0"],
329
+ surface: ["--aparte-surface-1", "#fafafa"],
330
+ background: ["--aparte-bg", "#ffffff"],
331
+ primary: ["--aparte-primary", "#3b5bdb"]
332
+ };
333
+ function readTokens(el) {
334
+ const style = getComputedStyle(el);
335
+ const probe = document.createElement("span");
336
+ probe.style.display = "none";
337
+ el.appendChild(probe);
338
+ const canvas = document.createElement("canvas");
339
+ canvas.width = canvas.height = 1;
340
+ const paint = canvas.getContext?.("2d", { willReadFrequently: true }) ?? null;
341
+ const read = (token, fallback) => {
342
+ probe.style.color = `var(${token}, ${fallback})`;
343
+ const computed = getComputedStyle(probe).color || fallback;
344
+ if (!paint) return computed;
345
+ paint.clearRect(0, 0, 1, 1);
346
+ paint.fillStyle = "#000";
347
+ paint.fillStyle = computed;
348
+ paint.fillRect(0, 0, 1, 1);
349
+ const [r, g, b, a] = paint.getImageData(0, 0, 1, 1).data;
350
+ return a === 255 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${((a ?? 0) / 255).toFixed(3)})`;
351
+ };
352
+ try {
353
+ const tokens = Object.fromEntries(
354
+ Object.entries(TOKENS).map(([name, [token, fallback]]) => [name, read(token, fallback)])
355
+ );
356
+ const font = style.getPropertyValue("--aparte-font-family").trim() || style.fontFamily;
357
+ if (font) tokens.fontFamily = font;
358
+ return tokens;
359
+ } finally {
360
+ probe.remove();
361
+ }
362
+ }
363
+ const WORDS = {
364
+ echartsChart: {
365
+ en: "Chart",
366
+ fr: "Graphique",
367
+ de: "Diagramm",
368
+ es: "Gráfico",
369
+ it: "Grafico",
370
+ ja: "グラフ",
371
+ ko: "차트",
372
+ pt: "Gráfico",
373
+ ru: "Диаграмма",
374
+ zh: "图表"
375
+ },
376
+ echartsError: {
377
+ en: "This chart could not be drawn.",
378
+ fr: "Ce graphique n’a pas pu être dessiné.",
379
+ de: "Dieses Diagramm konnte nicht gezeichnet werden.",
380
+ es: "No se pudo dibujar este gráfico.",
381
+ it: "Impossibile disegnare questo grafico.",
382
+ ja: "このグラフを描画できませんでした。",
383
+ ko: "이 차트를 그릴 수 없습니다.",
384
+ pt: "Não foi possível desenhar este gráfico.",
385
+ ru: "Не удалось построить эту диаграмму.",
386
+ zh: "无法绘制此图表。"
387
+ },
388
+ echartsLoading: {
389
+ en: "Loading chart",
390
+ fr: "Chargement du graphique",
391
+ de: "Diagramm wird geladen",
392
+ es: "Cargando gráfico",
393
+ it: "Caricamento del grafico",
394
+ ja: "グラフを読み込み中",
395
+ ko: "차트 불러오는 중",
396
+ pt: "Carregando gráfico",
397
+ ru: "Загрузка диаграммы",
398
+ zh: "正在加载图表"
399
+ },
400
+ echartsWaiting: {
401
+ en: "Waiting for your approval",
402
+ fr: "En attente de votre accord",
403
+ de: "Wartet auf Ihre Zustimmung",
404
+ es: "Esperando tu aprobación",
405
+ it: "In attesa della tua approvazione",
406
+ ja: "承認を待っています",
407
+ ko: "승인을 기다리는 중",
408
+ pt: "Aguardando sua aprovação",
409
+ ru: "Ожидает вашего подтверждения",
410
+ zh: "等待您的批准"
411
+ },
412
+ echartsNotRun: {
413
+ en: "No chart: the tool did not run.",
414
+ fr: "Pas de graphique : l’outil n’a pas été exécuté.",
415
+ de: "Kein Diagramm: Das Werkzeug wurde nicht ausgeführt.",
416
+ es: "Sin gráfico: la herramienta no se ejecutó.",
417
+ it: "Nessun grafico: lo strumento non è stato eseguito.",
418
+ ja: "グラフなし:ツールは実行されませんでした。",
419
+ ko: "차트 없음: 도구가 실행되지 않았습니다.",
420
+ pt: "Sem gráfico: a ferramenta não foi executada.",
421
+ ru: "Нет диаграммы: инструмент не был запущен.",
422
+ zh: "无图表:工具未运行。"
423
+ },
424
+ echartsFailed: {
425
+ en: "No chart: the tool failed.",
426
+ fr: "Pas de graphique : l’outil a échoué.",
427
+ de: "Kein Diagramm: Das Werkzeug ist fehlgeschlagen.",
428
+ es: "Sin gráfico: la herramienta falló.",
429
+ it: "Nessun grafico: lo strumento non è riuscito.",
430
+ ja: "グラフなし:ツールが失敗しました。",
431
+ ko: "차트 없음: 도구가 실패했습니다.",
432
+ pt: "Sem gráfico: a ferramenta falhou.",
433
+ ru: "Нет диаграммы: инструмент завершился с ошибкой.",
434
+ zh: "无图表:工具运行失败。"
435
+ },
436
+ echartsTableCut: {
437
+ en: "Only part of the data is listed.",
438
+ fr: "Seule une partie des données est listée.",
439
+ de: "Nur ein Teil der Daten ist aufgeführt.",
440
+ es: "Solo se muestra una parte de los datos.",
441
+ it: "È elencata solo una parte dei dati.",
442
+ ja: "データの一部のみを表示しています。",
443
+ ko: "데이터의 일부만 표시됩니다.",
444
+ pt: "Apenas parte dos dados está listada.",
445
+ ru: "Показана только часть данных.",
446
+ zh: "仅列出部分数据。"
447
+ }
448
+ };
449
+ function languageOf(config) {
450
+ const tag = config.getLocale().tag;
451
+ return typeof tag === "string" && tag ? tag.split("-")[0].toLowerCase() : "en";
452
+ }
453
+ function word(config, key) {
454
+ const fromApp = config.getLocale()[key];
455
+ if (typeof fromApp === "string" && fromApp) return fromApp;
456
+ const table = WORDS[key];
457
+ return table[languageOf(config)] ?? table.en;
458
+ }
459
+ const LANGUAGES = ["echarts", "echart"];
460
+ const TAG = "aparte-echarts";
461
+ const DEFAULT_HEIGHT = "18rem";
462
+ const MAX_WIDTH = "40rem";
463
+ const PROGRESSIVE_MS = 300;
464
+ const LINGER_MS = 1e3;
465
+ const ID_PREFIX = `aparte-echarts-${Math.random().toString(36).slice(2, 8)}`;
466
+ let setups = 0;
467
+ let streamIds = 0;
468
+ const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
469
+ const REGISTRY = Symbol.for("aparte.plugin-echarts.setups");
470
+ function registry() {
471
+ const scope = globalThis;
472
+ return scope[REGISTRY] ??= /* @__PURE__ */ new Map();
473
+ }
474
+ function defineElement() {
475
+ if (typeof customElements === "undefined" || typeof HTMLElement === "undefined" || customElements.get(TAG)) return;
476
+ customElements.define(TAG, class extends HTMLElement {
477
+ connectedCallback() {
478
+ registry().get(this.getAttribute("data-aparte-echarts") ?? "")?.connected(this);
479
+ }
480
+ disconnectedCallback() {
481
+ registry().get(this.getAttribute("data-aparte-echarts") ?? "")?.disconnected(this);
482
+ }
483
+ });
484
+ }
485
+ function setupEChartsWith(echarts, options = {}, config = aparteGlobalConfig) {
486
+ const load = typeof echarts === "function" ? echarts : () => Promise.resolve(echarts);
487
+ defineElement();
488
+ const id = `${ID_PREFIX}-${++setups}`;
489
+ const charts = createCharts(load, options, config);
490
+ registry().set(id, charts);
491
+ const unregister = config.registerCodeLanguageRenderer(createFenceRenderer(id, options, charts));
492
+ const matchers = options.tool === void 0 ? [] : Array.isArray(options.tool) ? options.tool : [options.tool];
493
+ const tool = createToolRenderer(id, options, config, charts);
494
+ for (const match of matchers) config.registerToolRenderer(match, tool);
495
+ return () => {
496
+ for (const match of matchers) config.unregisterToolRenderer(match);
497
+ unregister();
498
+ charts.stop();
499
+ registry().delete(id);
500
+ };
501
+ }
502
+ function cssLength(height) {
503
+ if (typeof height === "number" && height > 0) return `${height}px`;
504
+ if (typeof height === "string" && /^\d+(\.\d+)?(px|rem|em|vh|svh|dvh|lh|%)$/.test(height.trim())) return height.trim();
505
+ return DEFAULT_HEIGHT;
506
+ }
507
+ function prefersReducedMotion() {
508
+ return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
509
+ }
510
+ function skeleton(height) {
511
+ const box = document.createElement("span");
512
+ box.className = "aparte-skeleton aparte-skeleton--rect";
513
+ box.setAttribute("aria-hidden", "true");
514
+ box.style.setProperty("block-size", height);
515
+ box.style.setProperty("max-inline-size", MAX_WIDTH);
516
+ return box;
517
+ }
518
+ function note(config, key, className = "aparte-echarts-note") {
519
+ const p = document.createElement("p");
520
+ p.className = className;
521
+ p.setAttribute("role", "note");
522
+ p.setAttribute("data-aparte-echarts-word", key);
523
+ p.textContent = word(config, key);
524
+ return p;
525
+ }
526
+ function relabelWords(root, config) {
527
+ for (const el of Array.from(root.querySelectorAll("[data-aparte-echarts-word]"))) {
528
+ el.textContent = word(config, el.getAttribute("data-aparte-echarts-word"));
529
+ }
530
+ }
531
+ function finalOption(option, width = 0) {
532
+ const decal = isObject(option["aria"]) && isObject(option["aria"]["decal"]) ? { decal: option["aria"]["decal"] } : {};
533
+ const out = { ...option, aria: { enabled: true, label: { enabled: true }, ...decal } };
534
+ const title = fittedTitles(option, width);
535
+ if (title !== void 0) out["title"] = title;
536
+ return prefersReducedMotion() ? motionless(out) : out;
537
+ }
538
+ function fittedTitles(option, width) {
539
+ const titles = option["title"];
540
+ if (width <= 0 || titles === void 0) return void 0;
541
+ const fit = (t) => {
542
+ if (!isObject(t)) return t;
543
+ const style = isObject(t["textStyle"]) ? t["textStyle"] : {};
544
+ if (style["width"] !== void 0) return t;
545
+ return { ...t, textStyle: { ...style, width: Math.max(60, width - 32), overflow: "truncate" } };
546
+ };
547
+ return Array.isArray(titles) ? titles.map(fit) : fit(titles);
548
+ }
549
+ function motionless(option) {
550
+ const still = (o) => {
551
+ if (!isObject(o)) return o;
552
+ const out2 = { ...o, animation: false };
553
+ const series = (s) => {
554
+ if (!isObject(s)) return s;
555
+ const next = { ...s, animation: false };
556
+ if (s["type"] === "effectScatter") next["showEffectOn"] = "emphasis";
557
+ if (isObject(s["effect"])) next["effect"] = { ...s["effect"], show: false };
558
+ return next;
559
+ };
560
+ const each = (v, f) => Array.isArray(v) ? v.map(f) : f(v);
561
+ if (o["series"] !== void 0) out2["series"] = each(o["series"], series);
562
+ if (o["graphic"] !== void 0) out2["graphic"] = each(o["graphic"], (g) => isObject(g) ? { ...g, animation: false } : g);
563
+ if (o["timeline"] !== void 0) out2["timeline"] = each(o["timeline"], (t) => isObject(t) ? { ...t, autoPlay: false } : t);
564
+ return out2;
565
+ };
566
+ const out = still(option);
567
+ if (isObject(option["baseOption"])) out["baseOption"] = still(option["baseOption"]);
568
+ if (Array.isArray(option["options"])) out["options"] = option["options"].map(still);
569
+ if (Array.isArray(option["media"])) {
570
+ out["media"] = option["media"].map((m) => isObject(m) && isObject(m["option"]) ? { ...m, option: still(m["option"]) } : m);
571
+ }
572
+ return out;
573
+ }
574
+ function tableElement(option, name, config) {
575
+ const data = tableOf(option);
576
+ if (!data) return null;
577
+ const table = document.createElement("table");
578
+ table.className = "aparte-sr-only aparte-echarts-table";
579
+ const caption = table.createCaption();
580
+ caption.textContent = name;
581
+ const headRow = table.createTHead().insertRow();
582
+ for (const text of data.head) {
583
+ const th = document.createElement("th");
584
+ th.scope = "col";
585
+ th.textContent = text;
586
+ headRow.append(th);
587
+ }
588
+ if (data.cut) {
589
+ const cell2 = table.createTFoot().insertRow().insertCell();
590
+ cell2.colSpan = data.head.length;
591
+ cell2.setAttribute("data-aparte-echarts-word", "echartsTableCut");
592
+ cell2.textContent = word(config, "echartsTableCut");
593
+ }
594
+ const body = table.createTBody();
595
+ for (const row of data.rows) {
596
+ const tr = body.insertRow();
597
+ row.forEach((text, i) => {
598
+ const cell2 = document.createElement(i === 0 ? "th" : "td");
599
+ if (i === 0) cell2.scope = "row";
600
+ cell2.textContent = text;
601
+ tr.append(cell2);
602
+ });
603
+ }
604
+ return table;
605
+ }
606
+ function createCharts(load, options, config) {
607
+ const height = cssLength(options.height);
608
+ let library;
609
+ const libraryOf = () => library ??= load().catch((error) => {
610
+ library = void 0;
611
+ throw error;
612
+ });
613
+ const states = /* @__PURE__ */ new WeakMap();
614
+ const all = /* @__PURE__ */ new Set();
615
+ const live = /* @__PURE__ */ new Set();
616
+ const streams = /* @__PURE__ */ new Map();
617
+ const configs = /* @__PURE__ */ new WeakMap();
618
+ const roots = /* @__PURE__ */ new WeakSet();
619
+ let warned = false;
620
+ const warnOnce = (error) => {
621
+ if (warned) return;
622
+ warned = true;
623
+ console.warn("[aparte-echarts] a chart could not be drawn; a note is shown instead.", error);
624
+ };
625
+ const configOf = (el) => configs.get(el) ?? config;
626
+ const nameOf = (c) => titleOf(c.option) ?? word(configOf(c.el), "echartsChart");
627
+ const themeAt = (el) => {
628
+ const tokens = readTokens(el);
629
+ return { theme: echartsTheme(tokens), key: JSON.stringify(tokens) };
630
+ };
631
+ const retheme = (c) => {
632
+ if (!c.instance || !c.el.isConnected) return;
633
+ const { theme, key } = themeAt(c.el);
634
+ if (key === c.colours) return;
635
+ c.colours = key;
636
+ c.instance.setTheme(theme);
637
+ };
638
+ const watch = watchTheme(() => {
639
+ for (const c of live) retheme(c);
640
+ });
641
+ const destroy = (c) => {
642
+ c.dead = true;
643
+ clearTimeout(c.linger);
644
+ c.observer?.disconnect();
645
+ c.instance?.dispose();
646
+ c.instance = null;
647
+ live.delete(c);
648
+ all.delete(c);
649
+ if (states.get(c.el) === c) states.delete(c.el);
650
+ const stream = c.el.getAttribute("data-aparte-echarts-stream");
651
+ if (stream && streams.get(stream) === c.el) streams.delete(stream);
652
+ };
653
+ const fail = (c, error) => {
654
+ warnOnce(error);
655
+ destroy(c);
656
+ c.el.replaceChildren(note(configOf(c.el), "echartsError", "aparte-code-error"));
657
+ };
658
+ const apply = (c, option, fresh) => {
659
+ c.option = option;
660
+ const name = nameOf(c);
661
+ c.el.setAttribute("aria-label", name);
662
+ c.table?.remove();
663
+ c.table = tableElement(option, name, configOf(c.el));
664
+ if (c.table) c.el.append(c.table);
665
+ if (!c.instance) return;
666
+ try {
667
+ c.instance.setOption(finalOption(option, c.box.clientWidth), fresh ? { notMerge: true } : void 0);
668
+ } catch (error) {
669
+ fail(c, error);
670
+ }
671
+ };
672
+ const watchRoot = (el) => {
673
+ const root = el.getRootNode();
674
+ if (typeof ShadowRoot === "undefined" || !(root instanceof ShadowRoot) || roots.has(root)) return;
675
+ roots.add(root);
676
+ watch.add(root);
677
+ };
678
+ const mount = (el, option) => {
679
+ const box = document.createElement("div");
680
+ box.className = "aparte-echarts-chart";
681
+ box.style.setProperty("block-size", height);
682
+ box.style.setProperty("inline-size", "100%");
683
+ const waiting = skeleton("100%");
684
+ box.append(waiting);
685
+ el.replaceChildren(box);
686
+ const c = { el, box, table: null, instance: null, option, colours: "", observer: void 0, linger: void 0, dead: false };
687
+ states.set(el, c);
688
+ all.add(c);
689
+ live.add(c);
690
+ apply(c, option, true);
691
+ libraryOf().then((lib) => {
692
+ if (c.dead) return;
693
+ const { theme, key } = themeAt(c.el);
694
+ c.colours = key;
695
+ waiting.remove();
696
+ c.instance = lib.init(box, theme, {
697
+ renderer: options.renderer ?? "svg",
698
+ // ECharts' own description (aria) in the chat's language, when that language
699
+ // is registered with ECharts; English otherwise.
700
+ locale: languageOf(configOf(c.el)).toUpperCase()
701
+ });
702
+ if (typeof ResizeObserver !== "undefined") {
703
+ c.observer = new ResizeObserver(() => {
704
+ if (!c.instance) return;
705
+ try {
706
+ c.instance.resize();
707
+ const title = fittedTitles(c.option, c.box.clientWidth);
708
+ if (title !== void 0) c.instance.setOption({ title });
709
+ } catch (error) {
710
+ fail(c, error);
711
+ }
712
+ });
713
+ c.observer.observe(box);
714
+ }
715
+ apply(c, c.option, true);
716
+ }).catch((error) => {
717
+ if (!c.dead) fail(c, error);
718
+ });
719
+ };
720
+ return {
721
+ connected(el) {
722
+ el.style.setProperty("display", "block");
723
+ el.style.setProperty("inline-size", "100%");
724
+ el.style.setProperty("max-inline-size", MAX_WIDTH);
725
+ el.setAttribute("role", "figure");
726
+ watchRoot(el);
727
+ const kept = states.get(el);
728
+ if (kept) {
729
+ clearTimeout(kept.linger);
730
+ kept.linger = void 0;
731
+ live.add(kept);
732
+ return;
733
+ }
734
+ const option = sanitizeEChartsOption(parseOption(el.getAttribute("data-aparte-echarts-option") ?? ""));
735
+ if (!option) {
736
+ el.replaceChildren(note(configOf(el), "echartsError", "aparte-code-error"));
737
+ return;
738
+ }
739
+ const stream = el.getAttribute("data-aparte-echarts-stream");
740
+ const before = stream ? streams.get(stream) : void 0;
741
+ if (stream) streams.set(stream, el);
742
+ const adopted = before && before !== el && !before.isConnected ? states.get(before) : void 0;
743
+ if (adopted && !adopted.dead) {
744
+ clearTimeout(adopted.linger);
745
+ adopted.linger = void 0;
746
+ states.delete(adopted.el);
747
+ adopted.el = el;
748
+ states.set(el, adopted);
749
+ live.add(adopted);
750
+ el.replaceChildren(adopted.box);
751
+ apply(adopted, option, false);
752
+ return;
753
+ }
754
+ mount(el, option);
755
+ },
756
+ disconnected(el) {
757
+ const c = states.get(el);
758
+ if (!c) return;
759
+ live.delete(c);
760
+ clearTimeout(c.linger);
761
+ c.linger = setTimeout(() => {
762
+ if (c.el === el && !el.isConnected) destroy(c);
763
+ }, LINGER_MS);
764
+ },
765
+ /** The element is gone for good (a tool row's cleanup): no linger. */
766
+ dispose(el) {
767
+ const c = states.get(el);
768
+ if (c) destroy(c);
769
+ },
770
+ /** Name a chart in the locale of `cfg`, and keep that config for its later words. */
771
+ label(el, cfg) {
772
+ configs.set(el, cfg);
773
+ const c = states.get(el);
774
+ const name = c ? nameOf(c) : word(cfg, "echartsChart");
775
+ el.setAttribute("aria-label", name);
776
+ const caption = el.querySelector("caption");
777
+ if (caption) caption.textContent = name;
778
+ relabelWords(el, cfg);
779
+ },
780
+ stop() {
781
+ watch.stop();
782
+ for (const c of [...all]) destroy(c);
783
+ }
784
+ };
785
+ }
786
+ function createFenceRenderer(setupId, options, charts) {
787
+ const progressive = options.progressive !== false;
788
+ const height = cssLength(options.height);
789
+ const parts = /* @__PURE__ */ new WeakMap();
790
+ const arrivingOf = (host) => {
791
+ const part = host.closest("[data-segment-id]") ?? host.closest("pre")?.parentElement ?? host.parentElement ?? host;
792
+ const fences = Array.from(part.querySelectorAll(LANGUAGES.map((l) => `code.language-${l}`).join(", ")));
793
+ const rank = Math.max(0, fences.indexOf(host));
794
+ let byRank = parts.get(part);
795
+ if (!byRank) {
796
+ byRank = /* @__PURE__ */ new Map();
797
+ parts.set(part, byRank);
798
+ }
799
+ let state = byRank.get(rank);
800
+ if (!state) {
801
+ state = { id: `${setupId}-s${++streamIds}`, waiting: null, timer: void 0, at: 0, started: null, drawn: null };
802
+ byRank.set(rank, state);
803
+ }
804
+ return state;
805
+ };
806
+ const markup = (json, stream) => `<aparte-echarts class="aparte-echarts" data-aparte-echarts="${escapeAttr(setupId)}" data-aparte-echarts-stream="${escapeAttr(stream)}" data-aparte-echarts-option="${escapeAttr(json)}"></aparte-echarts>`;
807
+ const PENDING = '<span class="aparte-skeleton aparte-skeleton--rect aparte-echarts-pending" aria-hidden="true"></span>';
808
+ const FAILED = '<span class="aparte-code-error" role="note" data-aparte-echarts-error></span><pre class="aparte-code-error" data-aparte-echarts-source></pre>';
809
+ const schedule = (state) => {
810
+ if (state.timer !== void 0 || !state.waiting) return;
811
+ const wait = Math.max(0, state.at + PROGRESSIVE_MS - performance.now());
812
+ state.timer = setTimeout(() => {
813
+ state.timer = void 0;
814
+ const job = state.waiting;
815
+ state.waiting = null;
816
+ if (!job) return;
817
+ state.at = performance.now();
818
+ const option = sanitizeEChartsOption(parseOption(job.source, true));
819
+ const json = option && drawable(option) ? JSON.stringify(option) : null;
820
+ if (json === null || json === state.drawn) {
821
+ job.answer(null);
822
+ return;
823
+ }
824
+ state.drawn = json;
825
+ job.answer(markup(json, state.id));
826
+ }, wait);
827
+ };
828
+ return {
829
+ languages: LANGUAGES,
830
+ lazy: true,
831
+ render(ctx) {
832
+ if (!ctx.display) return null;
833
+ const state = arrivingOf(ctx.host);
834
+ state.waiting?.answer(null);
835
+ state.waiting = null;
836
+ if (!ctx.open) {
837
+ state.started = null;
838
+ state.drawn = null;
839
+ const option = sanitizeEChartsOption(parseOption(ctx.source));
840
+ return option && drawable(option) ? markup(JSON.stringify(option), state.id) : FAILED;
841
+ }
842
+ if (!progressive) return null;
843
+ if (state.started === null || !ctx.source.startsWith(state.started)) {
844
+ state.started = ctx.source;
845
+ state.drawn = null;
846
+ return PENDING;
847
+ }
848
+ return new Promise((answer) => {
849
+ state.waiting = { source: ctx.source, answer };
850
+ schedule(state);
851
+ });
852
+ },
853
+ relabel(drawing, ctx) {
854
+ const error = drawing.querySelector("[data-aparte-echarts-error]");
855
+ if (error) error.textContent = word(ctx.config, "echartsError");
856
+ const source = drawing.querySelector("[data-aparte-echarts-source]");
857
+ if (source) source.textContent = ctx.source;
858
+ for (const box of Array.from(drawing.querySelectorAll(".aparte-echarts-pending"))) {
859
+ box.style.setProperty("block-size", height);
860
+ box.style.setProperty("max-inline-size", MAX_WIDTH);
861
+ }
862
+ for (const el of Array.from(drawing.querySelectorAll(TAG))) charts.label(el, ctx.config);
863
+ }
864
+ };
865
+ }
866
+ function createToolRenderer(setupId, options, config, charts) {
867
+ const height = cssLength(options.height);
868
+ const shown = /* @__PURE__ */ new WeakMap();
869
+ let warned = false;
870
+ const optionOf = (segment) => {
871
+ let raw = segment.structuredResult;
872
+ if (raw === void 0 && typeof segment.result === "string") raw = parseOption(segment.result.trim()) ?? segment.result;
873
+ const input = isObject(segment.toolCall?.input) ? segment.toolCall.input : {};
874
+ try {
875
+ if (options.map) raw = options.map(raw, input);
876
+ } catch (error) {
877
+ if (!warned) {
878
+ warned = true;
879
+ console.warn(`[aparte-echarts] the \`map\` for tool "${segment.toolCall?.name}" threw:`, error);
880
+ }
881
+ return null;
882
+ }
883
+ const option = sanitizeEChartsOption(raw);
884
+ return option && drawable(option) ? option : null;
885
+ };
886
+ const chartOf = (option) => {
887
+ const el = document.createElement(TAG);
888
+ el.className = "aparte-echarts";
889
+ el.setAttribute("data-aparte-echarts", setupId);
890
+ el.setAttribute("data-aparte-echarts-option", JSON.stringify(option));
891
+ return el;
892
+ };
893
+ const fill = (root, segment) => {
894
+ let structured = "";
895
+ try {
896
+ structured = JSON.stringify(segment.structuredResult) ?? "";
897
+ } catch {
898
+ }
899
+ const signature = `${segment.status}${segment.result ?? ""}${structured}`;
900
+ if (shown.get(root) === signature) return;
901
+ shown.set(root, signature);
902
+ for (const el of Array.from(root.querySelectorAll(TAG))) charts.dispose(el);
903
+ root.removeAttribute("aria-busy");
904
+ root.removeAttribute("aria-label");
905
+ root.removeAttribute("data-aparte-echarts-label");
906
+ switch (segment.status) {
907
+ case "pending":
908
+ root.setAttribute("aria-busy", "true");
909
+ root.setAttribute("data-aparte-echarts-label", "echartsLoading");
910
+ root.setAttribute("aria-label", word(config, "echartsLoading"));
911
+ root.replaceChildren(skeleton(height));
912
+ return;
913
+ case "resolved": {
914
+ const option = optionOf(segment);
915
+ root.replaceChildren(option ? chartOf(option) : note(config, "echartsError"));
916
+ return;
917
+ }
918
+ case "awaiting-approval":
919
+ root.replaceChildren(note(config, "echartsWaiting"));
920
+ return;
921
+ case "rejected":
922
+ case "aborted":
923
+ root.replaceChildren(note(config, "echartsNotRun"));
924
+ return;
925
+ default:
926
+ root.replaceChildren(note(config, "echartsFailed"));
927
+ }
928
+ };
929
+ return {
930
+ render(segment) {
931
+ const root = document.createElement("div");
932
+ root.className = "aparte-echarts-tool";
933
+ fill(root, segment);
934
+ return root;
935
+ },
936
+ // Same element, new inside only when the call changed: a chart the reader is
937
+ // looking at is not drawn again because an approval or a relabel passed by.
938
+ update: (root, segment) => fill(root, segment),
939
+ relabel(root) {
940
+ const key = root.getAttribute("data-aparte-echarts-label");
941
+ if (key) root.setAttribute("aria-label", word(config, key));
942
+ relabelWords(root, config);
943
+ for (const el of Array.from(root.querySelectorAll(TAG))) charts.label(el, config);
944
+ },
945
+ cleanup(root) {
946
+ for (const el of Array.from(root.querySelectorAll(TAG))) charts.dispose(el);
947
+ }
948
+ };
949
+ }
950
+ function watchTheme(changed) {
951
+ if (typeof MutationObserver === "undefined" || typeof document === "undefined") return { add: () => {
952
+ }, stop: () => {
953
+ } };
954
+ const observer = new MutationObserver(changed);
955
+ const init = { attributes: true, subtree: true, attributeFilter: ["data-aparte-theme"] };
956
+ observer.observe(document.documentElement, init);
957
+ const media = typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
958
+ media?.addEventListener?.("change", changed);
959
+ return {
960
+ add: (root) => observer.observe(root, init),
961
+ stop: () => {
962
+ observer.disconnect();
963
+ media?.removeEventListener?.("change", changed);
964
+ }
965
+ };
966
+ }
967
+ export {
968
+ ECHARTS_SERIES_HUES,
969
+ echartsPalette,
970
+ echartsTheme,
971
+ sanitizeEChartsOption,
972
+ setupEChartsWith
973
+ };
974
+ //# sourceMappingURL=core.js.map