@fasl-work/caos-app-shell 0.18.0 → 0.19.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.
@@ -0,0 +1,680 @@
1
+ import { LANG_STORAGE_KEY, THEME_STORAGE_KEY } from './chunk-33WTUQ7Q.js';
2
+ import { create } from 'zustand';
3
+ import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ var KEY = LANG_STORAGE_KEY;
7
+ function readLang() {
8
+ try {
9
+ const s = localStorage.getItem(KEY);
10
+ if (s === "en" || s === "es") return s;
11
+ } catch {
12
+ }
13
+ return "en";
14
+ }
15
+ var useLangStore = create((set, get) => ({
16
+ lang: readLang(),
17
+ setLang: (l) => {
18
+ try {
19
+ localStorage.setItem(KEY, l);
20
+ } catch {
21
+ }
22
+ set({ lang: l });
23
+ },
24
+ toggle: () => {
25
+ const next = get().lang === "en" ? "es" : "en";
26
+ try {
27
+ localStorage.setItem(KEY, next);
28
+ } catch {
29
+ }
30
+ set({ lang: next });
31
+ }
32
+ }));
33
+ var useShellLang = () => useLangStore((s) => s.lang);
34
+
35
+ // src/lib/text.ts
36
+ function pick(text, lang) {
37
+ if (text === void 0) return "";
38
+ if (typeof text === "string") return text;
39
+ return text[lang] ?? text.en;
40
+ }
41
+ var KEY2 = THEME_STORAGE_KEY;
42
+ function readTheme() {
43
+ try {
44
+ const s = localStorage.getItem(KEY2);
45
+ if (s === "light" || s === "dark") return s;
46
+ return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
47
+ } catch {
48
+ return "dark";
49
+ }
50
+ }
51
+ function applyTheme(t) {
52
+ try {
53
+ document.documentElement.dataset.theme = t;
54
+ } catch {
55
+ }
56
+ }
57
+ var useThemeStore = create((set, get) => ({
58
+ theme: readTheme(),
59
+ toggleTheme: () => {
60
+ const next = get().theme === "dark" ? "light" : "dark";
61
+ applyTheme(next);
62
+ try {
63
+ localStorage.setItem(KEY2, next);
64
+ } catch {
65
+ }
66
+ set({ theme: next });
67
+ },
68
+ setTheme: (t) => {
69
+ applyTheme(t);
70
+ try {
71
+ localStorage.setItem(KEY2, t);
72
+ } catch {
73
+ }
74
+ set({ theme: t });
75
+ }
76
+ }));
77
+ var SHELL_TOKENS = [
78
+ "--color-bg",
79
+ "--color-surface",
80
+ "--color-surface-2",
81
+ "--color-border",
82
+ "--color-fg",
83
+ "--color-fg-subtle",
84
+ "--color-fg-faint",
85
+ "--color-accent",
86
+ "--color-accent-fg",
87
+ "--color-accent-soft",
88
+ "--color-accent-2",
89
+ "--color-magenta",
90
+ "--color-good",
91
+ "--color-warn",
92
+ "--color-bad",
93
+ "--color-shadow",
94
+ "--font-sans",
95
+ "--font-mono",
96
+ "--text-xs",
97
+ "--text-sm",
98
+ "--text-md",
99
+ "--text-base",
100
+ "--text-lg",
101
+ "--text-xl",
102
+ "--text-2xl",
103
+ "--space-1",
104
+ "--space-2",
105
+ "--space-3",
106
+ "--space-4",
107
+ "--space-5",
108
+ "--space-6",
109
+ "--radius-sm",
110
+ "--radius-md",
111
+ "--radius-lg",
112
+ "--radius-pill",
113
+ "--maxw",
114
+ "--maxw-wide",
115
+ "--measure",
116
+ "--measure-text",
117
+ "--header-h",
118
+ "--rail-w",
119
+ "--fade",
120
+ "--icon-sm",
121
+ "--icon-md",
122
+ "--icon-lg",
123
+ "--z-header",
124
+ "--z-focus",
125
+ "--z-modal"
126
+ ];
127
+ function resolveToken(token, fallback = "") {
128
+ if (typeof document === "undefined") return fallback;
129
+ const v = getComputedStyle(document.documentElement).getPropertyValue(token).trim();
130
+ return v || fallback;
131
+ }
132
+ function useThemeTokens() {
133
+ const theme = useThemeStore((s) => s.theme);
134
+ return useMemo(() => {
135
+ const out = {};
136
+ for (const t of SHELL_TOKENS) out[t] = resolveToken(t);
137
+ return out;
138
+ }, [theme]);
139
+ }
140
+
141
+ // src/lib/format.ts
142
+ var LOCALE = { en: "en-US", es: "es-CL" };
143
+ var NBSP = "\xA0";
144
+ var SCIENTIFIC_BELOW = 1e-4;
145
+ function belowScientific(magnitude) {
146
+ return magnitude !== 0 && Math.abs(magnitude) < SCIENTIFIC_BELOW * (1 - 1e-12);
147
+ }
148
+ var NOT_AVAILABLE = { en: "not available", es: "no disponible" };
149
+ function formatNumber(value, lang, opts = {}) {
150
+ if (value === null || value === void 0 || !Number.isFinite(value)) return NOT_AVAILABLE[lang];
151
+ const v = opts.percent ? value * 100 : value;
152
+ const digits = opts.digits ?? 4;
153
+ const scientific = v !== 0 && (opts.notation === "scientific" || belowScientific(v));
154
+ const grouping = opts.grouping === false ? { useGrouping: false } : {};
155
+ const nf = opts.decimals !== void 0 ? new Intl.NumberFormat(LOCALE[lang], { minimumFractionDigits: opts.decimals, maximumFractionDigits: opts.decimals, ...grouping }) : scientific ? new Intl.NumberFormat(LOCALE[lang], { notation: "scientific", maximumSignificantDigits: digits }) : Math.abs(v) >= 10 ** (digits - 1) ? new Intl.NumberFormat(LOCALE[lang], { maximumFractionDigits: 0, ...grouping }) : new Intl.NumberFormat(LOCALE[lang], { maximumSignificantDigits: digits, ...grouping });
156
+ return opts.percent ? `${nf.format(v)}${NBSP}%` : nf.format(v);
157
+ }
158
+ function formatTicks(values, lang, opts = {}) {
159
+ let o = opts;
160
+ if (opts.decimals === void 0 && opts.notation === void 0) {
161
+ const scale = opts.percent ? 100 : 1;
162
+ const small = values.some((v) => v !== null && v !== void 0 && Number.isFinite(v) && belowScientific(v * scale));
163
+ if (small) o = { ...opts, notation: "scientific" };
164
+ }
165
+ return values.map((v) => v === null || v === void 0 ? "" : formatNumber(v, lang, o));
166
+ }
167
+ function useFormat() {
168
+ const lang = useShellLang();
169
+ return (value, opts) => formatNumber(value, lang, opts);
170
+ }
171
+ function useStageSize() {
172
+ const [size, setSize] = useState({ width: 0, height: 0 });
173
+ const roRef = useRef(null);
174
+ const ref = useCallback((el) => {
175
+ roRef.current?.disconnect();
176
+ roRef.current = null;
177
+ if (!el) return;
178
+ const measure = () => {
179
+ const r = el.getBoundingClientRect();
180
+ setSize((prev) => prev.width === Math.round(r.width) && prev.height === Math.round(r.height) ? prev : { width: Math.round(r.width), height: Math.round(r.height) });
181
+ };
182
+ measure();
183
+ if (typeof ResizeObserver !== "undefined") {
184
+ roRef.current = new ResizeObserver(measure);
185
+ roRef.current.observe(el);
186
+ }
187
+ }, []);
188
+ useEffect(() => () => roRef.current?.disconnect(), []);
189
+ return [ref, size];
190
+ }
191
+ function Stage({ label, children, className }) {
192
+ const lang = useShellLang();
193
+ const [ref, size] = useStageSize();
194
+ const ready = size.width > 0 && size.height > 0;
195
+ const name = pick(label, lang);
196
+ useEffect(() => {
197
+ if (ready) return;
198
+ const t = setTimeout(() => {
199
+ console.error(`[caos-app-shell] Stage "${name}" has no size; give its container a height (ADR-0071 rule 8)`);
200
+ }, 1500);
201
+ return () => clearTimeout(t);
202
+ }, [ready, name]);
203
+ return /* @__PURE__ */ jsx(
204
+ "div",
205
+ {
206
+ ref,
207
+ className: ["caos-stage", className].filter(Boolean).join(" "),
208
+ "data-stage": name,
209
+ "data-drawn": ready ? "1" : "0",
210
+ "data-width": size.width,
211
+ "data-height": size.height,
212
+ children: ready ? children(size) : null
213
+ }
214
+ );
215
+ }
216
+
217
+ // src/chart/text.ts
218
+ function referenceLineName(lang, n, of) {
219
+ const base = lang === "es" ? "L\xEDnea de referencia" : "Reference line";
220
+ return of > 1 ? `${base} ${n}` : base;
221
+ }
222
+ var context;
223
+ function fontFamily() {
224
+ if (typeof document === "undefined") return "sans-serif";
225
+ return getComputedStyle(document.documentElement).getPropertyValue("--font-sans").trim() || "sans-serif";
226
+ }
227
+ function textWidth(text, px, weight = 400) {
228
+ if (context === void 0) {
229
+ try {
230
+ context = typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d");
231
+ } catch {
232
+ context = null;
233
+ }
234
+ }
235
+ if (!context) return text.length * px * 0.62;
236
+ context.font = `${weight} ${px}px ${fontFamily()}`;
237
+ return context.measureText(text).width;
238
+ }
239
+ function widestLabel(labels, px, weight = 400) {
240
+ return Math.max(0, ...labels.map((label) => textWidth(label, px, weight)));
241
+ }
242
+ function shorten(line, width, px) {
243
+ if (textWidth(line, px) <= width) return line;
244
+ let cut = line;
245
+ while (cut.length > 1 && textWidth(`${cut}\u2026`, px) > width) cut = cut.slice(0, -1);
246
+ return `${cut.trimEnd()}\u2026`;
247
+ }
248
+ function fitLabel(label, width, px, maxLines = 2) {
249
+ if (textWidth(label, px) <= width) return { lines: [label], shortened: false };
250
+ const words = label.split(" ");
251
+ if (maxLines === 2 && words.length > 1) {
252
+ let best = null;
253
+ let bestWidth = Infinity;
254
+ for (let k = 1; k < words.length; k += 1) {
255
+ const first = words.slice(0, k).join(" ");
256
+ const second = words.slice(k).join(" ");
257
+ const widest = Math.max(textWidth(first, px), textWidth(second, px));
258
+ if (widest < bestWidth) {
259
+ bestWidth = widest;
260
+ best = [first, second];
261
+ }
262
+ }
263
+ if (best && bestWidth <= width) return { lines: best, shortened: false };
264
+ if (best) return { lines: [shorten(best[0], width, px), shorten(best[1], width, px)], shortened: true };
265
+ }
266
+ return { lines: [shorten(label, width, px)], shortened: true };
267
+ }
268
+ function niceStep(x) {
269
+ if (!(x > 0) || !Number.isFinite(x)) return 1;
270
+ const magnitude = 10 ** Math.floor(Math.log10(x));
271
+ return ([1, 2, 2.5, 5, 10].find((m) => m * magnitude >= x * (1 - 1e-12)) ?? 10) * magnitude;
272
+ }
273
+ function niceStepAt(x, resolution = 0) {
274
+ if (!(resolution > 0)) return niceStep(x);
275
+ if (!(x > 0) || !Number.isFinite(x)) return resolution;
276
+ const start = Math.floor(Math.log10(Math.min(x, resolution))) - 1;
277
+ for (let e = start; e < start + 40; e++) {
278
+ for (const m of [1, 2, 2.5, 5]) {
279
+ const step = Number((m * 10 ** e).toPrecision(12));
280
+ const ratio = step / resolution;
281
+ if (step >= x * (1 - 1e-12) && step >= resolution * (1 - 1e-9) && Math.abs(ratio - Math.round(ratio)) < 1e-6) return step;
282
+ }
283
+ }
284
+ return Math.max(x, resolution);
285
+ }
286
+ function niceTicks(lo, hi, room, labelSize, most = 6, resolution = 0) {
287
+ if (!Number.isFinite(lo) || !Number.isFinite(hi)) return [0];
288
+ if (hi < lo) [lo, hi] = [hi, lo];
289
+ if (hi === lo) hi = lo === 0 ? 1 : lo + Math.abs(lo);
290
+ const count = Math.max(2, Math.min(most, Math.floor(room / (labelSize + 16)) + 1));
291
+ const step = niceStepAt((hi - lo) / (count - 1), resolution);
292
+ const first = Math.floor(lo / step + 1e-9) * step;
293
+ const ticks = [];
294
+ for (let k = 0; first + k * step <= hi + step * 1e-9 || ticks.length < 2; k += 1) {
295
+ ticks.push(Number((first + k * step).toPrecision(12)));
296
+ if (ticks.length > 50) break;
297
+ }
298
+ if (ticks[ticks.length - 1] < hi - step * 1e-9) ticks.push(Number((ticks[ticks.length - 1] + step).toPrecision(12)));
299
+ return ticks;
300
+ }
301
+ function repeatedLabels(labels) {
302
+ let n = 0;
303
+ for (let i = 1; i < labels.length; i++) if (labels[i] && labels[i] === labels[i - 1]) n++;
304
+ return n;
305
+ }
306
+ function ChartSummary({ id, text }) {
307
+ return /* @__PURE__ */ jsx("p", { id, className: "caos-chart-summary sr-only", children: text });
308
+ }
309
+ function ChartDataTable({ id, caption, head, rows }) {
310
+ return /* @__PURE__ */ jsx("div", { className: "sr-only", "data-chart-table-box": "", children: /* @__PURE__ */ jsxs("table", { id, className: "caos-chart-table", "data-chart-table": "", children: [
311
+ /* @__PURE__ */ jsx("caption", { children: caption }),
312
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: head.map((h, i) => /* @__PURE__ */ jsx("th", { scope: "col", children: h }, i)) }) }),
313
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, c) => c === 0 ? /* @__PURE__ */ jsx("th", { scope: "row", children: cell }, c) : /* @__PURE__ */ jsx("td", { children: cell }, c)) }, r)) })
314
+ ] }) });
315
+ }
316
+ var RESET_ZOOM = { en: "Reset zoom", es: "Restablecer zoom" };
317
+ function ResetZoomButton({ lang, onReset }) {
318
+ return /* @__PURE__ */ jsx("button", { type: "button", className: "caos-chart-reset", "data-chart-reset": "", onClick: onReset, children: RESET_ZOOM[lang] });
319
+ }
320
+ var CHART_ROLE = { en: "interactive chart", es: "gr\xE1fico interactivo" };
321
+ var ZOOM_HINT = {
322
+ en: "Drag to zoom, click then scroll to zoom, double-click to reset; arrow keys read the values",
323
+ es: "Arrastre para acercar, haga clic y desplace para acercar, doble clic para restablecer; las flechas leen los valores"
324
+ };
325
+
326
+ // src/chart/interaction.ts
327
+ var MIN_SPAN_SHARE = 1e-3;
328
+ var WHEEL_RATE = 15e-4;
329
+ var KEY_ZOOM = 0.7;
330
+ var KEY_PAN = 0.1;
331
+ var DRAG_THRESHOLD = 5;
332
+ var forward = (v, log) => log ? Math.log10(v) : v;
333
+ var back = (v, log) => log ? 10 ** v : v;
334
+ function logOk(log, ...ranges) {
335
+ return Boolean(log) && ranges.every((r) => r[0] > 0 && r[1] > 0);
336
+ }
337
+ function zoomRange(cur, full, anchor, factor, opts = {}) {
338
+ const log = logOk(opts.log, cur, full) && anchor > 0;
339
+ const c0 = forward(Math.min(cur[0], cur[1]), log);
340
+ const c1 = forward(Math.max(cur[0], cur[1]), log);
341
+ const f0 = forward(Math.min(full[0], full[1]), log);
342
+ const f1 = forward(Math.max(full[0], full[1]), log);
343
+ const fullSpan = f1 - f0;
344
+ if (!(fullSpan > 0) || !Number.isFinite(factor) || !(factor > 0) || !Number.isFinite(anchor)) return [full[0], full[1]];
345
+ const span = c1 - c0 > 0 ? c1 - c0 : fullSpan;
346
+ const least = Math.min(fullSpan, opts.minSpan !== void 0 ? forward(opts.minSpan, false) : fullSpan * MIN_SPAN_SHARE);
347
+ const next = Math.min(fullSpan, Math.max(least, span * factor));
348
+ if (next >= fullSpan * (1 - 1e-9)) return [full[0], full[1]];
349
+ const a = forward(anchor, log);
350
+ const share = Math.min(1, Math.max(0, (a - c0) / span));
351
+ let lo = a - share * next;
352
+ let hi = lo + next;
353
+ if (lo < f0) {
354
+ hi += f0 - lo;
355
+ lo = f0;
356
+ }
357
+ if (hi > f1) {
358
+ lo -= hi - f1;
359
+ hi = f1;
360
+ }
361
+ return [back(Math.max(f0, lo), log), back(Math.min(f1, hi), log)];
362
+ }
363
+ function panRange(cur, full, share, log = false) {
364
+ const lg = logOk(log, cur, full);
365
+ const c0 = forward(Math.min(cur[0], cur[1]), lg);
366
+ const c1 = forward(Math.max(cur[0], cur[1]), lg);
367
+ const f0 = forward(Math.min(full[0], full[1]), lg);
368
+ const f1 = forward(Math.max(full[0], full[1]), lg);
369
+ const span = c1 - c0;
370
+ if (!(span > 0) || span >= f1 - f0 || !Number.isFinite(share)) return [cur[0], cur[1]];
371
+ const lo = Math.min(f1 - span, Math.max(f0, c0 + share * span));
372
+ return [back(lo, lg), back(lo + span, lg)];
373
+ }
374
+ function revealValue(cur, full, value, log = false) {
375
+ if (value >= Math.min(cur[0], cur[1]) && value <= Math.max(cur[0], cur[1])) return [cur[0], cur[1]];
376
+ const lg = logOk(log, cur, full) && value > 0;
377
+ const mid = (forward(cur[0], lg) + forward(cur[1], lg)) / 2;
378
+ return panRange(cur, full, (forward(value, lg) - mid) / (forward(cur[1], lg) - forward(cur[0], lg) || 1), log);
379
+ }
380
+ function isZoomed(cur, full) {
381
+ const tol = Math.abs(full[1] - full[0]) * 1e-9;
382
+ return Math.abs(cur[0] - full[0]) > tol || Math.abs(cur[1] - full[1]) > tol;
383
+ }
384
+ function wheelFactor(deltaY, deltaMode = 0) {
385
+ const px = deltaMode === 1 ? deltaY * 16 : deltaMode === 2 ? deltaY * 400 : deltaY;
386
+ return Math.exp(Math.max(-600, Math.min(600, px)) * WHEEL_RATE);
387
+ }
388
+ function keyAction(key, mods = {}) {
389
+ if (mods.ctrl || mods.meta || mods.alt) return null;
390
+ const arrow = key === "ArrowLeft" || key === "ArrowUp" ? -1 : key === "ArrowRight" || key === "ArrowDown" ? 1 : 0;
391
+ if (arrow !== 0) return mods.shift ? arrow < 0 ? "panPrev" : "panNext" : arrow < 0 ? "prev" : "next";
392
+ switch (key) {
393
+ case "Home":
394
+ return "first";
395
+ case "End":
396
+ return "last";
397
+ case "+":
398
+ case "=":
399
+ return "zoomIn";
400
+ case "-":
401
+ case "_":
402
+ return "zoomOut";
403
+ case "0":
404
+ case "Escape":
405
+ return "reset";
406
+ case "Enter":
407
+ case " ":
408
+ return "activate";
409
+ default:
410
+ return null;
411
+ }
412
+ }
413
+ function stepIndex(i, action, n) {
414
+ if (n <= 0) return null;
415
+ if (action === "first") return 0;
416
+ if (action === "last") return n - 1;
417
+ if (i === null || i < 0 || i >= n) return action === "next" ? 0 : n - 1;
418
+ return Math.min(n - 1, Math.max(0, i + (action === "next" ? 1 : -1)));
419
+ }
420
+ function firstVisible(xs, lo, hi) {
421
+ for (let i = 0; i < xs.length; i++) if (xs[i] >= lo && xs[i] <= hi) return i;
422
+ return null;
423
+ }
424
+ function roundWindow(r, n, minCount = 2) {
425
+ const count = Math.min(n, Math.max(1, minCount));
426
+ let s = Math.max(0, Math.min(n - count, Math.round(r[0])));
427
+ let e = Math.min(n, Math.max(s + count, Math.round(r[1])));
428
+ if (e - s < count) s = Math.max(0, e - count);
429
+ return [s, e];
430
+ }
431
+ function stepWindow(cur, n, anchor, factor, minCount = 2) {
432
+ const least = Math.min(n, Math.max(1, minCount));
433
+ const span = cur[1] - cur[0];
434
+ let w = roundWindow(zoomRange(cur, [0, n], anchor, factor, { minSpan: least }), n, least);
435
+ const share = span > 0 ? (anchor - cur[0]) / span : 0.5;
436
+ if (factor < 1 && w[1] - w[0] >= span && span > least) w = share < 0.5 ? [cur[0], cur[1] - 1] : [cur[0] + 1, cur[1]];
437
+ else if (factor > 1 && w[1] - w[0] <= span && span < n) w = share >= 0.5 && cur[0] > 0 || cur[1] >= n ? [cur[0] - 1, cur[1]] : [cur[0], cur[1] + 1];
438
+ return roundWindow(w, n, least);
439
+ }
440
+ function windowRange(n, win) {
441
+ return win ? roundWindow(win, n) : [0, n];
442
+ }
443
+ function categoryAt(pos, start, end, win, n) {
444
+ if (!(end > start) || pos < start || pos > end) return null;
445
+ const [s, e] = windowRange(n, win);
446
+ const k = Math.min(e - s - 1, Math.floor((pos - start) / (end - start) * (e - s)));
447
+ return s + Math.max(0, k);
448
+ }
449
+ function brushWindow(a, b, start, end, win, n) {
450
+ if (!(end > start)) return null;
451
+ const lo = Math.max(start, Math.min(a, b));
452
+ const hi = Math.min(end, Math.max(a, b));
453
+ const first = categoryAt(lo, start, end, win, n);
454
+ const last = categoryAt(Math.min(hi, end - 1e-9), start, end, win, n);
455
+ if (first === null || last === null) return null;
456
+ return roundWindow([first, last + 1], n);
457
+ }
458
+ function windowZoomed(n, win) {
459
+ if (!win) return false;
460
+ const [s, e] = windowRange(n, win);
461
+ return s > 0 || e < n;
462
+ }
463
+ function pinchState(a, b) {
464
+ return { dist: Math.hypot(a.x - b.x, a.y - b.y), mx: (a.x + b.x) / 2, my: (a.y + b.y) / 2 };
465
+ }
466
+ function axisTitleFits(title, room, px = 11) {
467
+ return textWidth(title, px) <= Math.max(0, room);
468
+ }
469
+ function wrapLines(text, width, px = 11) {
470
+ const words = text.split(/\s+/).filter(Boolean);
471
+ const lines = [];
472
+ let line = "";
473
+ const push = (word) => {
474
+ let rest = word;
475
+ while (textWidth(rest, px) > width && rest.length > 1) {
476
+ let cut = rest.length - 1;
477
+ while (cut > 1 && textWidth(rest.slice(0, cut), px) > width) cut -= 1;
478
+ lines.push(rest.slice(0, cut));
479
+ rest = rest.slice(cut);
480
+ }
481
+ line = rest;
482
+ };
483
+ for (const word of words) {
484
+ const tryLine = line ? `${line} ${word}` : word;
485
+ if (!line || textWidth(tryLine, px) <= width) {
486
+ if (!line && textWidth(word, px) > width) push(word);
487
+ else line = tryLine;
488
+ } else {
489
+ lines.push(line);
490
+ line = "";
491
+ if (textWidth(word, px) > width) push(word);
492
+ else line = word;
493
+ }
494
+ }
495
+ if (line) lines.push(line);
496
+ return lines.length > 0 ? lines : [""];
497
+ }
498
+ function thinRows(n, most = 1e3) {
499
+ if (n <= most) return Array.from({ length: n }, (_, i) => i);
500
+ const step = Math.ceil(n / most);
501
+ const rows = [];
502
+ for (let i = 0; i < n; i += step) rows.push(i);
503
+ if (rows[rows.length - 1] !== n - 1) rows.push(n - 1);
504
+ return rows;
505
+ }
506
+ function allIntegers(values) {
507
+ return values.every((v) => v === null || v === void 0 || !Number.isFinite(v) || Number.isInteger(v));
508
+ }
509
+ function sameData(a, b) {
510
+ if (a === b) return true;
511
+ if (a.length !== b.length) return false;
512
+ for (let k = 0; k < a.length; k++) {
513
+ const x = a[k];
514
+ const y = b[k];
515
+ if (x === y) continue;
516
+ if (x.length !== y.length) return false;
517
+ for (let i = 0; i < x.length; i++) {
518
+ const u = x[i];
519
+ const v = y[i];
520
+ if (u === v) continue;
521
+ const mu = u === null || u === void 0 || !Number.isFinite(u);
522
+ const mv = v === null || v === void 0 || !Number.isFinite(v);
523
+ if (mu && mv) continue;
524
+ return false;
525
+ }
526
+ }
527
+ return true;
528
+ }
529
+ function leastLabelSpan(fmt, full, plotPx, spacePx) {
530
+ if (fmt && fmt.decimals !== void 0) return 0;
531
+ const scale = fmt?.percent ? 100 : 1;
532
+ const digits = fmt?.digits ?? 4;
533
+ const top = Math.max(Math.abs(full[0]), Math.abs(full[1])) * scale;
534
+ if (!(top > 0) || !Number.isFinite(top) || top < SCIENTIFIC_FLOOR) return 0;
535
+ const res = top >= 10 ** (digits - 1) ? 1 : 10 ** (Math.floor(Math.log10(top)) - digits + 1);
536
+ return res * (Math.ceil(Math.max(0, plotPx) / Math.max(1, spacePx)) + 1) / scale;
537
+ }
538
+ var SCIENTIFIC_FLOOR = 1e-4;
539
+ function formatTime(seconds, lang, spanSeconds) {
540
+ const short = spanSeconds < 3 * 86400;
541
+ const opts = short ? { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" } : { year: "numeric", month: "short", day: "numeric" };
542
+ return new Intl.DateTimeFormat(lang === "es" ? "es-CL" : "en-US", opts).format(new Date(seconds * 1e3));
543
+ }
544
+ function brushRange(sel, full, least) {
545
+ const f0 = Math.min(full[0], full[1]);
546
+ const f1 = Math.max(full[0], full[1]);
547
+ if (least >= f1 - f0) return [f0, f1];
548
+ let lo = Math.max(f0, Math.min(sel[0], sel[1]));
549
+ let hi = Math.min(f1, Math.max(sel[0], sel[1]));
550
+ if (hi - lo < least) {
551
+ const mid = (lo + hi) / 2;
552
+ lo = mid - least / 2;
553
+ hi = mid + least / 2;
554
+ if (lo < f0) {
555
+ hi += f0 - lo;
556
+ lo = f0;
557
+ }
558
+ if (hi > f1) {
559
+ lo -= hi - f1;
560
+ hi = f1;
561
+ }
562
+ }
563
+ return [lo, hi];
564
+ }
565
+ function chartSummary(lang, p) {
566
+ const es = lang === "es";
567
+ const parts = [];
568
+ if (p.kind === "bars") parts.push(es ? `Gr\xE1fico de barras, ${p.name}: ${p.count} barras` : `Bar chart, ${p.name}: ${p.count} bars`);
569
+ else parts.push(es ? `Gr\xE1fico de l\xEDneas, ${p.name}: ${p.count} puntos` : `Line chart, ${p.name}: ${p.count} points`);
570
+ if (p.series && p.series.length > 0) parts.push(es ? `${p.series.length === 1 ? "serie" : "series"} ${p.series.join(", ")}` : `${p.series.length === 1 ? "series" : "series"} ${p.series.join(", ")}`);
571
+ if (p.xLabel && p.xFrom !== void 0 && p.xTo !== void 0) parts.push(es ? `${p.xLabel} de ${p.xFrom} a ${p.xTo}` : `${p.xLabel} from ${p.xFrom} to ${p.xTo}`);
572
+ if (p.first && p.last) {
573
+ const axis = p.yLabel ? `${p.yLabel}: ` : "";
574
+ parts.push(es ? `${axis}de ${p.first.text} en ${p.first.at} a ${p.last.text} en ${p.last.at}` : `${axis}from ${p.first.text} at ${p.first.at} to ${p.last.text} at ${p.last.at}`);
575
+ } else if (p.low && p.high) {
576
+ const axis = p.yLabel ? `${p.yLabel}: ` : "";
577
+ parts.push(es ? `${axis}m\xEDnimo ${p.low.text} en ${p.low.at}, m\xE1ximo ${p.high.text} en ${p.high.at}` : `${axis}lowest ${p.low.text} at ${p.low.at}, highest ${p.high.text} at ${p.high.at}`);
578
+ }
579
+ return `${parts.join(". ")}.`;
580
+ }
581
+ function defaultChartName(lang, yLabel, xLabel) {
582
+ return lang === "es" ? `${yLabel} seg\xFAn ${xLabel}` : `${yLabel} by ${xLabel}`;
583
+ }
584
+ function barExtent(value, span) {
585
+ if (value === null || value === void 0 || !Number.isFinite(value)) return null;
586
+ if (!span) return { a: 0, b: value, dot: false, given: false };
587
+ const [a, b] = span;
588
+ if (a === null || a === void 0 || b === null || b === void 0 || !Number.isFinite(a) || !Number.isFinite(b)) return null;
589
+ return { a, b, dot: a === b, given: true };
590
+ }
591
+ function clipExtent(e, lo, hi, all = false) {
592
+ if (!e || !e.given && !all) return e;
593
+ if (Math.max(e.a, e.b) < lo || Math.min(e.a, e.b) > hi) return null;
594
+ const c = (v) => Math.min(hi, Math.max(lo, v));
595
+ return { ...e, a: c(e.a), b: c(e.b) };
596
+ }
597
+ function bandsOf(groups) {
598
+ const out = [];
599
+ groups.forEach((g, i) => {
600
+ const last = out[out.length - 1];
601
+ if (g !== null && g !== void 0 && last && last.group === g && last.to === i) last.to = i + 1;
602
+ else out.push({ from: i, to: i + 1, ...g !== null && g !== void 0 ? { group: g } : {} });
603
+ });
604
+ return out;
605
+ }
606
+ var GROUP_MARGIN = 0.1;
607
+ function memberSlots(count, most) {
608
+ const size = (1 - 2 * GROUP_MARGIN) / Math.max(1, most);
609
+ return { size, start: 0.5 - count * size / 2 };
610
+ }
611
+ function memberAt(frac, count, most) {
612
+ const { size, start } = memberSlots(count, most);
613
+ return Math.max(0, Math.min(count - 1, Math.floor((frac - start) / size)));
614
+ }
615
+ function unusedLegendTones(used, legend) {
616
+ const set = new Set(used);
617
+ return [...new Set(legend.map((l) => l.tone))].filter((t) => !set.has(t));
618
+ }
619
+ function boxesOverlap(a, b) {
620
+ return a.x0 < b.x1 && b.x0 < a.x1 && a.y0 < b.y1 && b.y0 < a.y1;
621
+ }
622
+ function countOverlaps(boxes) {
623
+ let n = 0;
624
+ for (let i = 0; i < boxes.length; i++) for (let j = i + 1; j < boxes.length; j++) if (boxesOverlap(boxes[i], boxes[j])) n += 1;
625
+ return n;
626
+ }
627
+ function ellipsize(text, k) {
628
+ return `${text.slice(0, Math.max(0, k)).trimEnd()}\u2026`;
629
+ }
630
+ function placeXLabel(text, s) {
631
+ const clear = (x0, w, y) => x0 >= s.left - 1e-6 && x0 + w <= s.right + 1e-6 && !s.boxes.some((b) => x0 < b.x1 + s.gap && x0 + w + s.gap > b.x0 && y - s.textH < b.y1 && y > b.y0);
632
+ const primary = (w) => {
633
+ let x0 = s.px + s.gap;
634
+ if (x0 + w > s.right) x0 = s.px - s.gap - w;
635
+ if (x0 < s.left) x0 = Math.max(s.left, Math.min(s.right - w, s.px - w / 2));
636
+ return x0;
637
+ };
638
+ const others = (w) => [s.px - s.gap - w, s.px + s.gap, Math.max(s.left, Math.min(s.right - w, s.px - w / 2))];
639
+ const attempt = (candidate) => {
640
+ const w = s.measure(candidate);
641
+ for (const pass of [[primary(w)], others(w)]) {
642
+ for (let y = s.first; y <= s.bottom + 1e-6; y += s.rowH) {
643
+ for (const x0 of pass) if (clear(x0, w, y)) return { text: candidate, x0, y, w, cut: candidate !== text };
644
+ }
645
+ }
646
+ return null;
647
+ };
648
+ const whole = attempt(text);
649
+ if (whole) return whole;
650
+ for (let k = text.length - 1; k >= 1; k--) {
651
+ const cut = attempt(ellipsize(text, k));
652
+ if (cut) return cut;
653
+ }
654
+ return null;
655
+ }
656
+ function placeYLabel(text, s) {
657
+ const above = s.py - s.gap;
658
+ const below = s.py + s.gap + s.textH;
659
+ const fitsRow = (y) => y - s.textH >= s.top - 1e-6 && y <= s.bottom + 1e-6;
660
+ const near = above - s.textH < s.top ? [below, above] : [above, below];
661
+ const rows = [...near, above - s.rowH, below + s.rowH].filter(fitsRow);
662
+ const clear = (x0, w, y) => x0 >= s.left + s.gap - 1e-6 && x0 + w <= s.right - s.gap + 1e-6 && !s.boxes.some((b) => x0 < b.x1 + s.gap && x0 + w + s.gap > b.x0 && y - s.textH < b.y1 && y > b.y0);
663
+ const attempt = (candidate) => {
664
+ const w = s.measure(candidate);
665
+ for (const y of rows) {
666
+ const inRow = s.boxes.filter((b) => y - s.textH < b.y1 && y > b.y0).map((b) => b.x0 - 2 * s.gap - w).sort((a, b) => b - a);
667
+ for (const x0 of [s.right - s.gap - w, ...inRow, s.left + s.gap]) if (clear(x0, w, y)) return { text: candidate, x0, y, w, cut: candidate !== text };
668
+ }
669
+ return null;
670
+ };
671
+ const whole = attempt(text);
672
+ if (whole) return whole;
673
+ for (let k = text.length - 1; k >= 1; k--) {
674
+ const cut = attempt(ellipsize(text, k));
675
+ if (cut) return cut;
676
+ }
677
+ return null;
678
+ }
679
+
680
+ export { CHART_ROLE, ChartDataTable, ChartSummary, DRAG_THRESHOLD, GROUP_MARGIN, KEY_PAN, KEY_ZOOM, MIN_SPAN_SHARE, NBSP, NOT_AVAILABLE, ResetZoomButton, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, ZOOM_HINT, allIntegers, applyTheme, axisTitleFits, bandsOf, barExtent, brushRange, brushWindow, categoryAt, chartSummary, clipExtent, countOverlaps, defaultChartName, firstVisible, fitLabel, fontFamily, formatNumber, formatTicks, formatTime, isZoomed, keyAction, leastLabelSpan, memberAt, memberSlots, niceStep, niceTicks, panRange, pick, pinchState, placeXLabel, placeYLabel, readTheme, referenceLineName, repeatedLabels, resolveToken, revealValue, roundWindow, sameData, stepIndex, stepWindow, textWidth, thinRows, unusedLegendTones, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens, wheelFactor, widestLabel, windowRange, windowZoomed, wrapLines, zoomRange };