@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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, NOT_AVAILABLE, widestLabel, fitLabel, formatTicks, niceTicks, textWidth, formatNumber, NBSP } from './chunk-DBCMAL2G.js';
2
- export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, fitLabel, fontFamily, formatNumber, formatTicks, niceStep, niceTicks, pick, readTheme, resolveToken, textWidth, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens, widestLabel } from './chunk-DBCMAL2G.js';
1
+ import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStageSize, barExtent, clipExtent, allIntegers, bandsOf, windowRange, windowZoomed, unusedLegendTones, NOT_AVAILABLE, formatNumber, widestLabel, fitLabel, GROUP_MARGIN, axisTitleFits, wrapLines, formatTicks, niceTicks, textWidth, repeatedLabels, chartSummary, NBSP, CHART_ROLE, ChartSummary, ChartDataTable, ResetZoomButton, memberSlots, wheelFactor, pinchState, DRAG_THRESHOLD, panRange, brushWindow, keyAction, KEY_ZOOM, stepIndex, KEY_PAN, stepWindow, roundWindow, zoomRange, categoryAt, memberAt } from './chunk-3LONJM5B.js';
2
+ export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, fitLabel, fontFamily, formatNumber, formatTicks, niceStep, niceTicks, pick, readTheme, resolveToken, textWidth, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens, widestLabel } from './chunk-3LONJM5B.js';
3
3
  import { APP_ROUTE, LANDING_ROUTE } from './chunk-33WTUQ7Q.js';
4
4
  export { APP_ROUTE, LANDING_ROUTE, LANG_STORAGE_KEY, STANDARD_ROUTES, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from './chunk-33WTUQ7Q.js';
5
5
  import { createContext, Component, useState, useRef, useId, useEffect, useLayoutEffect, useCallback, useContext, useMemo, Children, isValidElement } from 'react';
@@ -3142,23 +3142,87 @@ var TONE = {
3142
3142
  accent: "var(--color-accent)",
3143
3143
  neutral: "var(--color-fg-faint)"
3144
3144
  };
3145
+ var REST = {
3146
+ en: "Point at a bar, or use the arrow keys, to read its value",
3147
+ es: "Apunte a una barra, o use las flechas, para leer su valor"
3148
+ };
3145
3149
  function Lines({ x, y, lines, anchor, fill }) {
3146
3150
  return /* @__PURE__ */ jsx("text", { x, y, textAnchor: anchor, fontSize: PX, fill, style: { fontFamily: "var(--font-sans)" }, children: lines.map((line, i) => /* @__PURE__ */ jsx("tspan", { x, dy: i === 0 ? 0 : LINE, children: line }, i)) });
3147
3151
  }
3148
- function BarChart({ title, data, axis, orientation = "horizontal", values = true, height, onSelect }) {
3152
+ function BarChart({ title, data, axis, orientation = "horizontal", values = true, height, onSelect, zoomOff, legend }) {
3149
3153
  const lang = useShellLang();
3150
- const [ref, box] = useStageSize();
3154
+ const [measureRef, box] = useStageSize();
3155
+ const innerRef = useRef(null);
3156
+ const setInner = useCallback(
3157
+ (el) => {
3158
+ innerRef.current = el;
3159
+ measureRef(el);
3160
+ },
3161
+ [measureRef]
3162
+ );
3151
3163
  const fill = height === "fill";
3152
3164
  const name = pick(title, lang);
3153
- const axisTitle = axis.unit ? `${pick(axis.label, lang)} (${pick(axis.unit, lang)})` : pick(axis.label, lang);
3165
+ const unit = axis.unit ? pick(axis.unit, lang) : "";
3166
+ const axisTitle = unit ? `${pick(axis.label, lang)} (${unit})` : pick(axis.label, lang);
3154
3167
  const fmt = axis.format ?? { digits: 3 };
3168
+ const n = data.length;
3155
3169
  const finite = data.map((d) => d.value).filter((v) => v !== null && v !== void 0 && Number.isFinite(v));
3156
- const lo = Math.min(0, axis.range ? axis.range[0] : Math.min(0, ...finite));
3157
- const hi = Math.max(0, axis.range ? axis.range[1] : Math.max(0, ...finite));
3170
+ const spanEnds = data.flatMap((d) => {
3171
+ const e = barExtent(d.value, d.span);
3172
+ return e && e.given ? [e.a, e.b] : [];
3173
+ });
3174
+ const floating = data.some((d) => d.span !== void 0 && d.span !== null);
3175
+ const reach = [...finite, ...spanEnds];
3176
+ const lo = floating && axis.range ? Math.min(axis.range[0], axis.range[1]) : Math.min(0, axis.range ? axis.range[0] : Math.min(0, ...reach));
3177
+ const hi = floating && axis.range ? Math.max(axis.range[0], axis.range[1]) : Math.max(0, axis.range ? axis.range[1] : Math.max(0, ...reach));
3178
+ const extents = data.map((d) => clipExtent(barExtent(d.value, d.span), lo, hi, floating));
3179
+ const anyDot = extents.some((e) => e !== null && e.dot);
3180
+ const countAxis = fmt.decimals === void 0 && reach.length > 0 && allIntegers(reach) && allIntegers(axis.range ?? []);
3181
+ const resolution = fmt.decimals !== void 0 ? 10 ** -fmt.decimals / (fmt.percent ? 100 : 1) : countAxis ? 1 : 0;
3158
3182
  const anyHighlight = data.some((d) => d.highlight);
3159
3183
  const colour = (d) => d.tone ? TONE[d.tone] : !anyHighlight || d.highlight ? "var(--color-accent)" : "var(--color-fg-faint)";
3160
- const shown = (d) => d.value === null || d.value === void 0 || !Number.isFinite(d.value) ? NOT_AVAILABLE[lang] : formatNumber(d.value, lang, fmt);
3184
+ const outline = (d) => d.tone && d.highlight ? { stroke: "var(--color-fg)", strokeWidth: 2 } : {};
3185
+ const fmtOf = (d) => d.format ?? fmt;
3186
+ const shown = (d) => d.value === null || d.value === void 0 || !Number.isFinite(d.value) ? NOT_AVAILABLE[lang] : formatNumber(d.value, lang, fmtOf(d));
3161
3187
  const width = box.width;
3188
+ const ids = useId();
3189
+ const tableId = `${ids}-table`;
3190
+ const summaryId = `${ids}-summary`;
3191
+ const [win, setWin] = useState(null);
3192
+ const [cursor, setCursor] = useState(null);
3193
+ const [brush, setBrush] = useState(null);
3194
+ const bands = useMemo(() => bandsOf(data.map((d) => d.group?.id)), [data]);
3195
+ const nb = bands.length;
3196
+ const grouped = data.some((d) => d.group !== void 0 && d.group !== null);
3197
+ const most = Math.max(1, ...bands.map((b) => b.to - b.from));
3198
+ const bandOf = (i) => bands.findIndex((b) => i >= b.from && i < b.to);
3199
+ const bandLabel = (b) => pick(b.group !== void 0 ? data[b.from].group.label : data[b.from].label, lang);
3200
+ const [s0, e0] = windowRange(nb, win);
3201
+ const zoomed = windowZoomed(nb, win);
3202
+ const zoomIsOff = zoomOff !== void 0 && zoomOff !== null;
3203
+ const zoomable = !zoomIsOff && nb >= 3;
3204
+ const zoomWhy = zoomIsOff ? pick(zoomOff, lang) : "";
3205
+ const kbRef = useRef(false);
3206
+ const geoRef = useRef(null);
3207
+ const live = useRef({ win, n: nb, zoomable, cursor });
3208
+ live.current = { win, n: nb, zoomable, cursor };
3209
+ const idKey = data.map((d) => d.group ? `${d.id}${d.group.id}` : d.id).join("");
3210
+ useEffect(() => {
3211
+ setWin(null);
3212
+ setCursor(null);
3213
+ setBrush(null);
3214
+ }, [idKey]);
3215
+ const unusedTones = legend ? unusedLegendTones(data.map((d) => d.tone ?? (anyHighlight && !d.highlight ? "neutral" : "accent")), legend) : [];
3216
+ const unusedKey = unusedTones.join(",");
3217
+ useEffect(() => {
3218
+ let dev = false;
3219
+ try {
3220
+ dev = process.env.NODE_ENV !== "production";
3221
+ } catch {
3222
+ dev = false;
3223
+ }
3224
+ if (dev && unusedKey) console.warn(`BarChart "${name}": the legend names the tone(s) ${unusedKey}, which no bar uses`);
3225
+ }, [unusedKey, name]);
3162
3226
  const select = (id) => onSelect?.(id);
3163
3227
  const keyed = (id) => (e) => {
3164
3228
  if (e.key === "Enter" || e.key === " ") {
@@ -3167,53 +3231,103 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
3167
3231
  }
3168
3232
  };
3169
3233
  const interactive = (d) => onSelect ? { role: "button", tabIndex: 0, onClick: () => select(d.id), onKeyDown: keyed(d.id), style: { cursor: "pointer" } } : {};
3234
+ const hitArea = Boolean(onSelect) && (floating || grouped);
3170
3235
  let drawing = null;
3171
3236
  let drawnHeight = 0;
3172
- if (width > 0 && data.length > 0) {
3173
- const valueLabels = data.map(shown);
3237
+ let tickLabels = [];
3238
+ const valueLabels = data.map(shown);
3239
+ const endsText = data.map((d) => {
3240
+ const e = barExtent(d.value, d.span);
3241
+ if (!e || !e.given && !floating) return "";
3242
+ const [a, b] = [formatNumber(e.a, lang, fmtOf(d)), formatNumber(e.b, lang, fmtOf(d))];
3243
+ return lang === "es" ? `de ${a} a ${b}` : `from ${a} to ${b}`;
3244
+ });
3245
+ const memberName = (d, room) => {
3246
+ if (!d.group) return "";
3247
+ const full = pick(d.label, lang);
3248
+ for (let k = full.length; k >= 1; k--) {
3249
+ const t2 = full.slice(0, k).trimEnd();
3250
+ if (t2 && textWidth(t2, PX) <= room) return t2;
3251
+ }
3252
+ return "";
3253
+ };
3254
+ const fullName = (d) => d.group ? `${pick(d.group.label, lang)}, ${pick(d.label, lang)}` : pick(d.label, lang);
3255
+ if (width > 0 && n > 0) {
3256
+ const count = e0 - s0;
3174
3257
  const valueCol = values ? widestLabel(valueLabels, PX) + 8 : 0;
3258
+ const vis = bands.slice(s0, e0);
3259
+ const items = vis.flatMap((b, kb) => Array.from({ length: b.to - b.from }, (_, j) => ({ b, kb, j, i: b.from + j, c: b.to - b.from })));
3260
+ const slots = (c) => memberSlots(c, most);
3175
3261
  if (orientation === "horizontal") {
3176
- const labelCol = Math.min(widestLabel(data.map((d) => pick(d.label, lang)), PX), Math.max(80, 0.38 * width));
3177
- const fitted = data.map((d) => fitLabel(pick(d.label, lang), labelCol, PX, 2));
3178
- const need = Math.max(22, Math.max(...fitted.map((f) => f.lines.length)) * LINE + 8);
3179
- const axisH = 6 + LINE + 4 + LINE + 4;
3180
- const x0 = labelCol + 10;
3181
- const x1 = Math.max(x0 + 40, width - valueCol - 6);
3262
+ const bandTexts = bands.map(bandLabel);
3263
+ const labelCol = Math.min(widestLabel(bandTexts, PX), Math.max(80, 0.38 * width));
3264
+ const fittedAll = bandTexts.map((t2) => fitLabel(t2, labelCol, PX, 2));
3265
+ const fitted = fittedAll.slice(s0, e0);
3266
+ const need = Math.max(22, Math.max(...fittedAll.map((f) => f.lines.length)) * LINE + 8, grouped ? most * 18 / (1 - 2 * GROUP_MARGIN) : 0);
3267
+ const memberRoom = grouped ? Math.min(widestLabel(data.map((d) => d.group ? pick(d.label, lang) : ""), PX), Math.max(24, 0.2 * width)) : 0;
3268
+ const memberCol = memberRoom > 0 ? memberRoom + 8 : 0;
3269
+ const p0 = labelCol + 10 + memberCol;
3270
+ const p1 = Math.max(p0 + 40, width - valueCol - 6);
3271
+ const cx = p0 + (p1 - p0) / 2;
3272
+ const titleFits = axisTitleFits(axisTitle, 2 * Math.min(cx, width - cx) - 8, PX);
3273
+ const titleLines = titleFits ? [axisTitle] : wrapLines(axisTitle, width - 8, PX);
3274
+ const axisH = 6 + LINE + 4 + titleLines.length * LINE + 4;
3275
+ const rowFull = fill && box.height > 0 ? Math.max(need, (box.height - axisH - 4) / nb) : need;
3276
+ const rowH = nb * rowFull / count;
3277
+ const barH = grouped ? Math.min(rowH * slots(most).size * 0.8, 40) : Math.min(rowH * 0.5, 40);
3278
+ const inset = anyDot ? barH / 2 : 0;
3279
+ const x0 = p0 + inset;
3280
+ const x1 = Math.max(x0 + 40, p1 - inset);
3182
3281
  const tickProbe = widestLabel(formatTicks([lo, hi], lang, fmt), PX);
3183
- const ticks = niceTicks(lo, hi, x1 - x0, tickProbe);
3282
+ const ticks = niceTicks(lo, hi, x1 - x0, tickProbe, 6, resolution);
3184
3283
  const t0 = ticks[0];
3185
3284
  const t1 = ticks[ticks.length - 1];
3186
- const tickLabels = formatTicks(ticks, lang, fmt);
3285
+ tickLabels = formatTicks(ticks, lang, fmt);
3187
3286
  const sx = (v) => x0 + (v - t0) / (t1 - t0 || 1) * (x1 - x0);
3188
- const rowH = fill && box.height > 0 ? Math.max(need, (box.height - axisH - 4) / data.length) : need;
3189
3287
  const top = 4;
3190
- const plotBottom = top + data.length * rowH;
3288
+ const plotBottom = top + nb * rowFull;
3191
3289
  drawnHeight = fill && box.height > 0 ? Math.max(box.height, plotBottom + axisH) : plotBottom + axisH;
3192
- const barH = Math.min(rowH * 0.5, 40);
3193
- drawing = /* @__PURE__ */ jsxs("svg", { width, height: drawnHeight, role: "img", "aria-label": name, "data-chart": "", "data-chart-kind": "bars", children: [
3290
+ const slotH = grouped ? rowH * slots(1).size : rowH;
3291
+ const centre = (it) => top + it.kb * rowH + (grouped ? rowH * (slots(it.c).start + (it.j + 0.5) * slots(it.c).size) : rowH / 2);
3292
+ geoRef.current = { horizontal: true, start: top, end: plotBottom };
3293
+ const cb = cursor !== null ? bandOf(cursor) : -1;
3294
+ drawing = /* @__PURE__ */ jsxs("svg", { width, height: drawnHeight, role: "presentation", "data-chart": "", "data-chart-kind": "bars", children: [
3194
3295
  /* @__PURE__ */ jsx("title", { children: name }),
3195
3296
  ticks.map((t2, i) => /* @__PURE__ */ jsxs("g", { children: [
3196
3297
  /* @__PURE__ */ jsx("line", { x1: sx(t2), x2: sx(t2), y1: top, y2: plotBottom, stroke: "var(--color-border)", strokeWidth: t2 === 0 ? 1.4 : 1 }),
3197
3298
  /* @__PURE__ */ jsx("text", { x: sx(t2), y: plotBottom + 6 + LINE - 2, textAnchor: i === 0 && sx(t2) - textWidth(tickLabels[i], PX) / 2 < 0 ? "start" : i === ticks.length - 1 && sx(t2) + textWidth(tickLabels[i], PX) / 2 > width ? "end" : "middle", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: tickLabels[i] })
3198
3299
  ] }, `t${i}`)),
3199
- /* @__PURE__ */ jsx("text", { x: x0 + (x1 - x0) / 2, y: plotBottom + 6 + LINE + 4 + LINE - 2, textAnchor: "middle", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: axisTitle }),
3200
- data.map((d, i) => {
3201
- const cy = top + i * rowH + rowH / 2;
3202
- const f = fitted[i];
3300
+ /* @__PURE__ */ jsx(Lines, { x: titleFits ? cx : width / 2, y: plotBottom + 6 + LINE + 4 + LINE - 2, lines: titleLines, anchor: "middle", fill: "var(--color-fg-subtle)" }),
3301
+ cursor !== null && cb >= s0 && cb < e0 && (grouped ? /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: 0, y: top + (cb - s0) * rowH + rowH * (slots(bands[cb].to - bands[cb].from).start + (cursor - bands[cb].from) * slots(1).size), width, height: rowH * slots(1).size }) : /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: 0, y: top + (cursor - s0) * rowH, width, height: rowH })),
3302
+ vis.map(
3303
+ (b, kb) => b.group !== void 0 ? /* @__PURE__ */ jsxs("g", { "data-group": b.group, children: [
3304
+ /* @__PURE__ */ jsx("title", { children: bandTexts[s0 + kb] }),
3305
+ /* @__PURE__ */ jsx(Lines, { x: labelCol + 2, y: top + kb * rowH + rowH / 2 - (fitted[kb].lines.length - 1) * LINE / 2 + 4, lines: fitted[kb].lines, anchor: "end", fill: "var(--color-fg)" })
3306
+ ] }, `grp${s0 + kb}`) : null
3307
+ ),
3308
+ items.map((it) => {
3309
+ const { i, kb } = it;
3310
+ const d = data[i];
3311
+ const cy = centre(it);
3312
+ const f = fitted[kb];
3203
3313
  const v = d.value;
3204
3314
  const ok = v !== null && v !== void 0 && Number.isFinite(v);
3205
- const xa = sx(0);
3206
- const xb = ok ? sx(v) : xa;
3315
+ const ex = extents[i];
3316
+ const xa = ex ? sx(ex.a) : sx(0);
3317
+ const xb = ex ? sx(ex.b) : xa;
3207
3318
  const labelText = valueLabels[i];
3208
- const negative = ok && v < 0;
3209
- return /* @__PURE__ */ jsxs("g", { "data-bar": d.id, ...interactive(d), children: [
3210
- /* @__PURE__ */ jsx("title", { children: [pick(d.label, lang), labelText, pick(d.hint, lang)].filter(Boolean).join(": ") }),
3211
- /* @__PURE__ */ jsx(Lines, { x: x0 - 8, y: cy - (f.lines.length - 1) * LINE / 2 + 4, lines: f.lines, anchor: "end", fill: d.highlight ? "var(--color-fg)" : "var(--color-fg-subtle)" }),
3212
- ok && /* @__PURE__ */ jsx("rect", { x: Math.min(xa, xb), y: cy - barH / 2, width: Math.max(1, Math.abs(xb - xa)), height: barH, rx: 2, fill: colour(d) }),
3319
+ const negative = ok && !ex?.given && v < 0;
3320
+ const far = Math.max(xa, xb);
3321
+ const member = memberName(d, memberRoom);
3322
+ return /* @__PURE__ */ jsxs("g", { "data-bar": d.id, "data-cursor": cursor === i ? "1" : void 0, ...interactive(d), children: [
3323
+ /* @__PURE__ */ jsx("title", { children: [fullName(d), labelText, pick(d.hint, lang), endsText[i]].filter(Boolean).join(": ") }),
3324
+ !d.group && /* @__PURE__ */ jsx(Lines, { x: labelCol + 2, y: cy - (f.lines.length - 1) * LINE / 2 + 4, lines: f.lines, anchor: "end", fill: d.highlight ? "var(--color-fg)" : "var(--color-fg-subtle)" }),
3325
+ ex && ex.dot && /* @__PURE__ */ jsx("circle", { cx: xa, cy, r: barH / 2, fill: colour(d), ...outline(d) }),
3326
+ ex && !ex.dot && /* @__PURE__ */ jsx("rect", { x: Math.min(xa, xb), y: cy - barH / 2, width: Math.max(1, Math.abs(xb - xa)), height: barH, rx: 2, fill: colour(d), ...outline(d) }),
3213
3327
  values && /* @__PURE__ */ jsx(
3214
3328
  "text",
3215
3329
  {
3216
- x: ok ? negative ? Math.max(x0 + textWidth(labelText, PX) + 2, xb - 4) : xb + 4 : xa + 4,
3330
+ x: ok ? negative ? Math.max(x0 + textWidth(labelText, PX) + 2, xb - 4) : (ex?.given ? far : xb) + 4 + (ex?.dot ? barH / 2 : 0) : xa + 4,
3217
3331
  y: cy + 4,
3218
3332
  textAnchor: negative ? "end" : "start",
3219
3333
  fontSize: PX,
@@ -3222,63 +3336,382 @@ function BarChart({ title, data, axis, orientation = "horizontal", values = true
3222
3336
  style: { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" },
3223
3337
  children: labelText
3224
3338
  }
3225
- )
3339
+ ),
3340
+ member && /* @__PURE__ */ jsx("text", { "data-member": "", x: p0 - 8, y: cy + 4, textAnchor: "end", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: member }),
3341
+ hitArea && /* @__PURE__ */ jsx("rect", { "data-bar-hit": "", x: 0, y: cy - slotH / 2, width, height: slotH, fill: "transparent" })
3226
3342
  ] }, d.id);
3227
- })
3343
+ }),
3344
+ brush && /* @__PURE__ */ jsx("rect", { className: "caos-bars-brush", "data-bar-brush": "", x: 0, y: Math.min(brush.a, brush.b), width, height: Math.abs(brush.b - brush.a) })
3228
3345
  ] });
3229
3346
  } else {
3230
3347
  const h = fill && box.height > 0 ? box.height : typeof height === "number" ? height : 240;
3231
- const titleRow = LINE + 6;
3348
+ const titleLines = wrapLines(axisTitle, width - 4, PX);
3349
+ const titleRow = titleLines.length * LINE + 6;
3232
3350
  const valueRow = values ? LINE + 4 : 4;
3233
3351
  const probe = widestLabel(formatTicks([lo, hi], lang, fmt), PX);
3234
3352
  const left = probe + 12;
3235
3353
  const right = 8;
3236
- const band = (width - left - right) / data.length;
3237
- const fitted = data.map((d) => fitLabel(pick(d.label, lang), Math.max(24, band - 6), PX, 2));
3238
- const bottom = Math.max(...fitted.map((f) => f.lines.length)) * LINE + 10;
3239
- const y0 = titleRow + valueRow;
3240
- const y1 = Math.max(y0 + 40, h - bottom);
3241
- const ticks = niceTicks(lo, hi, y1 - y0, LINE);
3354
+ const band = Math.max(0, (width - left - right) / count);
3355
+ const fitted = vis.map((b) => fitLabel(bandLabel(b), Math.max(24, band - 6), PX, 2));
3356
+ const slotW = band * slots(most).size;
3357
+ const barW = grouped ? Math.max(0, Math.min(slotW * 0.84, 56)) : Math.max(0, Math.min(band * 0.62, 56));
3358
+ const memberRow = grouped && data.some((d) => memberName(d, slotW - 2) !== "") ? LINE + 3 : 0;
3359
+ const bottom = Math.max(...fitted.map((f) => f.lines.length)) * LINE + 10 + memberRow;
3360
+ const inset = anyDot ? barW / 2 : 0;
3361
+ const y0 = titleRow + valueRow + inset;
3362
+ const y1 = Math.max(y0 + 40, h - bottom - inset);
3363
+ const yl = y1 + inset;
3364
+ const ticks = niceTicks(lo, hi, y1 - y0, LINE, 6, resolution);
3242
3365
  const t0 = ticks[0];
3243
3366
  const t1 = ticks[ticks.length - 1];
3244
- const tickLabels = formatTicks(ticks, lang, fmt);
3367
+ tickLabels = formatTicks(ticks, lang, fmt);
3245
3368
  const sy = (v) => y1 - (v - t0) / (t1 - t0 || 1) * (y1 - y0);
3246
- const barW = Math.min(band * 0.62, 56);
3369
+ const crowded = grouped && values && widestLabel(valueLabels, PX) > slotW - 2;
3370
+ const centre = (it) => left + band * it.kb + (grouped ? band * (slots(it.c).start + (it.j + 0.5) * slots(it.c).size) : band / 2);
3247
3371
  drawnHeight = h;
3248
- drawing = /* @__PURE__ */ jsxs("svg", { width, height: h, role: "img", "aria-label": name, "data-chart": "", "data-chart-kind": "bars", children: [
3372
+ geoRef.current = { horizontal: false, start: left, end: width - right };
3373
+ const cb = cursor !== null ? bandOf(cursor) : -1;
3374
+ drawing = /* @__PURE__ */ jsxs("svg", { width, height: h, role: "presentation", "data-chart": "", "data-chart-kind": "bars", children: [
3249
3375
  /* @__PURE__ */ jsx("title", { children: name }),
3250
- /* @__PURE__ */ jsx("text", { x: 0, y: LINE - 2, textAnchor: "start", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: axisTitle }),
3376
+ /* @__PURE__ */ jsx(Lines, { x: 0, y: LINE - 2, lines: titleLines, anchor: "start", fill: "var(--color-fg-subtle)" }),
3251
3377
  ticks.map((t2, i) => /* @__PURE__ */ jsxs("g", { children: [
3252
3378
  /* @__PURE__ */ jsx("line", { x1: left, x2: width - right, y1: sy(t2), y2: sy(t2), stroke: "var(--color-border)", strokeWidth: t2 === 0 ? 1.4 : 1 }),
3253
3379
  /* @__PURE__ */ jsx("text", { x: left - 6, y: sy(t2) + 4, textAnchor: "end", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: tickLabels[i] })
3254
3380
  ] }, `t${i}`)),
3255
- data.map((d, i) => {
3256
- const cx = left + band * i + band / 2;
3381
+ cursor !== null && cb >= s0 && cb < e0 && (grouped ? /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: left + band * (cb - s0) + band * (slots(bands[cb].to - bands[cb].from).start + (cursor - bands[cb].from) * slots(1).size), y: y0, width: band * slots(1).size, height: h - y0 }) : /* @__PURE__ */ jsx("rect", { className: "caos-bars-cursor", "data-bar-cursor": "", x: left + band * (cursor - s0), y: y0, width: band, height: h - y0 })),
3382
+ vis.map(
3383
+ (b, kb) => b.group !== void 0 ? /* @__PURE__ */ jsxs("g", { "data-group": b.group, children: [
3384
+ /* @__PURE__ */ jsx("title", { children: bandLabel(b) }),
3385
+ /* @__PURE__ */ jsx(Lines, { x: left + band * kb + band / 2, y: yl + LINE + 2 + memberRow, lines: fitted[kb].lines, anchor: "middle", fill: "var(--color-fg)" })
3386
+ ] }, `grp${s0 + kb}`) : null
3387
+ ),
3388
+ items.map((it) => {
3389
+ const { i, kb } = it;
3390
+ const d = data[i];
3391
+ const cx = centre(it);
3257
3392
  const v = d.value;
3258
3393
  const ok = v !== null && v !== void 0 && Number.isFinite(v);
3259
- const ya = sy(0);
3260
- const yb = ok ? sy(v) : ya;
3261
- const f = fitted[i];
3262
- return /* @__PURE__ */ jsxs("g", { "data-bar": d.id, ...interactive(d), children: [
3263
- /* @__PURE__ */ jsx("title", { children: [pick(d.label, lang), valueLabels[i], pick(d.hint, lang)].filter(Boolean).join(": ") }),
3264
- ok && /* @__PURE__ */ jsx("rect", { x: cx - barW / 2, y: Math.min(ya, yb), width: barW, height: Math.max(1, Math.abs(yb - ya)), rx: 2, fill: colour(d) }),
3265
- values && /* @__PURE__ */ jsx("text", { x: cx, y: Math.min(ya, yb) - 5, textAnchor: "middle", fontSize: PX, fill: ok ? "var(--color-fg)" : "var(--color-fg-faint)", fontStyle: ok ? void 0 : "italic", style: { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" }, children: valueLabels[i] }),
3266
- /* @__PURE__ */ jsx(Lines, { x: cx, y: y1 + LINE + 2, lines: f.lines, anchor: "middle", fill: d.highlight ? "var(--color-fg)" : "var(--color-fg-subtle)" })
3394
+ const ex = extents[i];
3395
+ const ya = ex ? sy(ex.a) : sy(0);
3396
+ const yb = ex ? sy(ex.b) : ya;
3397
+ const f = fitted[kb];
3398
+ const member = memberName(d, slotW - 2);
3399
+ return /* @__PURE__ */ jsxs("g", { "data-bar": d.id, "data-cursor": cursor === i ? "1" : void 0, ...interactive(d), children: [
3400
+ /* @__PURE__ */ jsx("title", { children: [fullName(d), valueLabels[i], pick(d.hint, lang), endsText[i]].filter(Boolean).join(": ") }),
3401
+ ex && ex.dot && /* @__PURE__ */ jsx("circle", { cx, cy: ya, r: barW / 2, fill: colour(d), ...outline(d) }),
3402
+ ex && !ex.dot && /* @__PURE__ */ jsx("rect", { x: cx - barW / 2, y: Math.min(ya, yb), width: barW, height: Math.max(1, Math.abs(yb - ya)), rx: 2, fill: colour(d), ...outline(d) }),
3403
+ values && /* @__PURE__ */ jsx("text", { x: cx, y: Math.min(ya, yb) - 5 - (ex?.dot ? barW / 2 : 0) - (crowded && it.j % 2 === 1 ? LINE : 0), textAnchor: "middle", fontSize: PX, fill: ok ? "var(--color-fg)" : "var(--color-fg-faint)", fontStyle: ok ? void 0 : "italic", style: { fontFamily: "var(--font-sans)", fontVariantNumeric: "tabular-nums" }, children: valueLabels[i] }),
3404
+ !d.group && /* @__PURE__ */ jsx(Lines, { x: cx, y: yl + LINE + 2, lines: f.lines, anchor: "middle", fill: d.highlight ? "var(--color-fg)" : "var(--color-fg-subtle)" }),
3405
+ member && /* @__PURE__ */ jsx("text", { "data-member": "", x: cx, y: yl + LINE + 2, textAnchor: "middle", fontSize: PX, fill: "var(--color-fg-subtle)", style: { fontFamily: "var(--font-sans)" }, children: member }),
3406
+ hitArea && /* @__PURE__ */ jsx("rect", { "data-bar-hit": "", x: cx - (grouped ? slotW : band) / 2, y: 0, width: grouped ? slotW : band, height: h, fill: "transparent" })
3267
3407
  ] }, d.id);
3268
- })
3408
+ }),
3409
+ brush && /* @__PURE__ */ jsx("rect", { className: "caos-bars-brush", "data-bar-brush": "", x: Math.min(brush.a, brush.b), y: y0, width: Math.abs(brush.b - brush.a), height: h - y0 })
3269
3410
  ] });
3270
3411
  }
3271
3412
  }
3272
- return /* @__PURE__ */ jsx(
3413
+ const drawn = width > 0 && n > 0;
3414
+ const repeats = repeatedLabels(tickLabels);
3415
+ const a11y = useMemo(() => {
3416
+ if (!drawn) return null;
3417
+ const head = [
3418
+ ...grouped ? [lang === "es" ? "Grupo" : "Group"] : [],
3419
+ lang === "es" ? "Categor\xEDa" : "Category",
3420
+ unit ? `${pick(axis.label, lang)} (${unit})` : pick(axis.label, lang),
3421
+ ...floating ? lang === "es" ? ["Desde", "Hasta"] : ["From", "To"] : [],
3422
+ lang === "es" ? "Nota" : "Note"
3423
+ ];
3424
+ const rows = data.map((d, i) => {
3425
+ const e = barExtent(d.value, d.span);
3426
+ const end = (v) => e && v !== void 0 ? formatNumber(v, lang, fmtOf(d)) : "";
3427
+ return [...grouped ? [d.group ? pick(d.group.label, lang) : ""] : [], pick(d.label, lang), valueLabels[i], ...floating ? [end(e?.a), end(e?.b)] : [], pick(d.hint, lang)];
3428
+ });
3429
+ let low = -1;
3430
+ let high = -1;
3431
+ data.forEach((d, i) => {
3432
+ if (d.value === null || d.value === void 0 || !Number.isFinite(d.value)) return;
3433
+ if (low < 0 || d.value < data[low].value) low = i;
3434
+ if (high < 0 || d.value > data[high].value) high = i;
3435
+ });
3436
+ const withUnit = (i) => `${valueLabels[i]}${unit ? `${NBSP}${unit}` : ""}`;
3437
+ const drawnExtents = data.map((d) => barExtent(d.value, d.span));
3438
+ const firstAt = drawnExtents.findIndex((e) => e !== null);
3439
+ const lastAt = drawnExtents.length - 1 - [...drawnExtents].reverse().findIndex((e) => e !== null);
3440
+ const endOf = (i, v) => ({ text: `${formatNumber(v, lang, fmtOf(data[i]))}${unit ? `${NBSP}${unit}` : ""}`, at: fullName(data[i]) });
3441
+ const ends = floating && firstAt >= 0 ? { first: endOf(firstAt, drawnExtents[firstAt].a), last: endOf(lastAt, drawnExtents[lastAt].b) } : {};
3442
+ const summary = chartSummary(lang, {
3443
+ ...ends,
3444
+ kind: "bars",
3445
+ name,
3446
+ count: n,
3447
+ yLabel: axisTitle,
3448
+ low: low >= 0 ? { text: withUnit(low), at: fullName(data[low]) } : void 0,
3449
+ high: high >= 0 ? { text: withUnit(high), at: fullName(data[high]) } : void 0
3450
+ });
3451
+ return { head, rows, summary };
3452
+ }, [drawn, data, lang, unit, name, axisTitle, axis.label, floating, grouped]);
3453
+ const rest = lang === "es" ? REST.es : REST.en;
3454
+ const read = cursor !== null && cursor < n ? [`${fullName(data[cursor])}: ${valueLabels[cursor]}${data[cursor].value === null || data[cursor].value === void 0 || !unit ? "" : `${NBSP}${unit}`}`, pick(data[cursor].hint, lang), endsText[cursor]].filter(Boolean).join(" \xB7 ") : rest;
3455
+ const along = (e) => {
3456
+ const g = geoRef.current;
3457
+ const svg = innerRef.current?.querySelector("svg");
3458
+ if (!g || !svg) return null;
3459
+ const r = svg.getBoundingClientRect();
3460
+ return g.horizontal ? e.clientY - r.top : e.clientX - r.left;
3461
+ };
3462
+ const setWindow = (r) => {
3463
+ if (!r || r[0] <= 0 && r[1] >= nb) setWin(null);
3464
+ else setWin(roundWindow(r, nb));
3465
+ };
3466
+ const fine = useRef(null);
3467
+ const zoomAround = (factor, anchorBar, discrete = false) => {
3468
+ const cur = windowRange(nb, live.current.win);
3469
+ if (discrete) {
3470
+ fine.current = null;
3471
+ const next2 = stepWindow(cur, nb, anchorBar, factor);
3472
+ setWindow(next2);
3473
+ return next2[0] !== cur[0] || next2[1] !== cur[1];
3474
+ }
3475
+ const held = fine.current;
3476
+ const base = held && held[0] > cur[0] - 1 && held[1] < cur[1] + 1 && roundWindow(held, nb)[0] === cur[0] && roundWindow(held, nb)[1] === cur[1] ? held : cur;
3477
+ const next = zoomRange(base, [0, nb], anchorBar, factor, { minSpan: Math.min(2, nb) });
3478
+ fine.current = next;
3479
+ setWindow(next);
3480
+ return Math.abs(next[0] - base[0]) > 1e-9 || Math.abs(next[1] - base[1]) > 1e-9;
3481
+ };
3482
+ const barAt = (pos) => {
3483
+ const g = geoRef.current;
3484
+ if (pos === null || !g) return null;
3485
+ const win2 = windowRange(nb, live.current.win);
3486
+ const bi = categoryAt(pos, g.start, g.end, win2, nb);
3487
+ if (bi === null) return null;
3488
+ const b = bands[bi];
3489
+ if (b.to - b.from === 1) return b.from;
3490
+ const scaled = (pos - g.start) / (g.end - g.start || 1) * (win2[1] - win2[0]);
3491
+ return b.from + memberAt(scaled - Math.floor(scaled), b.to - b.from, most);
3492
+ };
3493
+ const barFloat = (pos) => {
3494
+ const g = geoRef.current;
3495
+ const [s, e] = windowRange(nb, live.current.win);
3496
+ return g ? s + (pos - g.start) / (g.end - g.start || 1) * (e - s) : s;
3497
+ };
3498
+ const wheelOn = drawn && zoomable;
3499
+ useEffect(() => {
3500
+ const el = innerRef.current;
3501
+ if (!el || !wheelOn) return;
3502
+ const onWheel = (e) => {
3503
+ if (!live.current.zoomable) return;
3504
+ if (!el.contains(document.activeElement)) return;
3505
+ const pos = along(e);
3506
+ if (pos === null) return;
3507
+ if (zoomAround(wheelFactor(e.deltaY, e.deltaMode), barFloat(pos))) e.preventDefault();
3508
+ };
3509
+ el.addEventListener("wheel", onWheel, { passive: false });
3510
+ return () => el.removeEventListener("wheel", onWheel);
3511
+ }, [wheelOn, nb]);
3512
+ const pointers = useRef(/* @__PURE__ */ new Map());
3513
+ const gesture = useRef({ kind: null, id: -1, start: 0, last: 0, moved: false, f: [0, 0], dist: 0 });
3514
+ const onPointerDown = (e) => {
3515
+ e.currentTarget.focus({ preventScroll: true });
3516
+ kbRef.current = false;
3517
+ if (!live.current.zoomable) return;
3518
+ pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
3519
+ const pos = along(e);
3520
+ const g = gesture.current;
3521
+ if (e.pointerType === "mouse") {
3522
+ if (e.button !== 0 || pos === null) return;
3523
+ Object.assign(g, { kind: e.shiftKey ? "pan" : "brush", id: e.pointerId, start: pos, last: pos, moved: false, f: windowRange(nb, live.current.win) });
3524
+ return;
3525
+ }
3526
+ if (pointers.current.size === 2) {
3527
+ const [a, b] = [...pointers.current.values()];
3528
+ Object.assign(g, { kind: "pinch", dist: pinchState(a, b).dist });
3529
+ } else if (pointers.current.size === 1 && windowZoomed(nb, live.current.win) && pos !== null) {
3530
+ Object.assign(g, { kind: "pan", id: e.pointerId, start: pos, last: pos, moved: false, f: windowRange(nb, live.current.win) });
3531
+ }
3532
+ };
3533
+ const onPointerMove = (e) => {
3534
+ const g = gesture.current;
3535
+ const pos = along(e);
3536
+ if (pointers.current.has(e.pointerId)) pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
3537
+ if (g.kind === "pinch" && pointers.current.size === 2) {
3538
+ const [a, b] = [...pointers.current.values()];
3539
+ const now = pinchState(a, b);
3540
+ const mid = along({ clientX: now.mx, clientY: now.my });
3541
+ if (g.dist > 0 && now.dist > 0 && mid !== null) {
3542
+ zoomAround(g.dist / now.dist, barFloat(mid));
3543
+ g.dist = now.dist;
3544
+ }
3545
+ return;
3546
+ }
3547
+ if (g.kind && g.id === e.pointerId && pos !== null) {
3548
+ const gap = geoRef.current ? geoRef.current.end - geoRef.current.start : 1;
3549
+ if (!g.moved && Math.abs(pos - g.start) < DRAG_THRESHOLD) return;
3550
+ if (!g.moved) {
3551
+ g.moved = true;
3552
+ try {
3553
+ e.currentTarget.setPointerCapture(e.pointerId);
3554
+ } catch {
3555
+ }
3556
+ }
3557
+ if (g.kind === "brush") setBrush({ a: g.start, b: pos });
3558
+ else {
3559
+ const [s, en] = windowRange(nb, live.current.win);
3560
+ const bars = (g.last - pos) / (gap || 1) * (en - s);
3561
+ g.last = pos;
3562
+ g.f = panRange(g.f, [0, nb], bars / (g.f[1] - g.f[0] || 1));
3563
+ setWindow(g.f);
3564
+ }
3565
+ return;
3566
+ }
3567
+ if (e.pointerType === "mouse" || e.pointerType === "pen") {
3568
+ const bar = barAt(pos);
3569
+ kbRef.current = false;
3570
+ setCursor(bar);
3571
+ }
3572
+ };
3573
+ const endPointer = (e) => {
3574
+ const g = gesture.current;
3575
+ pointers.current.delete(e.pointerId);
3576
+ if (e.pointerType === "touch" && e.type === "pointerup" && !g.moved && g.kind !== "pinch") {
3577
+ const bar = barAt(along(e));
3578
+ if (bar !== null) {
3579
+ kbRef.current = false;
3580
+ setCursor(bar);
3581
+ }
3582
+ }
3583
+ if (g.kind === "brush" && g.id === e.pointerId && g.moved) {
3584
+ const geo = geoRef.current;
3585
+ const pos = along(e);
3586
+ if (geo && pos !== null) {
3587
+ const w = brushWindow(g.start, pos, geo.start, geo.end, windowRange(nb, live.current.win), nb);
3588
+ if (w) setWindow(w);
3589
+ }
3590
+ }
3591
+ if (e.currentTarget.hasPointerCapture?.(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
3592
+ setBrush(null);
3593
+ if (g.kind !== "pinch" || pointers.current.size < 2) gesture.current = { ...g, kind: null, moved: false };
3594
+ };
3595
+ const onPointerLeave = (e) => {
3596
+ if (e.pointerType !== "touch" && !kbRef.current) setCursor(null);
3597
+ };
3598
+ const onKeyDown = (e) => {
3599
+ const onBar = e.target !== e.currentTarget && e.target instanceof Element && e.target.closest("[data-bar]") !== null;
3600
+ if (e.target !== e.currentTarget && !onBar || nb === 0) return;
3601
+ const act = keyAction(e.key, { shift: e.shiftKey, ctrl: e.ctrlKey, meta: e.metaKey, alt: e.altKey });
3602
+ if (!act || onBar && act === "activate") return;
3603
+ const [s, en] = windowRange(nb, win);
3604
+ const stand = cursor;
3605
+ switch (act) {
3606
+ case "prev":
3607
+ case "next":
3608
+ case "first":
3609
+ case "last": {
3610
+ const standBand = stand !== null ? bandOf(stand) : -1;
3611
+ const from = stand !== null && standBand >= s && standBand < en ? stand : null;
3612
+ let idx;
3613
+ if (from === null && (act === "next" || act === "prev") && windowZoomed(nb, win)) idx = act === "next" ? bands[s].from : bands[en - 1].to - 1;
3614
+ else idx = stepIndex(from, act, n);
3615
+ if (idx === null) return;
3616
+ kbRef.current = true;
3617
+ setCursor(idx);
3618
+ const into = bandOf(idx);
3619
+ if (into < s) setWindow([into, into + (en - s)]);
3620
+ else if (into >= en) setWindow([into + 1 - (en - s), into + 1]);
3621
+ break;
3622
+ }
3623
+ case "zoomIn":
3624
+ case "zoomOut":
3625
+ if (!zoomable) return;
3626
+ zoomAround(act === "zoomIn" ? KEY_ZOOM : 1 / KEY_ZOOM, stand !== null ? bandOf(stand) + 0.5 : (s + en) / 2, true);
3627
+ break;
3628
+ case "panPrev":
3629
+ case "panNext": {
3630
+ if (!zoomable || !windowZoomed(nb, win)) return;
3631
+ const bars = Math.max(1, Math.round((en - s) * KEY_PAN));
3632
+ setWindow(panRange([s, en], [0, nb], (act === "panNext" ? 1 : -1) * bars / (en - s)));
3633
+ break;
3634
+ }
3635
+ case "reset":
3636
+ if (e.key === "Escape" && !windowZoomed(nb, win)) return;
3637
+ setWin(null);
3638
+ e.stopPropagation();
3639
+ break;
3640
+ case "activate":
3641
+ if (stand === null || !onSelect) return;
3642
+ select(data[stand].id);
3643
+ break;
3644
+ }
3645
+ e.preventDefault();
3646
+ };
3647
+ const onBlur = (e) => {
3648
+ if (e.relatedTarget instanceof Node && e.currentTarget.contains(e.relatedTarget)) return;
3649
+ if (kbRef.current) {
3650
+ kbRef.current = false;
3651
+ setCursor(null);
3652
+ }
3653
+ };
3654
+ const resetZoom = () => {
3655
+ setWin(null);
3656
+ innerRef.current?.focus({ preventScroll: true });
3657
+ };
3658
+ return /* @__PURE__ */ jsxs(
3273
3659
  "div",
3274
3660
  {
3275
- ref,
3276
- className: fill ? "caos-bars fill" : "caos-bars",
3277
- style: !fill && orientation === "vertical" ? { height: typeof height === "number" ? height : 240 } : void 0,
3278
- "data-drawn": width > 0 ? "1" : "0",
3279
- "data-width": width,
3280
- "data-height": drawnHeight,
3281
- children: drawing
3661
+ className: fill ? "caos-bars-host fill" : "caos-bars-host",
3662
+ "data-chart-host": drawn ? "bars" : void 0,
3663
+ "data-points": drawn ? String(n) : void 0,
3664
+ "data-groups": drawn && grouped ? String(nb) : void 0,
3665
+ "data-legend": drawn && legend && legend.length > 0 ? String(legend.length) : void 0,
3666
+ "data-spans": drawn && floating ? String(data.filter((d) => d.span !== void 0 && d.span !== null).length) : void 0,
3667
+ "data-zoomable": drawn ? zoomable ? "1" : "0" : void 0,
3668
+ "data-zoomed": drawn && zoomed ? "1" : void 0,
3669
+ "data-zoom-off": drawn && zoomIsOff ? zoomWhy : void 0,
3670
+ "data-window": drawn ? `${s0},${e0}` : void 0,
3671
+ "data-orientation": drawn ? orientation : void 0,
3672
+ children: [
3673
+ /* @__PURE__ */ jsx(
3674
+ "div",
3675
+ {
3676
+ ref: setInner,
3677
+ className: fill ? "caos-bars fill" : "caos-bars",
3678
+ style: !fill && orientation === "vertical" ? { height: typeof height === "number" ? height : 240 } : void 0,
3679
+ "data-drawn": width > 0 ? "1" : "0",
3680
+ "data-width": width,
3681
+ "data-height": drawnHeight,
3682
+ "data-ticks-repeat": drawn ? String(repeats) : void 0,
3683
+ ...drawn ? {
3684
+ tabIndex: 0,
3685
+ role: "group",
3686
+ "aria-roledescription": CHART_ROLE[lang],
3687
+ "aria-label": name,
3688
+ "aria-describedby": summaryId,
3689
+ "aria-details": tableId,
3690
+ "data-chart-focus": "",
3691
+ "data-zoomed": zoomed ? "1" : void 0,
3692
+ onKeyDown,
3693
+ onBlur,
3694
+ onPointerDown,
3695
+ onPointerMove,
3696
+ onPointerUp: endPointer,
3697
+ onPointerCancel: endPointer,
3698
+ onPointerLeave,
3699
+ onDoubleClick: () => setWin(null)
3700
+ } : {},
3701
+ children: drawing
3702
+ }
3703
+ ),
3704
+ drawn && legend && legend.length > 0 && /* @__PURE__ */ jsx("ul", { className: "caos-bars-legend", "aria-label": lang === "es" ? "Leyenda" : "Legend", children: legend.map((l, k) => /* @__PURE__ */ jsxs("li", { className: "caos-bars-key", "data-tone": l.tone, children: [
3705
+ /* @__PURE__ */ jsx("span", { className: "caos-bars-swatch", style: { "--swatch": TONE[l.tone] }, "aria-hidden": "true" }),
3706
+ pick(l.label, lang)
3707
+ ] }, k)) }),
3708
+ drawn && a11y && /* @__PURE__ */ jsx(ChartSummary, { id: summaryId, text: a11y.summary }),
3709
+ drawn && a11y && /* @__PURE__ */ jsx(ChartDataTable, { id: tableId, caption: name, head: a11y.head, rows: a11y.rows }),
3710
+ drawn && /* @__PURE__ */ jsxs("div", { className: "caos-bars-foot", children: [
3711
+ /* @__PURE__ */ jsx("p", { className: "caos-bars-readout", "aria-live": "polite", title: read, children: read }),
3712
+ zoomed && zoomable && /* @__PURE__ */ jsx(ResetZoomButton, { lang, onReset: resetZoom })
3713
+ ] })
3714
+ ]
3282
3715
  }
3283
3716
  );
3284
3717
  }