twico-ui 1.9.0 → 1.11.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.mjs CHANGED
@@ -25,7 +25,7 @@ function useScopedStyles(id, css) {
25
25
  }
26
26
  if (el.textContent !== css) el.textContent = css;
27
27
  }, [id, css, nonce]);
28
- return SUPPORTS_STYLE_HOIST && css ? React51.createElement("style", { href: id, precedence: "twc-ui", nonce }, css) : null;
28
+ return SUPPORTS_STYLE_HOIST && css ? React51.createElement("style", { href: id, precedence: "twc-ui" }, css) : null;
29
29
  }
30
30
 
31
31
  // components/TwicoProvider.jsx
@@ -1287,6 +1287,62 @@ function compileSx(obj, selector) {
1287
1287
  out += rules;
1288
1288
  return out;
1289
1289
  }
1290
+ var BOX_EDGE4 = {
1291
+ padding: ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"],
1292
+ margin: ["marginTop", "marginRight", "marginBottom", "marginLeft"],
1293
+ inset: ["top", "right", "bottom", "left"]
1294
+ };
1295
+ var BOX_EDGE2 = {
1296
+ paddingBlock: ["paddingBlockStart", "paddingBlockEnd"],
1297
+ paddingInline: ["paddingInlineStart", "paddingInlineEnd"],
1298
+ marginBlock: ["marginBlockStart", "marginBlockEnd"],
1299
+ marginInline: ["marginInlineStart", "marginInlineEnd"]
1300
+ };
1301
+ function splitShorthand(value) {
1302
+ const s = String(value).trim();
1303
+ if (!s) return [];
1304
+ const out = [];
1305
+ let depth = 0, cur = "";
1306
+ for (let i = 0; i < s.length; i++) {
1307
+ const c = s[i];
1308
+ if (c === "(") {
1309
+ depth++;
1310
+ cur += c;
1311
+ } else if (c === ")") {
1312
+ depth = Math.max(0, depth - 1);
1313
+ cur += c;
1314
+ } else if (depth === 0 && (c === " " || c === " " || c === "\n" || c === "\r")) {
1315
+ if (cur) {
1316
+ out.push(cur);
1317
+ cur = "";
1318
+ }
1319
+ } else cur += c;
1320
+ }
1321
+ if (cur) out.push(cur);
1322
+ return out;
1323
+ }
1324
+ function expandBoxShorthand(key, value) {
1325
+ const four = BOX_EDGE4[key];
1326
+ const two = BOX_EDGE2[key];
1327
+ if (!four && !two || value == null) return null;
1328
+ if (typeof value === "number") return (four || two).map((edge) => [edge, value]);
1329
+ const p = splitShorthand(value);
1330
+ if (p.length === 0) return null;
1331
+ if (two) return [[two[0], p[0]], [two[1], p.length > 1 ? p[1] : p[0]]];
1332
+ let top, right, bottom, left;
1333
+ if (p.length === 1) top = right = bottom = left = p[0];
1334
+ else if (p.length === 2) {
1335
+ top = bottom = p[0];
1336
+ right = left = p[1];
1337
+ } else if (p.length === 3) {
1338
+ top = p[0];
1339
+ right = left = p[1];
1340
+ bottom = p[2];
1341
+ } else {
1342
+ [top, right, bottom, left] = p;
1343
+ }
1344
+ return [[four[0], top], [four[1], right], [four[2], bottom], [four[3], left]];
1345
+ }
1290
1346
  function buildSx(sx, uid) {
1291
1347
  if (!isObject(sx)) return { flatStyle: void 0, css: "" };
1292
1348
  const scope = `[data-twc-sx="${uid}"]`;
@@ -1304,7 +1360,13 @@ function buildSx(sx, uid) {
1304
1360
  css += compileSx(value, resolveSelector(key, scope));
1305
1361
  }
1306
1362
  } else {
1307
- (flatStyle || (flatStyle = {}))[key] = value;
1363
+ const expanded = expandBoxShorthand(key, value);
1364
+ if (expanded) {
1365
+ flatStyle || (flatStyle = {});
1366
+ for (const [k, v] of expanded) flatStyle[k] = v;
1367
+ } else {
1368
+ (flatStyle || (flatStyle = {}))[key] = value;
1369
+ }
1308
1370
  }
1309
1371
  }
1310
1372
  return { flatStyle, css };
@@ -1654,14 +1716,15 @@ var CHART_BASE_CSS = `
1654
1716
  .twc-chart__leg[data-off="true"] { opacity: 0.4; }
1655
1717
  .twc-chart__leg-sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }
1656
1718
 
1657
- /* Floating tooltip \u2014 a styled card that follows the pointer (ApexCharts / MUI X style). */
1719
+ /* Floating tooltip \u2014 a styled card that follows the pointer (ApexCharts / MUI X style).
1720
+ Positioned inline by <ChartTooltip> (which flips below / clamps to the container so it
1721
+ never clips); only the opacity fades in here so it can't fight the inline transform. */
1658
1722
  .twc-chart__tip {
1659
- position: absolute; z-index: 3; top: 0; left: 0; pointer-events: none;
1660
- transform: translate(-50%, calc(-100% - 12px));
1661
- min-width: 96px; max-width: 260px; padding: 8px 10px;
1723
+ position: absolute; z-index: 3; top: 0; left: 0; pointer-events: none; will-change: transform;
1724
+ min-width: 88px; max-width: 240px; padding: 7px 9px;
1662
1725
  background: var(--color-surface-raised); color: var(--color-text);
1663
1726
  border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-md);
1664
- box-shadow: var(--shadow-lg); font-size: var(--text-xs); line-height: 1.45;
1727
+ box-shadow: var(--shadow-lg); font-size: var(--text-xs); line-height: 1.4;
1665
1728
  opacity: 0; animation: twc-chart-tip-in var(--duration-fast) var(--ease-standard) forwards;
1666
1729
  }
1667
1730
  .twc-chart__tip-title { font-weight: var(--font-bold); color: var(--color-text); margin-bottom: 4px; white-space: nowrap; }
@@ -1670,12 +1733,36 @@ var CHART_BASE_CSS = `
1670
1733
  .twc-chart__tip-sw { width: 9px; height: 9px; border-radius: 2px; flex: none; }
1671
1734
  .twc-chart__tip-label { color: var(--color-text-muted); flex: 1; overflow: hidden; text-overflow: ellipsis; }
1672
1735
  .twc-chart__tip-val { font-weight: var(--font-semibold); color: var(--color-text); font-variant-numeric: tabular-nums; }
1673
- @keyframes twc-chart-tip-in { from { opacity: 0; transform: translate(-50%, calc(-100% - 6px)); } to { opacity: 1; transform: translate(-50%, calc(-100% - 12px)); } }
1736
+ @keyframes twc-chart-tip-in { from { opacity: 0; } to { opacity: 1; } }
1674
1737
 
1675
1738
  /* Hover emphasis: when the chart marks itself hovered, fade every mark except the active one. */
1676
- .twc-chart[data-hovering="true"] [data-mark] { opacity: 0.28; transition: opacity var(--duration-fast) var(--ease-standard); }
1739
+ .twc-chart[data-hovering="true"] [data-mark] { opacity: 0.3; transition: opacity var(--duration-base) var(--ease-standard); }
1677
1740
  .twc-chart[data-hovering="true"] [data-mark][data-active="true"] { opacity: 1; }
1678
- [data-mark] { transition: opacity var(--duration-fast) var(--ease-standard); cursor: default; }
1741
+ [data-mark] { transition: opacity var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-spring); cursor: default; }
1742
+
1743
+ /* Click-to-select: a soft accent ring + glow on the chosen mark (not a heavy outline). */
1744
+ [data-mark][data-selected="true"] { stroke: var(--color-text); stroke-width: 1.5; stroke-opacity: 0.5; paint-order: stroke; filter: drop-shadow(0 0 3px color-mix(in srgb, var(--color-text) 22%, transparent)); }
1745
+ /* Click-to-focus: a persistent selection also dims the other marks (focus mode), on every chart. */
1746
+ .twc-chart[data-has-selection="true"] [data-mark]:not([data-selected="true"]) { opacity: 0.4; }
1747
+ .twc-chart[data-clickable="true"] [data-mark], .twc-chart__overlay[data-clickable="true"] { cursor: pointer; }
1748
+
1749
+ /* Crosshair + full-plot event overlay (cartesian shared-axis interactions). */
1750
+ .twc-chart__overlay, .twc-chart__zone { fill: transparent; }
1751
+ .twc-chart__crosshair { stroke: var(--color-border-strong); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.7; pointer-events: none; }
1752
+
1753
+ /* Drag-to-zoom affordances: a crosshair cursor over the zoom overlay + a bordered band. */
1754
+ .twc-chart__overlay[data-zoom="true"] { cursor: crosshair; }
1755
+ .twc-chart__overlay[data-zoom="true"][data-panning="true"] { cursor: grabbing; }
1756
+ .twc-chart__zoomband { fill: var(--color-primary); opacity: 0.14; stroke: var(--color-primary); stroke-opacity: 0.5; stroke-width: 1; pointer-events: none; }
1757
+ .twc-chart__zoom-reset {
1758
+ position: absolute; top: 6px; inset-inline-end: 8px; z-index: 2;
1759
+ display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px;
1760
+ font-family: var(--font-sans); font-size: var(--text-xs); color: var(--color-text-muted);
1761
+ background: var(--color-surface-raised); border: var(--border-thin) solid var(--color-border);
1762
+ border-radius: var(--radius-md); box-shadow: var(--shadow-sm); cursor: pointer;
1763
+ }
1764
+ .twc-chart__zoom-reset:hover { color: var(--color-text); border-color: var(--color-border-strong); }
1765
+ .twc-chart__zoom-reset svg { width: 12px; height: 12px; }
1679
1766
 
1680
1767
  /* Entrance animations (respect reduced motion, below). */
1681
1768
  @keyframes twc-chart-fade { from { opacity: 0; } to { opacity: 1; } }
@@ -1723,7 +1810,7 @@ function ChartTable({ id, caption, columns, rows }) {
1723
1810
  )
1724
1811
  );
1725
1812
  }
1726
- function ChartLegend({ items, onToggle, hidden }) {
1813
+ function ChartLegend({ items, onToggle, hidden, onFocus, onBlur }) {
1727
1814
  const h = React51__default.createElement;
1728
1815
  return h(
1729
1816
  "div",
@@ -1744,7 +1831,9 @@ function ChartLegend({ items, onToggle, hidden }) {
1744
1831
  e.preventDefault();
1745
1832
  onToggle(it, i);
1746
1833
  }
1747
- } : void 0
1834
+ } : void 0,
1835
+ onMouseEnter: onFocus ? () => onFocus(it, i) : void 0,
1836
+ onMouseLeave: onBlur ? () => onBlur() : void 0
1748
1837
  },
1749
1838
  h("span", { className: "twc-chart__leg-sw", style: { background: it.color } }),
1750
1839
  it.label
@@ -1767,11 +1856,31 @@ function useChartTooltip() {
1767
1856
  }
1768
1857
  function ChartTooltip({ tip }) {
1769
1858
  const h = React51__default.createElement;
1859
+ const ref = React51__default.useRef(null);
1860
+ const [pos, setPos] = React51__default.useState({ dx: 0, below: false });
1861
+ React51__default.useLayoutEffect(() => {
1862
+ const el = ref.current;
1863
+ if (!tip || !el) return;
1864
+ const parent = el.offsetParent;
1865
+ const pw = parent && parent.clientWidth ? parent.clientWidth : 0;
1866
+ const w = el.offsetWidth || 0, ht = el.offsetHeight || 0;
1867
+ let dx = 0;
1868
+ if (pw > 0 && w > 0) {
1869
+ const half = w / 2;
1870
+ if (tip.left - half < 6) dx = 6 - (tip.left - half);
1871
+ else if (tip.left + half > pw - 6) dx = pw - 6 - (tip.left + half);
1872
+ }
1873
+ setPos({ dx, below: ht > 0 && tip.top - ht - 16 < 0 });
1874
+ }, [tip]);
1770
1875
  if (!tip) return null;
1771
- const left = Math.max(48, tip.left);
1876
+ const style = {
1877
+ left: tip.left + pos.dx,
1878
+ top: tip.top,
1879
+ transform: pos.below ? "translate(-50%, 18px)" : "translate(-50%, calc(-100% - 14px))"
1880
+ };
1772
1881
  return h(
1773
1882
  "div",
1774
- { className: "twc-chart__tip", role: "tooltip", "aria-hidden": "true", style: { left, top: tip.top } },
1883
+ { ref, className: "twc-chart__tip", role: "tooltip", "aria-hidden": "true", style },
1775
1884
  tip.title != null && tip.title !== "" ? h("div", { className: "twc-chart__tip-title" }, tip.title) : null,
1776
1885
  (tip.items || []).map(
1777
1886
  (it, i) => h(
@@ -1787,7 +1896,6 @@ function ChartTooltip({ tip }) {
1787
1896
  var CHART_CSS = `
1788
1897
  .twc-chart__grid { stroke: var(--color-divider); stroke-width: 1; }
1789
1898
  .twc-chart__axisline { stroke: var(--color-border); stroke-width: 1; }
1790
- .twc-chart__tick { stroke: var(--color-border); stroke-width: 1; }
1791
1899
  .twc-chart__axis { fill: var(--color-text-subtle); font-size: 11px; }
1792
1900
  .twc-chart__bar { fill: var(--color-primary); }
1793
1901
  .twc-chart__bar:hover { filter: brightness(1.06); }
@@ -1796,7 +1904,6 @@ var CHART_CSS = `
1796
1904
  .twc-chart__dot { fill: var(--color-surface); stroke: var(--color-primary); stroke-width: 2.5; transition: r var(--duration-fast) var(--ease-standard); }
1797
1905
  .twc-chart__hit { fill: transparent; }
1798
1906
  .twc-chart__hit:hover + .twc-chart__dot, .twc-chart__dot:hover { r: 5; }
1799
- .twc-chart__vline { stroke: var(--color-border-strong); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.7; }
1800
1907
  .twc-chart__label { fill: var(--color-text-muted); font-size: 10px; font-weight: var(--font-semibold); }
1801
1908
  `;
1802
1909
  function Chart({
@@ -1813,9 +1920,12 @@ function Chart({
1813
1920
  showGrid = true,
1814
1921
  showAxis = true,
1815
1922
  showLegend = false,
1923
+ crosshair = true,
1924
+ zoomable = false,
1816
1925
  animate = true,
1817
1926
  colors,
1818
1927
  valueFormat,
1928
+ onDataClick,
1819
1929
  ariaLabel,
1820
1930
  "aria-label": ariaLabelProp,
1821
1931
  tableFallback = true,
@@ -1830,15 +1940,23 @@ function Chart({
1830
1940
  const tableId = tableFallback ? `${uid}-table` : void 0;
1831
1941
  const { containerRef, tip, show, hide } = useChartTooltip();
1832
1942
  const [hidden, setHidden] = React51__default.useState(() => /* @__PURE__ */ new Set());
1943
+ const [focusSeries, setFocusSeries] = React51__default.useState(null);
1944
+ const [selected, setSelected] = React51__default.useState(null);
1945
+ const [hoverIdx, setHoverIdx] = React51__default.useState(null);
1946
+ const [zoom, setZoom] = React51__default.useState(null);
1947
+ const [drag, setDrag] = React51__default.useState(null);
1948
+ const svgRef = React51__default.useRef(null);
1833
1949
  const isBar = type === "bar" || type === "column";
1834
1950
  const isArea = type === "area";
1835
1951
  const crv = smooth ? "smooth" : curve;
1836
1952
  const pathOpts = { smooth: crv === "smooth", step: crv === "stepped" ? "after" : null };
1837
1953
  const canStack = stacked && (isBar || isArea);
1838
- const anim = animate ? "" : null;
1954
+ const clickable = !!onDataClick;
1839
1955
  const allKeys = series && series.length ? series : ["value"];
1840
1956
  const keys = allKeys.filter((k) => !hidden.has(k));
1841
- const rows = data || [];
1957
+ const allRows = data || [];
1958
+ const baseIdx = zoom ? zoom.s : 0;
1959
+ const rows = zoom ? allRows.slice(zoom.s, zoom.e + 1) : allRows;
1842
1960
  const fmt = valueFormat || fmtNumber;
1843
1961
  const multi = allKeys.length > 1;
1844
1962
  const W = 600, H = height;
@@ -1856,43 +1974,137 @@ function Chart({
1856
1974
  const catBand = innerW / Math.max(1, rows.length);
1857
1975
  const catBandH = innerH / Math.max(1, rows.length);
1858
1976
  const lineX = (i) => padL + innerW / Math.max(1, rows.length - 1) * i;
1977
+ const catX = (i) => isBar ? padL + catBand * i + catBand / 2 : lineX(i);
1859
1978
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? `${type} chart`;
1860
- const tipFor = (d) => ({
1861
- title: d.label,
1862
- items: keys.map((k) => ({ color: colorFor(k), label: k, value: fmt(Number(d[k]) || 0) }))
1863
- });
1864
1979
  function colorFor(k) {
1865
1980
  const idx = allKeys.indexOf(k);
1866
1981
  return multi || colors && colors.length ? paletteAt(colors, idx) : "var(--color-primary)";
1867
1982
  }
1983
+ const tipFor = (d) => ({
1984
+ title: d.label,
1985
+ items: keys.map((k) => ({ color: colorFor(k), label: k, value: fmt(Number(d[k]) || 0) }))
1986
+ });
1987
+ const focusActive = (k) => focusSeries == null ? void 0 : k === focusSeries || void 0;
1988
+ const onMarkMove = (d, localIdx, e) => {
1989
+ show(tipFor(d), e);
1990
+ setHoverIdx(localIdx);
1991
+ };
1992
+ const onMarkLeave = () => {
1993
+ hide();
1994
+ setHoverIdx(null);
1995
+ };
1996
+ const clickDatum = (d, k, localIdx) => {
1997
+ if (!onDataClick) return;
1998
+ onDataClick({ label: d.label, series: k, seriesIndex: allKeys.indexOf(k), value: Number(d[k]) || 0, index: baseIdx + localIdx, row: d });
1999
+ };
2000
+ const selectMark = (key) => setSelected((s) => s === key ? null : key);
2001
+ const applyZoom = (s, e) => {
2002
+ const n = allRows.length;
2003
+ if (e - s >= n - 1) {
2004
+ setZoom(null);
2005
+ return;
2006
+ }
2007
+ setZoom({ s: Math.max(0, s), e: Math.min(n - 1, e) });
2008
+ };
2009
+ const catFromEvent = (e) => {
2010
+ const svg = svgRef.current;
2011
+ if (!svg) return null;
2012
+ const r2 = svg.getBoundingClientRect();
2013
+ if (!(r2.width > 0)) return null;
2014
+ const frac = Math.min(1, Math.max(0, ((e.clientX - r2.left) / r2.width * W - padL) / innerW));
2015
+ const idx = isBar ? Math.floor(frac * rows.length) : Math.round(frac * Math.max(1, rows.length - 1));
2016
+ return Math.min(rows.length - 1, Math.max(0, idx));
2017
+ };
2018
+ const onOverlayMove = (e) => {
2019
+ const idx = catFromEvent(e);
2020
+ if (idx == null) return;
2021
+ setHoverIdx(idx);
2022
+ show(tipFor(rows[idx]), e);
2023
+ if (drag) {
2024
+ if (drag.pan) {
2025
+ const span = drag.z0.e - drag.z0.s;
2026
+ const s = Math.min(allRows.length - 1 - span, Math.max(0, drag.z0.s + (drag.start - idx)));
2027
+ applyZoom(s, s + span);
2028
+ } else {
2029
+ setDrag({ ...drag, end: idx });
2030
+ }
2031
+ }
2032
+ };
2033
+ const onOverlayDown = (e) => {
2034
+ const idx = catFromEvent(e);
2035
+ if (idx == null) return;
2036
+ setDrag({ start: idx, end: idx, pan: e.shiftKey, z0: zoom || { s: 0, e: allRows.length - 1 } });
2037
+ };
2038
+ const onOverlayUp = () => {
2039
+ if (drag && !drag.pan) {
2040
+ const lo = Math.min(drag.start, drag.end), hi = Math.max(drag.start, drag.end);
2041
+ if (hi - lo >= 1) applyZoom(baseIdx + lo, baseIdx + hi);
2042
+ }
2043
+ setDrag(null);
2044
+ };
2045
+ const onOverlayClick = (e) => {
2046
+ if (!onDataClick) return;
2047
+ const idx = catFromEvent(e);
2048
+ if (idx == null) return;
2049
+ const d = rows[idx];
2050
+ onDataClick({ label: d.label, series: null, seriesIndex: -1, value: void 0, index: baseIdx + idx, row: d });
2051
+ selectMark(`${idx}:*`);
2052
+ };
2053
+ React51__default.useEffect(() => {
2054
+ if (!zoomable) return void 0;
2055
+ const svg = svgRef.current;
2056
+ if (!svg) return void 0;
2057
+ const onWheel = (e) => {
2058
+ e.preventDefault();
2059
+ const n = allRows.length;
2060
+ const cur = zoom || { s: 0, e: n - 1 };
2061
+ const span = cur.e - cur.s;
2062
+ const r2 = svg.getBoundingClientRect();
2063
+ const frac = r2.width > 0 ? Math.min(1, Math.max(0, ((e.clientX - r2.left) / r2.width * W - padL) / innerW)) : 0.5;
2064
+ const center = cur.s + Math.round(frac * span);
2065
+ const newSpan = Math.min(n - 1, Math.max(1, Math.round(span * (e.deltaY < 0 ? 0.8 : 1.25))));
2066
+ const s = Math.min(n - 1 - newSpan, Math.max(0, center - Math.round(newSpan * frac)));
2067
+ applyZoom(s, s + newSpan);
2068
+ };
2069
+ svg.addEventListener("wheel", onWheel, { passive: false });
2070
+ return () => svg.removeEventListener("wheel", onWheel);
2071
+ }, [zoomable, zoom, allRows.length]);
1868
2072
  const renderBars = () => {
1869
2073
  const band = horizontal ? catBandH : catBand;
1870
- const gap = 0.28;
1871
- const slot = band * (1 - gap);
2074
+ const slot = band * 0.72;
1872
2075
  return rows.map((d, gi) => {
1873
- const gStart = (horizontal ? padT : padL) + band * gi + band * gap / 2;
1874
- const hoverProps = {
1875
- onMouseMove: (e) => show(tipFor(d), e),
1876
- onMouseLeave: hide
1877
- };
2076
+ const gStart = (horizontal ? padT : padL) + band * gi + band * 0.28 / 2;
2077
+ const handlers = { onMouseMove: (e) => onMarkMove(d, gi, e), onMouseLeave: onMarkLeave };
2078
+ const barCommon = (k, key) => ({
2079
+ className: `twc-chart__bar ${animate ? "twc-chart__anim-bar" : ""}`.trim(),
2080
+ "data-mark": true,
2081
+ "data-horizontal": horizontal || void 0,
2082
+ "data-active": focusActive(k),
2083
+ "data-selected": selected === key || void 0,
2084
+ style: { fill: colorFor(k) },
2085
+ rx: 2,
2086
+ ...handlers,
2087
+ onClick: () => {
2088
+ clickDatum(d, k, gi);
2089
+ selectMark(key);
2090
+ }
2091
+ });
1878
2092
  if (canStack) {
1879
2093
  let acc = 0;
1880
- return keys.map((k, si) => {
2094
+ return keys.map((k) => {
1881
2095
  const v = Number(d[k]) || 0;
1882
2096
  const p0 = vPix(acc), p1 = vPix(acc + v);
1883
2097
  acc += v;
1884
- const color = colorFor(k);
1885
- const common = { className: `twc-chart__bar ${anim === "" ? "twc-chart__anim-bar" : ""}`.trim(), "data-horizontal": horizontal || void 0, style: { fill: color }, rx: 2, ...hoverProps };
1886
- return horizontal ? /* @__PURE__ */ jsx("rect", { ...common, x: padL + p0, y: gStart, width: Math.max(0, p1 - p0), height: Math.max(1, slot) }, `${gi}-${si}`) : /* @__PURE__ */ jsx("rect", { ...common, x: gStart, y: padT + innerH - p1, width: Math.max(1, slot), height: Math.max(0, p1 - p0) }, `${gi}-${si}`);
2098
+ const key = `${gi}:${k}`;
2099
+ return horizontal ? /* @__PURE__ */ jsx("rect", { ...barCommon(k, key), x: padL + p0, y: gStart, width: Math.max(0, p1 - p0), height: Math.max(1, slot) }, key) : /* @__PURE__ */ jsx("rect", { ...barCommon(k, key), x: gStart, y: padT + innerH - p1, width: Math.max(1, slot), height: Math.max(0, p1 - p0) }, key);
1887
2100
  });
1888
2101
  }
1889
2102
  const bw = slot / Math.max(1, keys.length);
1890
2103
  return keys.map((k, si) => {
1891
2104
  const v = Number(d[k]) || 0;
1892
2105
  const len2 = vPix(v);
1893
- const color = colorFor(k);
1894
- const common = { className: `twc-chart__bar ${anim === "" ? "twc-chart__anim-bar" : ""}`.trim(), "data-horizontal": horizontal || void 0, style: { fill: color }, rx: 2, ...hoverProps };
1895
- return horizontal ? /* @__PURE__ */ jsx("rect", { ...common, x: padL, y: gStart + si * bw + 0.5, width: Math.max(0, len2), height: Math.max(1, bw - 1) }, `${gi}-${si}`) : /* @__PURE__ */ jsx("rect", { ...common, x: gStart + si * bw + 0.5, y: padT + innerH - len2, width: Math.max(1, bw - 1), height: Math.max(0, len2) }, `${gi}-${si}`);
2106
+ const key = `${gi}:${k}`;
2107
+ return horizontal ? /* @__PURE__ */ jsx("rect", { ...barCommon(k, key), x: padL, y: gStart + si * bw + 0.5, width: Math.max(0, len2), height: Math.max(1, bw - 1) }, key) : /* @__PURE__ */ jsx("rect", { ...barCommon(k, key), x: gStart + si * bw + 0.5, y: padT + innerH - len2, width: Math.max(1, bw - 1), height: Math.max(0, len2) }, key);
1896
2108
  });
1897
2109
  });
1898
2110
  };
@@ -1907,7 +2119,7 @@ function Chart({
1907
2119
  });
1908
2120
  const base = canStack ? rows.map((d, i) => vPos(stackAcc[i] - (Number(d[k]) || 0))) : null;
1909
2121
  const gradId = `twc-grad-${gid}-${allKeys.indexOf(k)}`;
1910
- return /* @__PURE__ */ jsxs("g", { children: [
2122
+ return /* @__PURE__ */ jsxs("g", { "data-mark": true, "data-active": focusActive(k), children: [
1911
2123
  isArea ? /* @__PURE__ */ jsxs(Fragment, { children: [
1912
2124
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
1913
2125
  /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.34" }),
@@ -1916,14 +2128,14 @@ function Chart({
1916
2128
  /* @__PURE__ */ jsx(
1917
2129
  "path",
1918
2130
  {
1919
- className: `twc-chart__area ${anim === "" ? "twc-chart__anim-fade" : ""}`.trim(),
2131
+ className: `twc-chart__area ${animate ? "twc-chart__anim-fade" : ""}`.trim(),
1920
2132
  style: { fill: `url(#${gradId})` },
1921
2133
  d: canStack ? `${linePath(pts, pathOpts)} ${pts.map((p, i) => `L${p[0]} ${base[i]}`).reverse().join(" ")} Z` : areaPath(pts, padT + innerH, pathOpts)
1922
2134
  }
1923
2135
  )
1924
2136
  ] }) : null,
1925
- /* @__PURE__ */ jsx("path", { className: `twc-chart__line ${anim === "" ? "twc-chart__anim-line" : ""}`.trim(), style: { stroke: color }, pathLength: "1", d: linePath(pts, pathOpts) }),
1926
- showDots || !isArea ? pts.map((p, i) => /* @__PURE__ */ jsxs("g", { className: anim === "" ? "twc-chart__anim-fade" : void 0, children: [
2137
+ /* @__PURE__ */ jsx("path", { className: `twc-chart__line ${animate ? "twc-chart__anim-line" : ""}`.trim(), style: { stroke: color }, pathLength: "1", d: linePath(pts, pathOpts) }),
2138
+ showDots || !isArea ? pts.map((p, i) => /* @__PURE__ */ jsxs("g", { className: animate ? "twc-chart__anim-fade" : void 0, children: [
1927
2139
  /* @__PURE__ */ jsx(
1928
2140
  "circle",
1929
2141
  {
@@ -1931,11 +2143,25 @@ function Chart({
1931
2143
  cx: p[0],
1932
2144
  cy: p[1],
1933
2145
  r: "14",
1934
- onMouseMove: (e) => show(tipFor(rows[i]), e),
1935
- onMouseLeave: hide
2146
+ onMouseMove: (e) => onMarkMove(rows[i], i, e),
2147
+ onMouseLeave: onMarkLeave,
2148
+ onClick: () => {
2149
+ clickDatum(rows[i], k, i);
2150
+ selectMark(`${i}:${k}`);
2151
+ }
1936
2152
  }
1937
2153
  ),
1938
- /* @__PURE__ */ jsx("circle", { className: "twc-chart__dot", style: { stroke: color }, cx: p[0], cy: p[1], r: "3.5" })
2154
+ /* @__PURE__ */ jsx(
2155
+ "circle",
2156
+ {
2157
+ className: "twc-chart__dot",
2158
+ style: { stroke: color },
2159
+ cx: p[0],
2160
+ cy: p[1],
2161
+ r: hoverIdx === i ? 5 : 3.5,
2162
+ "data-selected": selected === `${i}:${k}` || void 0
2163
+ }
2164
+ )
1939
2165
  ] }, i)) : null
1940
2166
  ] }, k);
1941
2167
  });
@@ -1961,62 +2187,153 @@ function Chart({
1961
2187
  return /* @__PURE__ */ jsx("text", { className: "twc-chart__label", x: lineX(i), y: vPos(v) - 9, textAnchor: "middle", children: shortNum(v) }, `${k}-${i}`);
1962
2188
  }));
1963
2189
  };
2190
+ const renderZones = () => rows.map((d, i) => {
2191
+ let x, y, w, hgt;
2192
+ if (isBar && horizontal) {
2193
+ x = padL;
2194
+ w = innerW;
2195
+ y = padT + catBandH * i;
2196
+ hgt = catBandH;
2197
+ } else if (isBar) {
2198
+ x = padL + catBand * i;
2199
+ w = catBand;
2200
+ y = padT;
2201
+ hgt = innerH;
2202
+ } else {
2203
+ const x0 = i === 0 ? padL : (lineX(i - 1) + lineX(i)) / 2;
2204
+ const x1 = i === rows.length - 1 ? padL + innerW : (lineX(i) + lineX(i + 1)) / 2;
2205
+ x = x0;
2206
+ w = x1 - x0;
2207
+ y = padT;
2208
+ hgt = innerH;
2209
+ }
2210
+ return /* @__PURE__ */ jsx(
2211
+ "rect",
2212
+ {
2213
+ className: "twc-chart__zone",
2214
+ x,
2215
+ y,
2216
+ width: Math.max(0, w),
2217
+ height: hgt,
2218
+ "data-clickable": clickable || void 0,
2219
+ onMouseMove: (e) => {
2220
+ show(tipFor(d), e);
2221
+ setHoverIdx(i);
2222
+ },
2223
+ onMouseLeave: onMarkLeave,
2224
+ onClick: () => {
2225
+ if (onDataClick) onDataClick({ label: d.label, series: null, seriesIndex: -1, value: void 0, index: baseIdx + i, row: d });
2226
+ }
2227
+ },
2228
+ i
2229
+ );
2230
+ });
1964
2231
  const toggle = (it) => setHidden((prev) => {
1965
2232
  const next = new Set(prev);
1966
2233
  if (next.has(it.label)) next.delete(it.label);
1967
2234
  else if (allKeys.length - next.size > 1) next.add(it.label);
1968
2235
  return next;
1969
2236
  });
1970
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--${type} ${className}`.trim(), ...rest, children: [
1971
- baseStyles,
1972
- styles,
1973
- /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
1974
- showGrid ? ticks.map((t, i) => {
1975
- const p = vPos(t);
1976
- return horizontal ? /* @__PURE__ */ jsx("line", { className: "twc-chart__grid", x1: p, y1: padT, x2: p, y2: padT + innerH }, i) : /* @__PURE__ */ jsx("line", { className: "twc-chart__grid", x1: padL, y1: p, x2: W - padR, y2: p }, i);
1977
- }) : null,
1978
- showAxis ? /* @__PURE__ */ jsx("line", { className: "twc-chart__axisline", x1: padL, y1: padT, x2: padL, y2: padT + innerH }) : null,
1979
- showAxis ? /* @__PURE__ */ jsx("line", { className: "twc-chart__axisline", x1: padL, y1: padT + innerH, x2: W - padR, y2: padT + innerH }) : null,
1980
- showAxis ? ticks.map((t, i) => {
1981
- const p = vPos(t);
1982
- return horizontal ? /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: p, y: H - 8, textAnchor: "middle", children: shortNum(t) }, i) : /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: padL - 8, y: p + 4, textAnchor: "end", children: shortNum(t) }, i);
1983
- }) : null,
1984
- isBar ? renderBars() : renderLines(),
1985
- renderValues(),
1986
- showAxis ? rows.map((d, i) => {
1987
- if (horizontal) {
1988
- const cy = padT + catBandH * i + catBandH / 2;
1989
- return /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: padL - 8, y: cy + 4, textAnchor: "end", children: d.label }, i);
1990
- }
1991
- const cx = isBar ? padL + catBand * i + catBand / 2 : lineX(i);
1992
- return /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: cx, y: H - 8, textAnchor: "middle", children: d.label }, i);
1993
- }) : null
1994
- ] }),
1995
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
1996
- tableFallback ? /* @__PURE__ */ jsx(
1997
- ChartTable,
1998
- {
1999
- id: tableId,
2000
- caption: caption ?? svgAriaLabel,
2001
- columns: allKeys,
2002
- rows: rows.map((d) => ({ label: d.label, values: allKeys.map((k) => fmt(Number(d[k]) || 0)) }))
2003
- }
2004
- ) : null,
2005
- showLegend && multi ? /* @__PURE__ */ jsx(
2006
- ChartLegend,
2007
- {
2008
- items: allKeys.map((k) => ({ label: k, color: colorFor(k) })),
2009
- onToggle: toggle,
2010
- hidden: (it) => hidden.has(it.label)
2011
- }
2012
- ) : null
2013
- ] });
2237
+ const showCrosshair = crosshair && !horizontal && hoverIdx != null && rows[hoverIdx];
2238
+ const dragBand = zoomable && drag && !drag.pan && drag.end !== drag.start;
2239
+ return /* @__PURE__ */ jsxs(
2240
+ "div",
2241
+ {
2242
+ ref: containerRef,
2243
+ className: `twc-chart twc-chart--${type} ${className}`.trim(),
2244
+ "data-hovering": focusSeries != null || void 0,
2245
+ "data-has-selection": selected != null || void 0,
2246
+ "data-clickable": clickable || void 0,
2247
+ ...rest,
2248
+ children: [
2249
+ baseStyles,
2250
+ styles,
2251
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
2252
+ showGrid ? ticks.map((t, i) => {
2253
+ const p = vPos(t);
2254
+ return horizontal ? /* @__PURE__ */ jsx("line", { className: "twc-chart__grid", x1: p, y1: padT, x2: p, y2: padT + innerH }, i) : /* @__PURE__ */ jsx("line", { className: "twc-chart__grid", x1: padL, y1: p, x2: W - padR, y2: p }, i);
2255
+ }) : null,
2256
+ showAxis ? /* @__PURE__ */ jsx("line", { className: "twc-chart__axisline", x1: padL, y1: padT, x2: padL, y2: padT + innerH }) : null,
2257
+ showAxis ? /* @__PURE__ */ jsx("line", { className: "twc-chart__axisline", x1: padL, y1: padT + innerH, x2: W - padR, y2: padT + innerH }) : null,
2258
+ showAxis ? ticks.map((t, i) => {
2259
+ const p = vPos(t);
2260
+ return horizontal ? /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: p, y: H - 8, textAnchor: "middle", children: shortNum(t) }, i) : /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: padL - 8, y: p + 4, textAnchor: "end", children: shortNum(t) }, i);
2261
+ }) : null,
2262
+ showCrosshair ? /* @__PURE__ */ jsx("line", { className: "twc-chart__crosshair", x1: catX(hoverIdx), y1: padT, x2: catX(hoverIdx), y2: padT + innerH }) : null,
2263
+ !zoomable ? renderZones() : null,
2264
+ isBar ? renderBars() : renderLines(),
2265
+ renderValues(),
2266
+ showAxis ? rows.map((d, i) => {
2267
+ if (horizontal) {
2268
+ const cy = padT + catBandH * i + catBandH / 2;
2269
+ return /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: padL - 8, y: cy + 4, textAnchor: "end", children: d.label }, i);
2270
+ }
2271
+ return /* @__PURE__ */ jsx("text", { className: "twc-chart__axis", x: catX(i), y: H - 8, textAnchor: "middle", children: d.label }, i);
2272
+ }) : null,
2273
+ dragBand ? (() => {
2274
+ const lo = Math.min(drag.start, drag.end), hi = Math.max(drag.start, drag.end);
2275
+ const x0 = isBar ? padL + catBand * lo : lineX(lo);
2276
+ const x1 = isBar ? padL + catBand * (hi + 1) : lineX(hi);
2277
+ return /* @__PURE__ */ jsx("rect", { className: "twc-chart__zoomband", x: x0, y: padT, width: Math.max(0, x1 - x0), height: innerH });
2278
+ })() : null,
2279
+ zoomable ? /* @__PURE__ */ jsx(
2280
+ "rect",
2281
+ {
2282
+ className: "twc-chart__overlay",
2283
+ "data-zoom": "true",
2284
+ "data-clickable": clickable || void 0,
2285
+ "data-panning": drag && drag.pan || void 0,
2286
+ x: padL,
2287
+ y: padT,
2288
+ width: innerW,
2289
+ height: innerH,
2290
+ onMouseMove: onOverlayMove,
2291
+ onMouseLeave: () => {
2292
+ onMarkLeave();
2293
+ setDrag(null);
2294
+ },
2295
+ onMouseDown: onOverlayDown,
2296
+ onMouseUp: onOverlayUp,
2297
+ onClick: onOverlayClick
2298
+ }
2299
+ ) : null
2300
+ ] }),
2301
+ zoomable && zoom ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-chart__zoom-reset", onClick: () => setZoom(null), "aria-label": "Reset zoom", children: [
2302
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2303
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
2304
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
2305
+ ] }),
2306
+ "Reset"
2307
+ ] }) : null,
2308
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2309
+ tableFallback ? /* @__PURE__ */ jsx(
2310
+ ChartTable,
2311
+ {
2312
+ id: tableId,
2313
+ caption: caption ?? svgAriaLabel,
2314
+ columns: allKeys,
2315
+ rows: allRows.map((d) => ({ label: d.label, values: allKeys.map((k) => fmt(Number(d[k]) || 0)) }))
2316
+ }
2317
+ ) : null,
2318
+ showLegend && multi ? /* @__PURE__ */ jsx(
2319
+ ChartLegend,
2320
+ {
2321
+ items: allKeys.map((k) => ({ label: k, color: colorFor(k) })),
2322
+ onToggle: toggle,
2323
+ hidden: (it) => hidden.has(it.label),
2324
+ onFocus: (it) => setFocusSeries(it.label),
2325
+ onBlur: () => setFocusSeries(null)
2326
+ }
2327
+ ) : null
2328
+ ]
2329
+ }
2330
+ );
2014
2331
  }
2015
2332
  var CHART_CSS2 = `
2016
2333
  .twc-chart--pie svg { overflow: visible; }
2017
2334
  .twc-chart__slice { transition: opacity var(--duration-fast) var(--ease-standard); stroke: var(--color-surface); stroke-width: 1.5; }
2018
2335
  .twc-chart__slice:hover { opacity: 0.85; }
2019
- .twc-chart__slice-label { fill: var(--color-surface); font-size: 11px; font-weight: 600; }
2336
+ .twc-chart__slice-label { fill: var(--color-chart-on-fill); paint-order: stroke; stroke: var(--color-chart-on-fill-halo); stroke-width: 3px; stroke-linejoin: round; vector-effect: non-scaling-stroke; font-size: 11px; font-weight: 600; }
2020
2337
  .twc-chart__center { fill: var(--color-text); font-weight: 700; }
2021
2338
  .twc-chart__center-sub { fill: var(--color-text-subtle); }
2022
2339
  .twc-chart__empty { fill: var(--color-text-subtle); font-size: var(--text-sm); }
@@ -2033,6 +2350,7 @@ function PieChart({
2033
2350
  valueFormat,
2034
2351
  height = 260,
2035
2352
  colors,
2353
+ onDataClick,
2036
2354
  ariaLabel,
2037
2355
  "aria-label": ariaLabelProp,
2038
2356
  tableFallback = true,
@@ -2046,6 +2364,9 @@ function PieChart({
2046
2364
  const tableId = tableFallback ? `${uid}-table` : void 0;
2047
2365
  const { containerRef, tip, show, hide } = useChartTooltip();
2048
2366
  const [active, setActive] = React51__default.useState(null);
2367
+ const [focus, setFocus] = React51__default.useState(null);
2368
+ const [selected, setSelected] = React51__default.useState(null);
2369
+ const clickable = !!onDataClick;
2049
2370
  const rows = data || [];
2050
2371
  const fmt = valueFormat || fmtNumber;
2051
2372
  const values = rows.map((d) => Math.max(0, Number(d.value) || 0));
@@ -2069,20 +2390,37 @@ function PieChart({
2069
2390
  const color = d.color || paletteAt(colors, i);
2070
2391
  return { d, i, v, frac, s0, s1, span, pct, color };
2071
2392
  });
2072
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--pie ${className}`.trim(), "data-donut": isDonut ? "true" : void 0, "data-hovering": active != null ? "true" : void 0, ...rest, children: [
2393
+ const clickDatum = (d, i, v, pct) => {
2394
+ if (!onDataClick) return;
2395
+ onDataClick({ label: d.label, value: v, index: i, percent: pct });
2396
+ };
2397
+ const selectMark = (i) => setSelected((s) => s === i ? null : i);
2398
+ const emph = focus != null ? focus : active;
2399
+ const multi = rows.length > 1;
2400
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--pie ${className}`.trim(), "data-donut": isDonut ? "true" : void 0, "data-has-selection": selected != null || void 0, "data-hovering": emph != null ? "true" : void 0, "data-clickable": clickable || void 0, ...rest, children: [
2073
2401
  baseStyles,
2074
2402
  styles,
2075
2403
  /* @__PURE__ */ jsx("svg", { viewBox: `0 0 ${H} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, children: total <= 0 ? /* @__PURE__ */ jsx("text", { className: "twc-chart__empty", x: cx, y: cy, textAnchor: "middle", dominantBaseline: "middle", children: "No data" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2076
2404
  slices.map(({ d, i, v, s0, s1, span, pct, color }) => {
2077
2405
  const pad = span > padAngle ? padAngle / 2 : 0;
2078
2406
  const items = [{ color, label: fmt(v), value: `${pct}%` }];
2407
+ let explode;
2408
+ if (selected === i) {
2409
+ const mid = (s0 + s1) / 2;
2410
+ const a = (mid - 90) * Math.PI / 180;
2411
+ const dx = Math.cos(a) * 8, dy = Math.sin(a) * 8;
2412
+ if (Number.isFinite(dx) && Number.isFinite(dy)) {
2413
+ explode = `translate(${dx}px, ${dy}px)`;
2414
+ }
2415
+ }
2079
2416
  return /* @__PURE__ */ jsx(
2080
2417
  "path",
2081
2418
  {
2082
2419
  className: "twc-chart__slice twc-chart__anim-arc",
2083
- style: { fill: color },
2420
+ style: { fill: color, transform: explode },
2084
2421
  "data-mark": "",
2085
- "data-active": active === i ? "true" : void 0,
2422
+ "data-active": emph === i ? "true" : void 0,
2423
+ "data-selected": selected === i ? "true" : void 0,
2086
2424
  d: arcPath(cx, cy, rOuter, rInner, s0 + pad, s1 - pad),
2087
2425
  onMouseMove: (e) => {
2088
2426
  setActive(i);
@@ -2091,6 +2429,10 @@ function PieChart({
2091
2429
  onMouseLeave: () => {
2092
2430
  setActive(null);
2093
2431
  hide();
2432
+ },
2433
+ onClick: () => {
2434
+ clickDatum(d, i, v, pct);
2435
+ selectMark(i);
2094
2436
  }
2095
2437
  },
2096
2438
  i
@@ -2126,7 +2468,14 @@ function PieChart({
2126
2468
  rows: slices.map(({ d, v, pct }) => ({ label: d.label, values: [fmt(v), `${pct}%`] }))
2127
2469
  }
2128
2470
  ) : null,
2129
- showLegend && rows.length ? /* @__PURE__ */ jsx(ChartLegend, { items: slices.map(({ d, color }) => ({ label: d.label, color })) }) : null
2471
+ showLegend && rows.length ? /* @__PURE__ */ jsx(
2472
+ ChartLegend,
2473
+ {
2474
+ items: slices.map(({ d, color }) => ({ label: d.label, color })),
2475
+ onFocus: multi ? (it, i) => setFocus(i) : void 0,
2476
+ onBlur: multi ? () => setFocus(null) : void 0
2477
+ }
2478
+ ) : null
2130
2479
  ] });
2131
2480
  }
2132
2481
  function DonutChart(props) {
@@ -2138,6 +2487,7 @@ var GAUGE_CSS = `
2138
2487
  .twc-gauge__arc { fill: none; stroke-linecap: round; transition: stroke-dashoffset var(--duration-slow) var(--ease-standard); }
2139
2488
  .twc-gauge__value { fill: var(--color-text); font-weight: 700; }
2140
2489
  .twc-gauge__label { fill: var(--color-text-subtle); }
2490
+ .twc-gauge__arc[data-selected="true"] { filter: drop-shadow(0 0 2px var(--color-text)); }
2141
2491
  `;
2142
2492
  function Gauge({
2143
2493
  value = 0,
@@ -2154,6 +2504,7 @@ function Gauge({
2154
2504
  size = 200,
2155
2505
  series,
2156
2506
  showLegend = true,
2507
+ onDataClick,
2157
2508
  ariaLabel,
2158
2509
  "aria-label": ariaLabelProp,
2159
2510
  tableFallback = true,
@@ -2166,6 +2517,8 @@ function Gauge({
2166
2517
  const uid = React51__default.useId();
2167
2518
  const tableId = tableFallback ? `${uid}-table` : void 0;
2168
2519
  const { containerRef, tip, show, hide } = useChartTooltip();
2520
+ const [selected, setSelected] = React51__default.useState(null);
2521
+ const [focus, setFocus] = React51__default.useState(null);
2169
2522
  const fmt = valueFormat || ((n) => String(Math.round(Number(n) || 0)));
2170
2523
  const cx = size / 2, cy = size / 2;
2171
2524
  const span = endAngle - startAngle;
@@ -2190,79 +2543,106 @@ function Gauge({
2190
2543
  return `M${r(x0)} ${r(y0)} A${r(radius)} ${r(radius)} 0 ${large} ${sweep} ${r(x1)} ${r(y1)}`;
2191
2544
  };
2192
2545
  const single = items[0];
2546
+ const clickable = !!onDataClick;
2547
+ const activeFor = (i) => focus == null ? void 0 : i === focus || void 0;
2548
+ const clickRing = (it, i) => {
2549
+ if (onDataClick) onDataClick({ value: Number(it.value) || 0, label: it.label, index: i });
2550
+ };
2551
+ const selectRing = (i) => setSelected((s) => s === i ? null : i);
2193
2552
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? (isMulti ? `Gauge with ${items.length} values` : `${labelText(label) || "Gauge"}: ${fmt(single.value)} of ${fmt(max)}`);
2194
2553
  const valueFont = Math.round(size * (isMulti ? 0.11 : 0.17));
2195
2554
  const labelFont = Math.round(size * 0.072);
2196
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-gauge twc-chart twc-chart--gauge ${className}`.trim(), "data-multi": isMulti || void 0, ...rest, children: [
2197
- baseStyles,
2198
- styles,
2199
- /* @__PURE__ */ jsxs(
2200
- "svg",
2201
- {
2202
- viewBox: `0 0 ${size} ${size}`,
2203
- width: size,
2204
- height: size,
2205
- role: "img",
2206
- "aria-label": svgAriaLabel,
2207
- "aria-describedby": tableId,
2208
- children: [
2209
- items.map((it, i) => {
2210
- const radius = ringRadius(i);
2211
- const f = frac(it.value);
2212
- const c = it.color || paletteAt(void 0, i);
2213
- const valEnd = startAngle + f * span;
2214
- return /* @__PURE__ */ jsxs("g", { children: [
2215
- /* @__PURE__ */ jsx(
2216
- "path",
2217
- {
2218
- className: "twc-gauge__track",
2219
- d: arc(radius, startAngle, endAngle),
2220
- style: { stroke: trackColor, strokeWidth: strokeW }
2221
- }
2222
- ),
2223
- f > 0 ? /* @__PURE__ */ jsx(
2224
- "path",
2225
- {
2226
- className: "twc-gauge__arc twc-chart__anim-fade",
2227
- d: arc(radius, startAngle, valEnd),
2228
- style: { stroke: c, strokeWidth: strokeW },
2229
- onMouseMove: (e) => show({
2230
- title: labelText(it.label) || "Value",
2231
- items: [{ color: c, label: "", value: fmt(it.value) }]
2232
- }, e),
2233
- onMouseLeave: hide
2234
- }
2235
- ) : null
2236
- ] }, i);
2237
- }),
2238
- !isMulti && showValue ? /* @__PURE__ */ jsx("text", { className: "twc-gauge__value", x: cx, y: cy, textAnchor: "middle", dominantBaseline: "middle", style: { fontSize: valueFont }, children: fmt(single.value) }) : null,
2239
- !isMulti && label != null ? /* @__PURE__ */ jsx("text", { className: "twc-gauge__label", x: cx, y: cy + valueFont * 0.78, textAnchor: "middle", style: { fontSize: labelFont }, children: labelText(label) }) : null
2240
- ]
2241
- }
2242
- ),
2243
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2244
- tableFallback ? /* @__PURE__ */ jsx(
2245
- ChartTable,
2246
- {
2247
- id: tableId,
2248
- caption: caption ?? svgAriaLabel,
2249
- columns: ["Value"],
2250
- rows: items.map((it, i) => ({
2251
- label: labelText(it.label) || (isMulti ? `Series ${i + 1}` : "Value"),
2252
- values: [fmt(it.value)]
2253
- }))
2254
- }
2255
- ) : null,
2256
- isMulti && showLegend ? /* @__PURE__ */ jsx(
2257
- ChartLegend,
2258
- {
2259
- items: items.map((it, i) => ({
2260
- label: labelText(it.label) || `Series ${i + 1}`,
2261
- color: it.color || paletteAt(void 0, i)
2262
- }))
2263
- }
2264
- ) : null
2265
- ] });
2555
+ return /* @__PURE__ */ jsxs(
2556
+ "div",
2557
+ {
2558
+ ref: containerRef,
2559
+ className: `twc-gauge twc-chart twc-chart--gauge ${className}`.trim(),
2560
+ "data-multi": isMulti || void 0,
2561
+ "data-hovering": focus != null || void 0,
2562
+ "data-clickable": clickable || void 0,
2563
+ "data-has-selection": selected != null || void 0,
2564
+ ...rest,
2565
+ children: [
2566
+ baseStyles,
2567
+ styles,
2568
+ /* @__PURE__ */ jsxs(
2569
+ "svg",
2570
+ {
2571
+ viewBox: `0 0 ${size} ${size}`,
2572
+ width: size,
2573
+ height: size,
2574
+ role: "img",
2575
+ "aria-label": svgAriaLabel,
2576
+ "aria-describedby": tableId,
2577
+ children: [
2578
+ items.map((it, i) => {
2579
+ const radius = ringRadius(i);
2580
+ const f = frac(it.value);
2581
+ const c = it.color || paletteAt(void 0, i);
2582
+ const valEnd = startAngle + f * span;
2583
+ return /* @__PURE__ */ jsxs("g", { children: [
2584
+ /* @__PURE__ */ jsx(
2585
+ "path",
2586
+ {
2587
+ className: "twc-gauge__track",
2588
+ d: arc(radius, startAngle, endAngle),
2589
+ style: { stroke: trackColor, strokeWidth: strokeW }
2590
+ }
2591
+ ),
2592
+ f > 0 ? /* @__PURE__ */ jsx(
2593
+ "path",
2594
+ {
2595
+ className: "twc-gauge__arc twc-chart__anim-fade",
2596
+ "data-mark": true,
2597
+ "data-active": activeFor(i),
2598
+ "data-selected": selected === i || void 0,
2599
+ d: arc(radius, startAngle, valEnd),
2600
+ style: { stroke: c, strokeWidth: strokeW },
2601
+ onMouseMove: (e) => show({
2602
+ title: labelText(it.label) || "Value",
2603
+ items: [{ color: c, label: "", value: fmt(it.value) }]
2604
+ }, e),
2605
+ onMouseLeave: hide,
2606
+ onClick: () => {
2607
+ clickRing(it, i);
2608
+ selectRing(i);
2609
+ }
2610
+ }
2611
+ ) : null
2612
+ ] }, i);
2613
+ }),
2614
+ !isMulti && showValue ? /* @__PURE__ */ jsx("text", { className: "twc-gauge__value", x: cx, y: cy, textAnchor: "middle", dominantBaseline: "middle", style: { fontSize: valueFont }, children: fmt(single.value) }) : null,
2615
+ !isMulti && label != null ? /* @__PURE__ */ jsx("text", { className: "twc-gauge__label", x: cx, y: cy + valueFont * 0.78, textAnchor: "middle", style: { fontSize: labelFont }, children: labelText(label) }) : null
2616
+ ]
2617
+ }
2618
+ ),
2619
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2620
+ tableFallback ? /* @__PURE__ */ jsx(
2621
+ ChartTable,
2622
+ {
2623
+ id: tableId,
2624
+ caption: caption ?? svgAriaLabel,
2625
+ columns: ["Value"],
2626
+ rows: items.map((it, i) => ({
2627
+ label: labelText(it.label) || (isMulti ? `Series ${i + 1}` : "Value"),
2628
+ values: [fmt(it.value)]
2629
+ }))
2630
+ }
2631
+ ) : null,
2632
+ isMulti && showLegend ? /* @__PURE__ */ jsx(
2633
+ ChartLegend,
2634
+ {
2635
+ items: items.map((it, i) => ({
2636
+ label: labelText(it.label) || `Series ${i + 1}`,
2637
+ color: it.color || paletteAt(void 0, i)
2638
+ })),
2639
+ onFocus: items.length > 1 ? (it, i) => setFocus(i) : void 0,
2640
+ onBlur: items.length > 1 ? () => setFocus(null) : void 0
2641
+ }
2642
+ ) : null
2643
+ ]
2644
+ }
2645
+ );
2266
2646
  }
2267
2647
  function labelText(label) {
2268
2648
  return typeof label === "string" || typeof label === "number" ? String(label) : "";
@@ -2289,6 +2669,7 @@ function Sparkline({
2289
2669
  min,
2290
2670
  max,
2291
2671
  valueFormat,
2672
+ onDataClick,
2292
2673
  ariaLabel,
2293
2674
  "aria-label": ariaLabelProp,
2294
2675
  tableFallback = false,
@@ -2302,6 +2683,8 @@ function Sparkline({
2302
2683
  const tableId = tableFallback ? `${uid}-table` : void 0;
2303
2684
  const { containerRef, tip, show, hide } = useChartTooltip();
2304
2685
  const [hover, setHover] = React51__default.useState(null);
2686
+ const [selected, setSelected] = React51__default.useState(null);
2687
+ const clickable = !!onDataClick;
2305
2688
  const kind = area ? "area" : type;
2306
2689
  const isBar = kind === "bar";
2307
2690
  const isArea = kind === "area";
@@ -2343,6 +2726,10 @@ function Sparkline({
2343
2726
  setHover(null);
2344
2727
  hide();
2345
2728
  };
2729
+ const clickPoint = (i) => {
2730
+ if (onDataClick) onDataClick({ value: values[i], index: i, label: labels[i] });
2731
+ setSelected((s) => s === i ? null : i);
2732
+ };
2346
2733
  const last = n ? values[n - 1] : 0;
2347
2734
  const summary = !n ? "sparkline, no data" : tableFallback ? `sparkline, ${n} points, latest ${fmt(last)}` : `sparkline, ${n} points, first ${fmt(values[0])}, latest ${fmt(last)}, min ${fmt(Math.min(...values))}, max ${fmt(Math.max(...values))}`;
2348
2735
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? summary;
@@ -2355,94 +2742,120 @@ function Sparkline({
2355
2742
  "rect",
2356
2743
  {
2357
2744
  className: "twc-sparkline__bar twc-chart__anim-fade",
2745
+ "data-mark": true,
2746
+ "data-selected": selected === i || void 0,
2358
2747
  style: { fill: color },
2359
2748
  x: pad + band * i + (band - slot) / 2,
2360
2749
  y: baselineY - h,
2361
2750
  width: Math.max(1, slot),
2362
2751
  height: Math.max(1, h),
2363
2752
  onMouseMove: (e) => show(tipFor(i), e),
2364
- onMouseLeave: hide
2753
+ onMouseLeave: hide,
2754
+ onClick: () => clickPoint(i)
2365
2755
  },
2366
2756
  i
2367
2757
  );
2368
2758
  });
2369
2759
  };
2370
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-sparkline twc-chart--${kind} ${className}`.trim(), ...rest, children: [
2371
- baseStyles,
2372
- styles,
2373
- /* @__PURE__ */ jsx(
2374
- "svg",
2375
- {
2376
- viewBox: `0 0 ${W} ${H}`,
2377
- width: W,
2378
- height: H,
2379
- role: "img",
2380
- "aria-label": svgAriaLabel,
2381
- "aria-describedby": tableId,
2382
- children: n === 0 ? null : isBar ? renderBars() : /* @__PURE__ */ jsxs(Fragment, { children: [
2383
- isArea ? /* @__PURE__ */ jsx("path", { className: "twc-sparkline__area twc-chart__anim-fade", style: { fill: color }, d: areaPath(points, baselineY, pathOpts) }) : null,
2384
- /* @__PURE__ */ jsx(
2385
- "path",
2386
- {
2387
- className: "twc-sparkline__line twc-chart__anim-line",
2388
- style: { stroke: color, strokeWidth },
2389
- pathLength: "1",
2390
- d: linePath(points, pathOpts)
2391
- }
2392
- ),
2393
- showDots && n ? /* @__PURE__ */ jsx(
2394
- "circle",
2395
- {
2396
- className: "twc-sparkline__dot twc-chart__anim-fade",
2397
- style: { fill: color },
2398
- cx: points[n - 1][0],
2399
- cy: points[n - 1][1],
2400
- r: dotR
2401
- }
2402
- ) : null,
2403
- hover != null ? /* @__PURE__ */ jsx(
2404
- "circle",
2405
- {
2406
- className: "twc-sparkline__dot",
2407
- style: { fill: color },
2408
- cx: points[hover][0],
2409
- cy: points[hover][1],
2410
- r: Math.max(2, strokeWidth + 1)
2411
- }
2412
- ) : null,
2413
- points.map((p, i) => /* @__PURE__ */ jsx(
2414
- "circle",
2415
- {
2416
- className: "twc-chart__hit",
2417
- cx: p[0],
2418
- cy: p[1],
2419
- r: "14",
2420
- onMouseMove: onPointMove(i),
2421
- onMouseLeave: onPointLeave
2422
- },
2423
- i
2424
- ))
2425
- ] })
2426
- }
2427
- ),
2428
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2429
- tableFallback ? /* @__PURE__ */ jsx(
2430
- ChartTable,
2431
- {
2432
- id: tableId,
2433
- caption: caption ?? svgAriaLabel,
2434
- columns: ["value"],
2435
- rows: values.map((v, i) => ({ label: labels[i], values: [fmt(v)] }))
2436
- }
2437
- ) : null
2438
- ] });
2439
- }
2440
- var SCATTER_CSS = `
2441
- .twc-chart--scatter__grid { stroke: var(--color-divider); stroke-width: 1; }
2442
- .twc-chart--scatter__axis { fill: var(--color-text-subtle); font-size: 11px; }
2443
- .twc-chart--scatter__title { fill: var(--color-text-muted); font-size: 11px; font-weight: 500; }
2444
- .twc-chart--scatter__dot { transform-box: fill-box; transform-origin: center; transition: transform var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard); stroke: var(--color-surface); stroke-width: 1; }
2445
- .twc-chart--scatter__dot[data-bubble="true"] { fill-opacity: 0.55; }
2760
+ return /* @__PURE__ */ jsxs(
2761
+ "div",
2762
+ {
2763
+ ref: containerRef,
2764
+ className: `twc-chart twc-sparkline twc-chart--${kind} ${className}`.trim(),
2765
+ "data-clickable": clickable || void 0,
2766
+ "data-has-selection": selected != null || void 0,
2767
+ ...rest,
2768
+ children: [
2769
+ baseStyles,
2770
+ styles,
2771
+ /* @__PURE__ */ jsx(
2772
+ "svg",
2773
+ {
2774
+ viewBox: `0 0 ${W} ${H}`,
2775
+ width: W,
2776
+ height: H,
2777
+ role: "img",
2778
+ "aria-label": svgAriaLabel,
2779
+ "aria-describedby": tableId,
2780
+ children: n === 0 ? null : isBar ? renderBars() : /* @__PURE__ */ jsxs(Fragment, { children: [
2781
+ isArea ? /* @__PURE__ */ jsx("path", { className: "twc-sparkline__area twc-chart__anim-fade", style: { fill: color }, d: areaPath(points, baselineY, pathOpts) }) : null,
2782
+ /* @__PURE__ */ jsx(
2783
+ "path",
2784
+ {
2785
+ className: "twc-sparkline__line twc-chart__anim-line",
2786
+ style: { stroke: color, strokeWidth },
2787
+ pathLength: "1",
2788
+ d: linePath(points, pathOpts)
2789
+ }
2790
+ ),
2791
+ showDots && n ? /* @__PURE__ */ jsx(
2792
+ "circle",
2793
+ {
2794
+ className: "twc-sparkline__dot twc-chart__anim-fade",
2795
+ style: { fill: color },
2796
+ cx: points[n - 1][0],
2797
+ cy: points[n - 1][1],
2798
+ r: dotR
2799
+ }
2800
+ ) : null,
2801
+ hover != null ? /* @__PURE__ */ jsx(
2802
+ "circle",
2803
+ {
2804
+ className: "twc-sparkline__dot",
2805
+ style: { fill: color },
2806
+ cx: points[hover][0],
2807
+ cy: points[hover][1],
2808
+ r: Math.max(2, strokeWidth + 1)
2809
+ }
2810
+ ) : null,
2811
+ selected != null && points[selected] ? /* @__PURE__ */ jsx(
2812
+ "circle",
2813
+ {
2814
+ className: "twc-sparkline__dot",
2815
+ "data-mark": true,
2816
+ "data-selected": "true",
2817
+ style: { fill: color, pointerEvents: "none" },
2818
+ cx: points[selected][0],
2819
+ cy: points[selected][1],
2820
+ r: Math.max(2, strokeWidth + 1)
2821
+ }
2822
+ ) : null,
2823
+ points.map((p, i) => /* @__PURE__ */ jsx(
2824
+ "circle",
2825
+ {
2826
+ className: "twc-chart__hit",
2827
+ cx: p[0],
2828
+ cy: p[1],
2829
+ r: "14",
2830
+ onMouseMove: onPointMove(i),
2831
+ onMouseLeave: onPointLeave,
2832
+ onClick: () => clickPoint(i)
2833
+ },
2834
+ i
2835
+ ))
2836
+ ] })
2837
+ }
2838
+ ),
2839
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2840
+ tableFallback ? /* @__PURE__ */ jsx(
2841
+ ChartTable,
2842
+ {
2843
+ id: tableId,
2844
+ caption: caption ?? svgAriaLabel,
2845
+ columns: ["value"],
2846
+ rows: values.map((v, i) => ({ label: labels[i], values: [fmt(v)] }))
2847
+ }
2848
+ ) : null
2849
+ ]
2850
+ }
2851
+ );
2852
+ }
2853
+ var SCATTER_CSS = `
2854
+ .twc-chart--scatter__grid { stroke: var(--color-divider); stroke-width: 1; }
2855
+ .twc-chart--scatter__axis { fill: var(--color-text-subtle); font-size: 11px; }
2856
+ .twc-chart--scatter__title { fill: var(--color-text-muted); font-size: 11px; font-weight: 500; }
2857
+ .twc-chart--scatter__dot { transform-box: fill-box; transform-origin: center; transition: transform var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard); stroke: var(--color-surface); stroke-width: 1; }
2858
+ .twc-chart--scatter__dot[data-bubble="true"] { fill-opacity: 0.55; }
2446
2859
  .twc-chart__hit { fill: transparent; }
2447
2860
  .twc-chart__hit:hover + .twc-chart--scatter__dot { transform: scale(1.18); }
2448
2861
  `;
@@ -2456,9 +2869,11 @@ function ScatterChart({
2456
2869
  yLabel,
2457
2870
  showGrid = true,
2458
2871
  showLegend,
2872
+ zoomable = false,
2459
2873
  xFormat,
2460
2874
  yFormat,
2461
2875
  valueFormat,
2876
+ onDataClick,
2462
2877
  ariaLabel,
2463
2878
  "aria-label": ariaLabelProp,
2464
2879
  tableFallback = true,
@@ -2469,15 +2884,24 @@ function ScatterChart({
2469
2884
  const baseStyles = useScopedStyles("twc-chart-base", CHART_BASE_CSS);
2470
2885
  const styles = useScopedStyles("twc-scatter-styles", SCATTER_CSS);
2471
2886
  const uid = React51__default.useId();
2887
+ const gid = uid.replace(/[^a-zA-Z0-9]/g, "");
2888
+ const clipId = `twc-sc-clip-${gid}`;
2472
2889
  const tableId = tableFallback ? `${uid}-table` : void 0;
2473
2890
  const { containerRef, tip, show, hide } = useChartTooltip();
2891
+ const [focus, setFocus] = React51__default.useState(null);
2892
+ const [selected, setSelected] = React51__default.useState(null);
2893
+ const [zoom, setZoom] = React51__default.useState(null);
2894
+ const [drag, setDrag] = React51__default.useState(null);
2895
+ const svgRef = React51__default.useRef(null);
2896
+ const movedRef = React51__default.useRef(false);
2474
2897
  const list = Array.isArray(series) ? series : [];
2475
2898
  const vFmt = valueFormat || fmtNumber;
2476
2899
  const xFmt = xFormat || vFmt;
2477
2900
  const yFmt = yFormat || vFmt;
2478
- const all = list.flatMap(
2479
- (s, si) => (s && Array.isArray(s.points) ? s.points : []).filter((p) => p && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y))).map((p) => ({ si, x: Number(p.x), y: Number(p.y), z: p.z, label: p.label }))
2480
- );
2901
+ const all = list.flatMap((s, si) => {
2902
+ const pts = s && Array.isArray(s.points) ? s.points : [];
2903
+ return pts.map((p, pi) => ({ p, pi })).filter(({ p }) => p && Number.isFinite(Number(p.x)) && Number.isFinite(Number(p.y))).map(({ p, pi }) => ({ si, pi, key: `${si}:${pi}`, x: Number(p.x), y: Number(p.y), z: p.z, label: p.label, point: p }));
2904
+ });
2481
2905
  const hasZ = all.some((p) => Number.isFinite(Number(p.z)));
2482
2906
  const useBubble = bubble || hasZ;
2483
2907
  const zMax = useBubble ? Math.max(0, ...all.map((p) => Number(p.z) || 0)) : 0;
@@ -2488,8 +2912,12 @@ function ScatterChart({
2488
2912
  return Math.max(2, Math.sqrt(zv / zMax) * maxBubble);
2489
2913
  };
2490
2914
  const xs = all.map((p) => p.x), ys = all.map((p) => p.y);
2491
- const xScale = niceScale(Math.min(...xs), Math.max(...xs), 5);
2492
- const yScale = niceScale(Math.min(...ys), Math.max(...ys), 5);
2915
+ const base = { x0: Math.min(...xs), x1: Math.max(...xs), y0: Math.min(...ys), y1: Math.max(...ys) };
2916
+ const baseSpanX = base.x1 - base.x0, baseSpanY = base.y1 - base.y0;
2917
+ const canZoom = zoomable && all.length > 0 && baseSpanX > 0 && baseSpanY > 0;
2918
+ const dom = canZoom && zoom ? zoom : base;
2919
+ const xScale = niceScale(dom.x0, dom.x1, 5);
2920
+ const yScale = niceScale(dom.y0, dom.y1, 5);
2493
2921
  const xSpan = xScale.max - xScale.min || 1;
2494
2922
  const ySpan = yScale.max - yScale.min || 1;
2495
2923
  const W = 600, H = height;
@@ -2502,7 +2930,9 @@ function ScatterChart({
2502
2930
  const yPos = (y) => padT + innerH - (y - yScale.min) / ySpan * innerH;
2503
2931
  const seriesColor = (s, i) => s && s.color || paletteAt(void 0, i);
2504
2932
  const seriesName = (s, i) => s && s.name || `Series ${i + 1}`;
2505
- const legend = showLegend ?? list.length > 1;
2933
+ const multi = list.length > 1;
2934
+ const legend = showLegend ?? multi;
2935
+ const clickable = !!onDataClick;
2506
2936
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? `${useBubble ? "bubble" : "scatter"} chart`;
2507
2937
  const dots = all.map((p) => ({ ...p, radius: dotR(p.z), fill: seriesColor(list[p.si], p.si) })).sort((a, b) => b.radius - a.radius);
2508
2938
  const tipLabel = (p) => labelText2(p.label) || seriesName(list[p.si], p.si);
@@ -2515,72 +2945,272 @@ function ScatterChart({
2515
2945
  ...Number.isFinite(Number(p.z)) ? [{ label: "z", value: vFmt(Number(p.z)) }] : []
2516
2946
  ]
2517
2947
  });
2518
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--scatter ${className}`.trim(), "data-bubble": useBubble || void 0, ...rest, children: [
2519
- baseStyles,
2520
- styles,
2521
- /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, children: [
2522
- showGrid ? yScale.ticks.map((t, i) => {
2523
- const p = yPos(t);
2524
- return /* @__PURE__ */ jsx("line", { className: "twc-chart--scatter__grid", x1: padL, y1: p, x2: W - padR, y2: p }, `y${i}`);
2525
- }) : null,
2526
- showGrid ? xScale.ticks.map((t, i) => {
2527
- const p = xPos(t);
2528
- return /* @__PURE__ */ jsx("line", { className: "twc-chart--scatter__grid", x1: p, y1: padT, x2: p, y2: padT + innerH }, `x${i}`);
2529
- }) : null,
2530
- yScale.ticks.map((t, i) => /* @__PURE__ */ jsx("text", { className: "twc-chart--scatter__axis", x: padL - 8, y: yPos(t) + 4, textAnchor: "end", children: shortNum(t) }, `yt${i}`)),
2531
- xScale.ticks.map((t, i) => /* @__PURE__ */ jsx("text", { className: "twc-chart--scatter__axis", x: xPos(t), y: padT + innerH + 16, textAnchor: "middle", children: shortNum(t) }, `xt${i}`)),
2532
- xLabel != null ? /* @__PURE__ */ jsx("text", { className: "twc-chart--scatter__title", x: padL + innerW / 2, y: H - 6, textAnchor: "middle", children: xLabel }) : null,
2533
- yLabel != null ? /* @__PURE__ */ jsx(
2534
- "text",
2535
- {
2536
- className: "twc-chart--scatter__title",
2537
- x: 16,
2538
- y: padT + innerH / 2,
2539
- textAnchor: "middle",
2540
- transform: `rotate(-90 16 ${padT + innerH / 2})`,
2541
- children: yLabel
2948
+ const focusActive = (si) => focus == null ? void 0 : si === focus || void 0;
2949
+ const clickPoint = (p) => {
2950
+ if (!onDataClick) return;
2951
+ onDataClick({ x: p.x, y: p.y, z: p.point.z, series: seriesName(list[p.si], p.si), seriesIndex: p.si, index: p.pi, point: p.point });
2952
+ };
2953
+ const selectPoint = (key) => setSelected((s) => s === key ? null : key);
2954
+ const clamp2 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
2955
+ const svgPoint = (e) => {
2956
+ const svg = svgRef.current;
2957
+ if (!svg || typeof svg.getScreenCTM !== "function" || typeof svg.createSVGPoint !== "function") return null;
2958
+ const ctm = svg.getScreenCTM();
2959
+ if (!ctm || !ctm.a || !ctm.d) return null;
2960
+ const pt = svg.createSVGPoint();
2961
+ pt.x = e.clientX;
2962
+ pt.y = e.clientY;
2963
+ const loc = pt.matrixTransform(ctm.inverse());
2964
+ if (!Number.isFinite(loc.x) || !Number.isFinite(loc.y)) return null;
2965
+ return { px: loc.x, py: loc.y };
2966
+ };
2967
+ const dataFromPx = (px, py) => {
2968
+ const cx = clamp2(px, padL, padL + innerW);
2969
+ const cy = clamp2(py, padT, padT + innerH);
2970
+ return {
2971
+ x: xScale.min + (cx - padL) / innerW * (xScale.max - xScale.min),
2972
+ y: yScale.min + (padT + innerH - cy) / innerH * (yScale.max - yScale.min)
2973
+ };
2974
+ };
2975
+ const nearestDot = (px, py) => {
2976
+ let best = null, bestD = Infinity;
2977
+ for (const p of dots) {
2978
+ const dx = xPos(p.x) - px, dy = yPos(p.y) - py;
2979
+ const d = dx * dx + dy * dy;
2980
+ const rr = Math.max(14, p.radius);
2981
+ if (d <= rr * rr && d < bestD) {
2982
+ bestD = d;
2983
+ best = p;
2984
+ }
2985
+ }
2986
+ return best;
2987
+ };
2988
+ const applyZoom = (win) => {
2989
+ const x0 = Math.max(Math.min(win.x0, win.x1), base.x0), x1 = Math.min(Math.max(win.x0, win.x1), base.x1);
2990
+ const y0 = Math.max(Math.min(win.y0, win.y1), base.y0), y1 = Math.min(Math.max(win.y0, win.y1), base.y1);
2991
+ if (!(x1 - x0 > 0) || !(y1 - y0 > 0)) return;
2992
+ if (x1 - x0 >= baseSpanX && y1 - y0 >= baseSpanY) {
2993
+ setZoom(null);
2994
+ return;
2995
+ }
2996
+ setZoom({ x0, x1, y0, y1 });
2997
+ };
2998
+ const panTo = (win) => {
2999
+ const wSpan = win.x1 - win.x0, hSpan = win.y1 - win.y0;
3000
+ let { x0, x1, y0, y1 } = win;
3001
+ if (x0 < base.x0) {
3002
+ x0 = base.x0;
3003
+ x1 = base.x0 + wSpan;
3004
+ } else if (x1 > base.x1) {
3005
+ x1 = base.x1;
3006
+ x0 = base.x1 - wSpan;
3007
+ }
3008
+ if (y0 < base.y0) {
3009
+ y0 = base.y0;
3010
+ y1 = base.y0 + hSpan;
3011
+ } else if (y1 > base.y1) {
3012
+ y1 = base.y1;
3013
+ y0 = base.y1 - hSpan;
3014
+ }
3015
+ setZoom({ x0, x1, y0, y1 });
3016
+ };
3017
+ const onOverlayDown = (e) => {
3018
+ const pt = svgPoint(e);
3019
+ if (!pt) return;
3020
+ movedRef.current = false;
3021
+ setDrag({ x0: pt.px, y0: pt.py, x1: pt.px, y1: pt.py, pan: e.shiftKey, z0: zoom || base });
3022
+ };
3023
+ const onOverlayMove = (e) => {
3024
+ const pt = svgPoint(e);
3025
+ if (!pt) return;
3026
+ if (drag) {
3027
+ if (Math.abs(pt.px - drag.x0) > 4 || Math.abs(pt.py - drag.y0) > 4) movedRef.current = true;
3028
+ if (drag.pan) {
3029
+ if (zoom) {
3030
+ const z0 = drag.z0;
3031
+ const dxData = -((pt.px - drag.x0) / innerW) * (z0.x1 - z0.x0);
3032
+ const dyData = (pt.py - drag.y0) / innerH * (z0.y1 - z0.y0);
3033
+ panTo({ x0: z0.x0 + dxData, x1: z0.x1 + dxData, y0: z0.y0 + dyData, y1: z0.y1 + dyData });
2542
3034
  }
2543
- ) : null,
2544
- dots.map((p, i) => /* @__PURE__ */ jsxs("g", { className: "twc-chart__anim-fade", children: [
2545
- /* @__PURE__ */ jsx(
2546
- "circle",
3035
+ } else {
3036
+ setDrag({ ...drag, x1: pt.px, y1: pt.py });
3037
+ }
3038
+ hide();
3039
+ return;
3040
+ }
3041
+ const hit = nearestDot(pt.px, pt.py);
3042
+ if (hit) show(tipFor(hit), e);
3043
+ else hide();
3044
+ };
3045
+ const onOverlayUp = () => {
3046
+ if (drag && !drag.pan) {
3047
+ const a = dataFromPx(drag.x0, drag.y0), b = dataFromPx(drag.x1, drag.y1);
3048
+ if (Math.abs(drag.x1 - drag.x0) > 4 && Math.abs(drag.y1 - drag.y0) > 4) applyZoom({ x0: a.x, x1: b.x, y0: a.y, y1: b.y });
3049
+ }
3050
+ setDrag(null);
3051
+ };
3052
+ const onOverlayLeave = () => {
3053
+ hide();
3054
+ setDrag(null);
3055
+ };
3056
+ const onOverlayClick = (e) => {
3057
+ if (movedRef.current) {
3058
+ movedRef.current = false;
3059
+ return;
3060
+ }
3061
+ const pt = svgPoint(e);
3062
+ if (!pt) return;
3063
+ const hit = nearestDot(pt.px, pt.py);
3064
+ if (hit) {
3065
+ clickPoint(hit);
3066
+ selectPoint(hit.key);
3067
+ }
3068
+ };
3069
+ React51__default.useEffect(() => {
3070
+ if (!canZoom) return void 0;
3071
+ const svg = svgRef.current;
3072
+ if (!svg) return void 0;
3073
+ const onWheel = (e) => {
3074
+ e.preventDefault();
3075
+ const pt = svgPoint(e);
3076
+ const fx = pt ? clamp2((pt.px - padL) / innerW, 0, 1) : 0.5;
3077
+ const fy = pt ? clamp2((padT + innerH - pt.py) / innerH, 0, 1) : 0.5;
3078
+ const factor = e.deltaY < 0 ? 0.8 : 1.25;
3079
+ const x0 = xScale.min, x1 = xScale.max, y0 = yScale.min, y1 = yScale.max;
3080
+ const cxv = x0 + fx * (x1 - x0), cyv = y0 + fy * (y1 - y0);
3081
+ const nw = (x1 - x0) * factor, nh = (y1 - y0) * factor;
3082
+ applyZoom({ x0: cxv - fx * nw, x1: cxv + (1 - fx) * nw, y0: cyv - fy * nh, y1: cyv + (1 - fy) * nh });
3083
+ };
3084
+ svg.addEventListener("wheel", onWheel, { passive: false });
3085
+ return () => svg.removeEventListener("wheel", onWheel);
3086
+ }, [canZoom, zoom, xScale.min, xScale.max, yScale.min, yScale.max]);
3087
+ const dragBand = canZoom && drag && !drag.pan && Math.abs(drag.x1 - drag.x0) > 2 && Math.abs(drag.y1 - drag.y0) > 2;
3088
+ return /* @__PURE__ */ jsxs(
3089
+ "div",
3090
+ {
3091
+ ref: containerRef,
3092
+ className: `twc-chart twc-chart--scatter ${className}`.trim(),
3093
+ "data-bubble": useBubble || void 0,
3094
+ "data-hovering": focus != null || void 0,
3095
+ "data-has-selection": selected != null || void 0,
3096
+ "data-clickable": clickable || void 0,
3097
+ ...rest,
3098
+ children: [
3099
+ baseStyles,
3100
+ styles,
3101
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, children: [
3102
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx("rect", { x: padL, y: padT, width: Math.max(0, innerW), height: Math.max(0, innerH) }) }) }),
3103
+ showGrid ? yScale.ticks.map((t, i) => {
3104
+ const p = yPos(t);
3105
+ return /* @__PURE__ */ jsx("line", { className: "twc-chart--scatter__grid", x1: padL, y1: p, x2: W - padR, y2: p }, `y${i}`);
3106
+ }) : null,
3107
+ showGrid ? xScale.ticks.map((t, i) => {
3108
+ const p = xPos(t);
3109
+ return /* @__PURE__ */ jsx("line", { className: "twc-chart--scatter__grid", x1: p, y1: padT, x2: p, y2: padT + innerH }, `x${i}`);
3110
+ }) : null,
3111
+ yScale.ticks.map((t, i) => /* @__PURE__ */ jsx("text", { className: "twc-chart--scatter__axis", x: padL - 8, y: yPos(t) + 4, textAnchor: "end", children: shortNum(t) }, `yt${i}`)),
3112
+ xScale.ticks.map((t, i) => /* @__PURE__ */ jsx("text", { className: "twc-chart--scatter__axis", x: xPos(t), y: padT + innerH + 16, textAnchor: "middle", children: shortNum(t) }, `xt${i}`)),
3113
+ xLabel != null ? /* @__PURE__ */ jsx("text", { className: "twc-chart--scatter__title", x: padL + innerW / 2, y: H - 6, textAnchor: "middle", children: xLabel }) : null,
3114
+ yLabel != null ? /* @__PURE__ */ jsx(
3115
+ "text",
3116
+ {
3117
+ className: "twc-chart--scatter__title",
3118
+ x: 16,
3119
+ y: padT + innerH / 2,
3120
+ textAnchor: "middle",
3121
+ transform: `rotate(-90 16 ${padT + innerH / 2})`,
3122
+ children: yLabel
3123
+ }
3124
+ ) : null,
3125
+ /* @__PURE__ */ jsx("g", { clipPath: canZoom && zoom ? `url(#${clipId})` : void 0, children: dots.map((p) => /* @__PURE__ */ jsxs("g", { className: "twc-chart__anim-fade", children: [
3126
+ /* @__PURE__ */ jsx(
3127
+ "circle",
3128
+ {
3129
+ className: "twc-chart__hit",
3130
+ cx: xPos(p.x),
3131
+ cy: yPos(p.y),
3132
+ r: Math.max(14, p.radius),
3133
+ onMouseMove: (e) => show(tipFor(p), e),
3134
+ onMouseLeave: hide,
3135
+ onClick: () => {
3136
+ clickPoint(p);
3137
+ selectPoint(p.key);
3138
+ }
3139
+ }
3140
+ ),
3141
+ /* @__PURE__ */ jsx(
3142
+ "circle",
3143
+ {
3144
+ className: "twc-chart--scatter__dot",
3145
+ "data-mark": true,
3146
+ "data-active": focusActive(p.si),
3147
+ "data-selected": selected === p.key || void 0,
3148
+ "data-bubble": useBubble || void 0,
3149
+ style: { fill: p.fill },
3150
+ cx: xPos(p.x),
3151
+ cy: yPos(p.y),
3152
+ r: p.radius
3153
+ }
3154
+ )
3155
+ ] }, p.key)) }),
3156
+ dragBand ? /* @__PURE__ */ jsx(
3157
+ "rect",
3158
+ {
3159
+ className: "twc-chart__zoomband",
3160
+ x: Math.min(drag.x0, drag.x1),
3161
+ y: Math.min(drag.y0, drag.y1),
3162
+ width: Math.abs(drag.x1 - drag.x0),
3163
+ height: Math.abs(drag.y1 - drag.y0)
3164
+ }
3165
+ ) : null,
3166
+ canZoom ? /* @__PURE__ */ jsx(
3167
+ "rect",
3168
+ {
3169
+ className: "twc-chart__overlay",
3170
+ "data-clickable": clickable || void 0,
3171
+ x: padL,
3172
+ y: padT,
3173
+ width: Math.max(0, innerW),
3174
+ height: Math.max(0, innerH),
3175
+ onMouseDown: onOverlayDown,
3176
+ onMouseMove: onOverlayMove,
3177
+ onMouseUp: onOverlayUp,
3178
+ onMouseLeave: onOverlayLeave,
3179
+ onClick: onOverlayClick
3180
+ }
3181
+ ) : null
3182
+ ] }),
3183
+ canZoom && zoom ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-chart__zoom-reset", onClick: () => setZoom(null), "aria-label": "Reset zoom", children: [
3184
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3185
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
3186
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
3187
+ ] }),
3188
+ "Reset"
3189
+ ] }) : null,
3190
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
3191
+ tableFallback ? /* @__PURE__ */ jsx(
3192
+ ChartTable,
2547
3193
  {
2548
- className: "twc-chart__hit",
2549
- cx: xPos(p.x),
2550
- cy: yPos(p.y),
2551
- r: Math.max(14, p.radius),
2552
- onMouseMove: (e) => show(tipFor(p), e),
2553
- onMouseLeave: hide
3194
+ id: tableId,
3195
+ caption: caption ?? svgAriaLabel,
3196
+ columns: tableColumns,
3197
+ rows: all.map((p) => ({
3198
+ label: seriesName(list[p.si], p.si),
3199
+ values: hasZ ? [xFmt(p.x), yFmt(p.y), Number.isFinite(Number(p.z)) ? vFmt(Number(p.z)) : ""] : [xFmt(p.x), yFmt(p.y)]
3200
+ }))
2554
3201
  }
2555
- ),
2556
- /* @__PURE__ */ jsx(
2557
- "circle",
3202
+ ) : null,
3203
+ legend && list.length ? /* @__PURE__ */ jsx(
3204
+ ChartLegend,
2558
3205
  {
2559
- className: "twc-chart--scatter__dot",
2560
- "data-bubble": useBubble || void 0,
2561
- style: { fill: p.fill },
2562
- cx: xPos(p.x),
2563
- cy: yPos(p.y),
2564
- r: p.radius
3206
+ items: list.map((s, i) => ({ label: seriesName(s, i), color: seriesColor(s, i) })),
3207
+ onFocus: multi ? (it, i) => setFocus(i) : void 0,
3208
+ onBlur: multi ? () => setFocus(null) : void 0
2565
3209
  }
2566
- )
2567
- ] }, i))
2568
- ] }),
2569
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2570
- tableFallback ? /* @__PURE__ */ jsx(
2571
- ChartTable,
2572
- {
2573
- id: tableId,
2574
- caption: caption ?? svgAriaLabel,
2575
- columns: tableColumns,
2576
- rows: all.map((p) => ({
2577
- label: seriesName(list[p.si], p.si),
2578
- values: hasZ ? [xFmt(p.x), yFmt(p.y), Number.isFinite(Number(p.z)) ? vFmt(Number(p.z)) : ""] : [xFmt(p.x), yFmt(p.y)]
2579
- }))
2580
- }
2581
- ) : null,
2582
- legend && list.length ? /* @__PURE__ */ jsx(ChartLegend, { items: list.map((s, i) => ({ label: seriesName(s, i), color: seriesColor(s, i) })) }) : null
2583
- ] });
3210
+ ) : null
3211
+ ]
3212
+ }
3213
+ );
2584
3214
  }
2585
3215
  function labelText2(label) {
2586
3216
  return typeof label === "string" || typeof label === "number" ? String(label) : "";
@@ -2612,6 +3242,7 @@ function RadarChart({
2612
3242
  colors,
2613
3243
  valueFormat,
2614
3244
  height = 300,
3245
+ onDataClick,
2615
3246
  ariaLabel,
2616
3247
  "aria-label": ariaLabelProp,
2617
3248
  tableFallback = true,
@@ -2624,11 +3255,16 @@ function RadarChart({
2624
3255
  const uid = React51__default.useId();
2625
3256
  const tableId = tableFallback ? `${uid}-table` : void 0;
2626
3257
  const { containerRef, tip, show, hide } = useChartTooltip();
3258
+ const [focus, setFocus] = React51__default.useState(null);
3259
+ const [selected, setSelected] = React51__default.useState(null);
2627
3260
  const rows = data || [];
2628
3261
  const keys = series && series.length ? series : ["value"];
2629
3262
  const fmt = valueFormat || fmtNumber;
2630
3263
  const multi = keys.length > 1;
3264
+ const clickable = !!onDataClick;
2631
3265
  const legend = showLegend != null ? showLegend : multi;
3266
+ const focusActive = (k) => focus == null ? void 0 : k === focus || void 0;
3267
+ const selectMark = (key) => setSelected((s) => s === key ? null : key);
2632
3268
  const H = height;
2633
3269
  const cx = H / 2, cy = H / 2;
2634
3270
  const R = Math.max(0, H / 2 - 40);
@@ -2642,69 +3278,106 @@ function RadarChart({
2642
3278
  const frac = Math.max(0, Math.min(1, (Number(v) || 0) / radialMax));
2643
3279
  return polarDeg(cx, cy, frac * R, angleOf(i));
2644
3280
  };
2645
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--radar ${className}`.trim(), ...rest, children: [
2646
- baseStyles,
2647
- styles,
2648
- /* @__PURE__ */ jsx(
2649
- "svg",
2650
- {
2651
- viewBox: `0 0 ${H} ${H}`,
2652
- role: "img",
2653
- "aria-label": svgAriaLabel,
2654
- "aria-describedby": tableId,
2655
- style: { maxWidth: H, margin: "0 auto" },
2656
- children: n === 0 ? /* @__PURE__ */ jsx("text", { className: "twc-chart__empty", x: cx, y: cy, textAnchor: "middle", dominantBaseline: "middle", children: "No data" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2657
- Array.from({ length: rings }, (_, li) => {
2658
- const rr = R * (li + 1) / rings;
2659
- const pts = rows.map((_2, i) => polarDeg(cx, cy, rr, angleOf(i)));
2660
- return /* @__PURE__ */ jsx("path", { className: "twc-chart__ring", d: polygonPath(pts) }, li);
2661
- }),
2662
- rows.map((d, i) => {
2663
- const [ex, ey] = polarDeg(cx, cy, R, angleOf(i));
2664
- const [lx, ly] = polarDeg(cx, cy, R + 16, angleOf(i));
2665
- const anchor = lx > cx + 1 ? "start" : lx < cx - 1 ? "end" : "middle";
2666
- return /* @__PURE__ */ jsxs("g", { children: [
2667
- /* @__PURE__ */ jsx("line", { className: "twc-chart__spoke", x1: cx, y1: cy, x2: ex, y2: ey }),
2668
- /* @__PURE__ */ jsx("text", { className: "twc-chart__radar-axis", x: lx, y: ly, textAnchor: anchor, dominantBaseline: "middle", children: d.label })
2669
- ] }, i);
2670
- }),
2671
- keys.map((k, si) => {
2672
- const color = paletteAt(colors, si);
2673
- const pts = rows.map((d, i) => vertex(d[k], i));
2674
- return /* @__PURE__ */ jsxs("g", { children: [
2675
- fill ? /* @__PURE__ */ jsx("path", { className: "twc-chart__radar-area twc-chart__anim-fade", style: { fill: color, opacity: 0.18 }, d: polygonPath(pts) }) : null,
2676
- /* @__PURE__ */ jsx("path", { className: "twc-chart__radar-line twc-chart__anim-fade", style: { stroke: color }, d: polygonPath(pts) }),
2677
- showDots ? pts.map((p, i) => /* @__PURE__ */ jsxs("g", { className: "twc-chart__anim-fade", children: [
2678
- /* @__PURE__ */ jsx(
2679
- "circle",
2680
- {
2681
- className: "twc-chart__hit",
2682
- cx: p[0],
2683
- cy: p[1],
2684
- r: "14",
2685
- onMouseMove: (e) => show({ title: rows[i].label, items: [{ color, label: k, value: fmt(Number(rows[i][k]) || 0) }] }, e),
2686
- onMouseLeave: hide
2687
- }
2688
- ),
2689
- /* @__PURE__ */ jsx("circle", { className: "twc-chart__radar-dot", style: { fill: color }, cx: p[0], cy: p[1], r: "3" })
2690
- ] }, i)) : null
2691
- ] }, k);
2692
- })
2693
- ] })
2694
- }
2695
- ),
2696
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2697
- tableFallback ? /* @__PURE__ */ jsx(
2698
- ChartTable,
2699
- {
2700
- id: tableId,
2701
- caption: caption ?? svgAriaLabel,
2702
- columns: keys,
2703
- rows: rows.map((d) => ({ label: d.label, values: keys.map((k) => fmt(Number(d[k]) || 0)) }))
2704
- }
2705
- ) : null,
2706
- legend && multi ? /* @__PURE__ */ jsx(ChartLegend, { items: keys.map((k, si) => ({ label: k, color: paletteAt(colors, si) })) }) : null
2707
- ] });
3281
+ return /* @__PURE__ */ jsxs(
3282
+ "div",
3283
+ {
3284
+ ref: containerRef,
3285
+ className: `twc-chart twc-chart--radar ${className}`.trim(),
3286
+ "data-hovering": focus != null || void 0,
3287
+ "data-clickable": clickable || void 0,
3288
+ "data-has-selection": selected != null || void 0,
3289
+ ...rest,
3290
+ children: [
3291
+ baseStyles,
3292
+ styles,
3293
+ /* @__PURE__ */ jsx(
3294
+ "svg",
3295
+ {
3296
+ viewBox: `0 0 ${H} ${H}`,
3297
+ role: "img",
3298
+ "aria-label": svgAriaLabel,
3299
+ "aria-describedby": tableId,
3300
+ style: { maxWidth: H, margin: "0 auto" },
3301
+ children: n === 0 ? /* @__PURE__ */ jsx("text", { className: "twc-chart__empty", x: cx, y: cy, textAnchor: "middle", dominantBaseline: "middle", children: "No data" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3302
+ Array.from({ length: rings }, (_, li) => {
3303
+ const rr = R * (li + 1) / rings;
3304
+ const pts = rows.map((_2, i) => polarDeg(cx, cy, rr, angleOf(i)));
3305
+ return /* @__PURE__ */ jsx("path", { className: "twc-chart__ring", d: polygonPath(pts) }, li);
3306
+ }),
3307
+ rows.map((d, i) => {
3308
+ const [ex, ey] = polarDeg(cx, cy, R, angleOf(i));
3309
+ const [lx, ly] = polarDeg(cx, cy, R + 16, angleOf(i));
3310
+ const anchor = lx > cx + 1 ? "start" : lx < cx - 1 ? "end" : "middle";
3311
+ return /* @__PURE__ */ jsxs("g", { children: [
3312
+ /* @__PURE__ */ jsx("line", { className: "twc-chart__spoke", x1: cx, y1: cy, x2: ex, y2: ey }),
3313
+ /* @__PURE__ */ jsx("text", { className: "twc-chart__radar-axis", x: lx, y: ly, textAnchor: anchor, dominantBaseline: "middle", children: d.label })
3314
+ ] }, i);
3315
+ }),
3316
+ keys.map((k, si) => {
3317
+ const color = paletteAt(colors, si);
3318
+ const pts = rows.map((d, i) => vertex(d[k], i));
3319
+ return /* @__PURE__ */ jsxs("g", { "data-mark": true, "data-active": focusActive(k), children: [
3320
+ fill ? /* @__PURE__ */ jsx("path", { className: "twc-chart__radar-area twc-chart__anim-fade", style: { fill: color, opacity: 0.18 }, d: polygonPath(pts) }) : null,
3321
+ /* @__PURE__ */ jsx("path", { className: "twc-chart__radar-line twc-chart__anim-fade", style: { stroke: color }, d: polygonPath(pts) }),
3322
+ showDots ? pts.map((p, i) => {
3323
+ const key = `${i}:${k}`;
3324
+ return /* @__PURE__ */ jsxs("g", { className: "twc-chart__anim-fade", children: [
3325
+ /* @__PURE__ */ jsx(
3326
+ "circle",
3327
+ {
3328
+ className: "twc-chart__hit",
3329
+ cx: p[0],
3330
+ cy: p[1],
3331
+ r: "14",
3332
+ onMouseMove: (e) => show({ title: rows[i].label, items: [{ color, label: k, value: fmt(Number(rows[i][k]) || 0) }] }, e),
3333
+ onMouseLeave: hide,
3334
+ onClick: () => {
3335
+ if (onDataClick) onDataClick({ label: rows[i].label, series: k, value: Number(rows[i][k]) || 0, index: i });
3336
+ selectMark(key);
3337
+ }
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsx(
3341
+ "circle",
3342
+ {
3343
+ className: "twc-chart__radar-dot",
3344
+ style: { fill: color },
3345
+ cx: p[0],
3346
+ cy: p[1],
3347
+ r: "3",
3348
+ "data-mark": true,
3349
+ "data-active": focusActive(k),
3350
+ "data-selected": selected === key || void 0
3351
+ }
3352
+ )
3353
+ ] }, i);
3354
+ }) : null
3355
+ ] }, k);
3356
+ })
3357
+ ] })
3358
+ }
3359
+ ),
3360
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
3361
+ tableFallback ? /* @__PURE__ */ jsx(
3362
+ ChartTable,
3363
+ {
3364
+ id: tableId,
3365
+ caption: caption ?? svgAriaLabel,
3366
+ columns: keys,
3367
+ rows: rows.map((d) => ({ label: d.label, values: keys.map((k) => fmt(Number(d[k]) || 0)) }))
3368
+ }
3369
+ ) : null,
3370
+ legend && multi ? /* @__PURE__ */ jsx(
3371
+ ChartLegend,
3372
+ {
3373
+ items: keys.map((k, si) => ({ label: k, color: paletteAt(colors, si) })),
3374
+ onFocus: (it) => setFocus(it.label),
3375
+ onBlur: () => setFocus(null)
3376
+ }
3377
+ ) : null
3378
+ ]
3379
+ }
3380
+ );
2708
3381
  }
2709
3382
  var CHART_CSS4 = `
2710
3383
  .twc-chart--polar svg { margin-inline: auto; }
@@ -2723,6 +3396,7 @@ function PolarAreaChart({
2723
3396
  valueFormat,
2724
3397
  height = 280,
2725
3398
  colors,
3399
+ onDataClick,
2726
3400
  ariaLabel,
2727
3401
  "aria-label": ariaLabelProp,
2728
3402
  tableFallback = true,
@@ -2736,6 +3410,8 @@ function PolarAreaChart({
2736
3410
  const tableId = tableFallback ? `${uid}-table` : void 0;
2737
3411
  const { containerRef, tip, show, hide } = useChartTooltip();
2738
3412
  const [active, setActive] = React51__default.useState(null);
3413
+ const [focus, setFocus] = React51__default.useState(null);
3414
+ const [selected, setSelected] = React51__default.useState(null);
2739
3415
  const rows = data || [];
2740
3416
  const n = rows.length;
2741
3417
  const fmt = valueFormat || fmtNumber;
@@ -2750,14 +3426,23 @@ function PolarAreaChart({
2750
3426
  };
2751
3427
  const rings = Math.max(1, Math.floor(levels));
2752
3428
  const anglePer = 360 / Math.max(1, n);
3429
+ const EXPLODE = Math.min(12, Rmax * 0.1);
2753
3430
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? "polar area chart";
3431
+ const clickable = !!onDataClick;
3432
+ const emphasis = focus != null ? focus : active;
2754
3433
  const labelText8 = (label) => typeof label === "string" || typeof label === "number" ? String(label) : "";
3434
+ const clickSlice = (d, v, i) => {
3435
+ if (onDataClick) onDataClick({ label: d.label, value: v, index: i });
3436
+ setSelected((s) => s === i ? null : i);
3437
+ };
2755
3438
  return /* @__PURE__ */ jsxs(
2756
3439
  "div",
2757
3440
  {
2758
3441
  ref: containerRef,
2759
3442
  className: `twc-chart twc-chart--polar ${className}`.trim(),
2760
- "data-hovering": active != null || void 0,
3443
+ "data-hovering": emphasis != null || void 0,
3444
+ "data-clickable": clickable || void 0,
3445
+ "data-has-selection": selected != null || void 0,
2761
3446
  ...rest,
2762
3447
  children: [
2763
3448
  baseStyles,
@@ -2786,13 +3471,16 @@ function PolarAreaChart({
2786
3471
  const start = startAngle + i * anglePer;
2787
3472
  const color = d.color || paletteAt(colors, i);
2788
3473
  const tipFor = { title: labelText8(d.label), items: [{ color, label: "", value: fmt(v) }] };
3474
+ const midRad = (start + anglePer / 2 - 90) * Math.PI / 180;
3475
+ const explode = selected === i ? `translate(${r(Math.cos(midRad) * EXPLODE)}px, ${r(Math.sin(midRad) * EXPLODE)}px)` : void 0;
2789
3476
  return /* @__PURE__ */ jsx(
2790
3477
  "path",
2791
3478
  {
2792
3479
  className: "twc-chart__slice twc-chart__anim-arc",
2793
3480
  "data-mark": true,
2794
- "data-active": active === i || void 0,
2795
- style: { fill: color, fillOpacity: 0.82 },
3481
+ "data-active": emphasis == null ? void 0 : emphasis === i || void 0,
3482
+ "data-selected": selected === i || void 0,
3483
+ style: { fill: color, fillOpacity: 0.82, transform: explode },
2796
3484
  d: arcPath(cx, cy, radiusFor(v), 0, start, start + anglePer),
2797
3485
  onMouseMove: (e) => {
2798
3486
  setActive(i);
@@ -2801,7 +3489,8 @@ function PolarAreaChart({
2801
3489
  onMouseLeave: () => {
2802
3490
  setActive(null);
2803
3491
  hide();
2804
- }
3492
+ },
3493
+ onClick: () => clickSlice(d, v, i)
2805
3494
  },
2806
3495
  `slice-${i}`
2807
3496
  );
@@ -2824,7 +3513,14 @@ function PolarAreaChart({
2824
3513
  rows: rows.map((d) => ({ label: d.label, values: [fmt(Number(d.value) || 0)] }))
2825
3514
  }
2826
3515
  ) : null,
2827
- showLegend && n ? /* @__PURE__ */ jsx(ChartLegend, { items: rows.map((d, i) => ({ label: d.label, color: d.color || paletteAt(colors, i) })) }) : null
3516
+ showLegend && n ? /* @__PURE__ */ jsx(
3517
+ ChartLegend,
3518
+ {
3519
+ items: rows.map((d, i) => ({ label: d.label, color: d.color || paletteAt(colors, i) })),
3520
+ onFocus: n > 1 ? (_it, i) => setFocus(i) : void 0,
3521
+ onBlur: n > 1 ? () => setFocus(null) : void 0
3522
+ }
3523
+ ) : null
2828
3524
  ]
2829
3525
  }
2830
3526
  );
@@ -2834,7 +3530,7 @@ var HEATMAP_CSS = `
2834
3530
  .twc-chart__hm-cell { transition: opacity var(--duration-fast) var(--ease-standard); }
2835
3531
  .twc-chart__hm-cell:hover { opacity: 0.82; }
2836
3532
  .twc-chart__hm-empty { fill: var(--color-surface-sunken); }
2837
- .twc-chart__hm-val { fill: var(--color-text); font-size: 10px; font-weight: 500; pointer-events: none; }
3533
+ .twc-chart__hm-val { fill: var(--color-text); paint-order: stroke; stroke: var(--color-surface); stroke-width: 2.5px; stroke-linejoin: round; vector-effect: non-scaling-stroke; font-size: 10px; font-weight: 500; pointer-events: none; }
2838
3534
  .twc-chart__hm-tick { fill: var(--color-text-subtle); font-size: 11px; }
2839
3535
  .twc-chart__hm-title { fill: var(--color-text-muted); font-size: 11px; font-weight: 500; }
2840
3536
  .twc-chart__hm-scale { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); font-size: var(--text-xs); color: var(--color-text-muted); }
@@ -2852,8 +3548,10 @@ function Heatmap({
2852
3548
  xLabel,
2853
3549
  yLabel,
2854
3550
  showLegend = true,
3551
+ zoomable = false,
2855
3552
  valueFormat,
2856
3553
  height = 300,
3554
+ onDataClick,
2857
3555
  ariaLabel,
2858
3556
  "aria-label": ariaLabelProp,
2859
3557
  tableFallback = true,
@@ -2867,10 +3565,17 @@ function Heatmap({
2867
3565
  const tableId = tableFallback ? `${uid}-table` : void 0;
2868
3566
  const { containerRef, tip, show, hide } = useChartTooltip();
2869
3567
  const [hovered, setHovered] = React51__default.useState(null);
3568
+ const [selected, setSelected] = React51__default.useState(null);
3569
+ const [zoom, setZoom] = React51__default.useState(null);
3570
+ const [drag, setDrag] = React51__default.useState(null);
3571
+ const svgRef = React51__default.useRef(null);
3572
+ const movedRef = React51__default.useRef(false);
3573
+ const clickable = !!onDataClick;
3574
+ const selectMark = (key) => setSelected((s) => s === key ? null : key);
2870
3575
  const rows = Array.isArray(data) ? data : [];
2871
3576
  const fmt = valueFormat || fmtNumber;
2872
- const xs = [];
2873
- const ys = [];
3577
+ const fullXs = [];
3578
+ const fullYs = [];
2874
3579
  const seenX = /* @__PURE__ */ new Set();
2875
3580
  const seenY = /* @__PURE__ */ new Set();
2876
3581
  const lookup = /* @__PURE__ */ new Map();
@@ -2879,11 +3584,11 @@ function Heatmap({
2879
3584
  const ky = String(d.y);
2880
3585
  if (!seenX.has(kx)) {
2881
3586
  seenX.add(kx);
2882
- xs.push(d.x);
3587
+ fullXs.push(d.x);
2883
3588
  }
2884
3589
  if (!seenY.has(ky)) {
2885
3590
  seenY.add(ky);
2886
- ys.push(d.y);
3591
+ fullYs.push(d.y);
2887
3592
  }
2888
3593
  lookup.set(`${kx}\0${ky}`, Number(d.value) || 0);
2889
3594
  }
@@ -2896,6 +3601,15 @@ function Heatmap({
2896
3601
  const span = hi - lo;
2897
3602
  const pctOf = (v) => span > 0 ? clamp((v - lo) / span * 100, 0, 100) : 60;
2898
3603
  const fillOf = (v) => `color-mix(in srgb, ${colorScale} ${pctOf(v)}%, transparent)`;
3604
+ const nX = fullXs.length;
3605
+ const nY = fullYs.length;
3606
+ const canZoom = zoomable && nX > 1 && nY > 1;
3607
+ const vc0 = canZoom && zoom ? clamp(zoom.c0, 0, nX - 1) : 0;
3608
+ const vc1 = canZoom && zoom ? clamp(zoom.c1, 0, nX - 1) : Math.max(0, nX - 1);
3609
+ const vr0 = canZoom && zoom ? clamp(zoom.r0, 0, nY - 1) : 0;
3610
+ const vr1 = canZoom && zoom ? clamp(zoom.r1, 0, nY - 1) : Math.max(0, nY - 1);
3611
+ const xs = fullXs.slice(vc0, vc1 + 1);
3612
+ const ys = fullYs.slice(vr0, vr1 + 1);
2899
3613
  const cols = xs.length;
2900
3614
  const rowN = ys.length;
2901
3615
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? "heatmap";
@@ -2914,10 +3628,137 @@ function Heatmap({
2914
3628
  const cw = innerW / Math.max(1, cols);
2915
3629
  const chh = innerH / Math.max(1, rowN);
2916
3630
  const gap = clamp(cellGap, 0, Math.min(cw, chh) - 1) || 0;
2917
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--heatmap ${className}`.trim(), "data-hovering": hovered != null || void 0, ...rest, children: [
3631
+ const svgPoint = (e) => {
3632
+ const svg = svgRef.current;
3633
+ if (!svg || typeof svg.getScreenCTM !== "function" || typeof svg.createSVGPoint !== "function") return null;
3634
+ const ctm = svg.getScreenCTM();
3635
+ if (!ctm || !ctm.a || !ctm.d) return null;
3636
+ const pt = svg.createSVGPoint();
3637
+ pt.x = e.clientX;
3638
+ pt.y = e.clientY;
3639
+ const loc = pt.matrixTransform(ctm.inverse());
3640
+ if (!Number.isFinite(loc.x) || !Number.isFinite(loc.y)) return null;
3641
+ return { px: loc.x, py: loc.y };
3642
+ };
3643
+ const colAtPx = (px) => cw > 0 ? clamp(Math.floor((clamp(px, padL, padL + innerW) - padL) / cw), 0, cols - 1) : 0;
3644
+ const rowAtPx = (py) => chh > 0 ? clamp(Math.floor((clamp(py, padT, padT + innerH) - padT) / chh), 0, rowN - 1) : 0;
3645
+ const cellAtPx = (px, py) => {
3646
+ if (!(cols > 0) || !(rowN > 0)) return null;
3647
+ if (px < padL || px > padL + innerW || py < padT || py > padT + innerH) return null;
3648
+ const ci = colAtPx(px);
3649
+ const ri = rowAtPx(py);
3650
+ const x = xs[ci], y = ys[ri];
3651
+ const v = cellOf(x, y);
3652
+ const has = v !== void 0;
3653
+ return { x, y, v, has, key: `${vc0 + ci}-${vr0 + ri}`, fill: has ? fillOf(v) : void 0 };
3654
+ };
3655
+ const applyZoom = (win) => {
3656
+ const c0 = clamp(Math.min(win.c0, win.c1), 0, nX - 1);
3657
+ const c1 = clamp(Math.max(win.c0, win.c1), 0, nX - 1);
3658
+ const r0 = clamp(Math.min(win.r0, win.r1), 0, nY - 1);
3659
+ const r1 = clamp(Math.max(win.r0, win.r1), 0, nY - 1);
3660
+ if (!(c1 >= c0) || !(r1 >= r0)) return;
3661
+ if (c0 === 0 && c1 === nX - 1 && r0 === 0 && r1 === nY - 1) {
3662
+ setZoom(null);
3663
+ return;
3664
+ }
3665
+ setZoom({ c0, c1, r0, r1 });
3666
+ };
3667
+ const panTo = (z0, dCols, dRows) => {
3668
+ const wc = z0.c1 - z0.c0, wr = z0.r1 - z0.r0;
3669
+ const c0 = clamp(z0.c0 + dCols, 0, Math.max(0, nX - 1 - wc));
3670
+ const r0 = clamp(z0.r0 + dRows, 0, Math.max(0, nY - 1 - wr));
3671
+ setZoom({ c0, c1: c0 + wc, r0, r1: r0 + wr });
3672
+ };
3673
+ const onOverlayDown = (e) => {
3674
+ const pt = svgPoint(e);
3675
+ if (!pt) return;
3676
+ movedRef.current = false;
3677
+ setDrag({ x0: pt.px, y0: pt.py, x1: pt.px, y1: pt.py, pan: e.shiftKey, z0: zoom || { c0: 0, c1: nX - 1, r0: 0, r1: nY - 1 } });
3678
+ };
3679
+ const onOverlayMove = (e) => {
3680
+ const pt = svgPoint(e);
3681
+ if (!pt) return;
3682
+ if (drag) {
3683
+ if (Math.abs(pt.px - drag.x0) > 4 || Math.abs(pt.py - drag.y0) > 4) movedRef.current = true;
3684
+ if (drag.pan) {
3685
+ if (zoom) {
3686
+ const dCols = cw > 0 ? -Math.round((pt.px - drag.x0) / cw) : 0;
3687
+ const dRows = chh > 0 ? -Math.round((pt.py - drag.y0) / chh) : 0;
3688
+ panTo(drag.z0, dCols, dRows);
3689
+ }
3690
+ } else {
3691
+ setDrag({ ...drag, x1: pt.px, y1: pt.py });
3692
+ }
3693
+ setHovered(null);
3694
+ hide();
3695
+ return;
3696
+ }
3697
+ const cell = cellAtPx(pt.px, pt.py);
3698
+ if (cell) {
3699
+ setHovered(cell.key);
3700
+ show({ title: `${labelText3(cell.y)} / ${labelText3(cell.x)}`, items: [{ color: cell.fill, label: "", value: cell.has ? fmt(cell.v) : "\u2014" }] }, e);
3701
+ } else {
3702
+ setHovered(null);
3703
+ hide();
3704
+ }
3705
+ };
3706
+ const onOverlayUp = () => {
3707
+ if (drag && !drag.pan && movedRef.current) {
3708
+ applyZoom({
3709
+ c0: vc0 + colAtPx(drag.x0),
3710
+ c1: vc0 + colAtPx(drag.x1),
3711
+ r0: vr0 + rowAtPx(drag.y0),
3712
+ r1: vr0 + rowAtPx(drag.y1)
3713
+ });
3714
+ }
3715
+ setDrag(null);
3716
+ };
3717
+ const onOverlayLeave = () => {
3718
+ setHovered(null);
3719
+ hide();
3720
+ setDrag(null);
3721
+ };
3722
+ const onOverlayClick = (e) => {
3723
+ if (movedRef.current) {
3724
+ movedRef.current = false;
3725
+ return;
3726
+ }
3727
+ const pt = svgPoint(e);
3728
+ if (!pt) return;
3729
+ const cell = cellAtPx(pt.px, pt.py);
3730
+ if (cell && cell.has) {
3731
+ if (onDataClick) onDataClick({ x: cell.x, y: cell.y, value: cell.v });
3732
+ selectMark(cell.key);
3733
+ }
3734
+ };
3735
+ React51__default.useEffect(() => {
3736
+ if (!canZoom) return void 0;
3737
+ const svg = svgRef.current;
3738
+ if (!svg) return void 0;
3739
+ const onWheel = (e) => {
3740
+ e.preventDefault();
3741
+ const pt = svgPoint(e);
3742
+ const fx = pt && innerW > 0 ? clamp((pt.px - padL) / innerW, 0, 1) : 0.5;
3743
+ const fy = pt && innerH > 0 ? clamp((pt.py - padT) / innerH, 0, 1) : 0.5;
3744
+ const factor = e.deltaY < 0 ? 0.7 : 1.4;
3745
+ const wc = vc1 - vc0 + 1, wr = vr1 - vr0 + 1;
3746
+ const newWc = clamp(Math.round(wc * factor), 1, nX);
3747
+ const newWr = clamp(Math.round(wr * factor), 1, nY);
3748
+ const curCol = vc0 + clamp(Math.floor(fx * cols), 0, cols - 1);
3749
+ const curRow = vr0 + clamp(Math.floor(fy * rowN), 0, rowN - 1);
3750
+ const nc0 = clamp(Math.round(curCol - fx * (newWc - 1)), 0, nX - newWc);
3751
+ const nr0 = clamp(Math.round(curRow - fy * (newWr - 1)), 0, nY - newWr);
3752
+ applyZoom({ c0: nc0, c1: nc0 + newWc - 1, r0: nr0, r1: nr0 + newWr - 1 });
3753
+ };
3754
+ svg.addEventListener("wheel", onWheel, { passive: false });
3755
+ return () => svg.removeEventListener("wheel", onWheel);
3756
+ }, [canZoom, vc0, vc1, vr0, vr1]);
3757
+ const dragBand = canZoom && drag && !drag.pan && Math.abs(drag.x1 - drag.x0) > 2 && Math.abs(drag.y1 - drag.y0) > 2;
3758
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--heatmap ${className}`.trim(), "data-hovering": hovered != null || void 0, "data-has-selection": selected != null || void 0, "data-clickable": clickable || void 0, ...rest, children: [
2918
3759
  baseStyles,
2919
3760
  styles,
2920
- /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
3761
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
2921
3762
  ys.map(
2922
3763
  (y, ri) => xs.map((x, ci) => {
2923
3764
  const v = cellOf(x, y);
@@ -2926,7 +3767,7 @@ function Heatmap({
2926
3767
  const cy = padT + chh * ri + gap / 2;
2927
3768
  const cwPx = Math.max(0, cw - gap);
2928
3769
  const chPx = Math.max(0, chh - gap);
2929
- const cellKey = `${ci}-${ri}`;
3770
+ const cellKey = `${vc0 + ci}-${vr0 + ri}`;
2930
3771
  const fill = has ? fillOf(v) : void 0;
2931
3772
  return /* @__PURE__ */ jsxs("g", { children: [
2932
3773
  /* @__PURE__ */ jsx(
@@ -2941,6 +3782,7 @@ function Heatmap({
2941
3782
  style: has ? { fill } : void 0,
2942
3783
  "data-mark": true,
2943
3784
  "data-active": hovered === cellKey || void 0,
3785
+ "data-selected": selected === cellKey || void 0,
2944
3786
  onMouseMove: (e) => {
2945
3787
  setHovered(cellKey);
2946
3788
  show({ title: `${labelText3(y)} / ${labelText3(x)}`, items: [{ color: fill, label: "", value: has ? fmt(v) : "\u2014" }] }, e);
@@ -2948,7 +3790,11 @@ function Heatmap({
2948
3790
  onMouseLeave: () => {
2949
3791
  setHovered(null);
2950
3792
  hide();
2951
- }
3793
+ },
3794
+ onClick: has ? () => {
3795
+ if (onDataClick) onDataClick({ x, y, value: v });
3796
+ selectMark(cellKey);
3797
+ } : void 0
2952
3798
  }
2953
3799
  ),
2954
3800
  showValues && has ? /* @__PURE__ */ jsx("text", { className: "twc-chart__hm-val", x: cx + cwPx / 2, y: cy + chPx / 2 + 3.5, textAnchor: "middle", children: fmt(v) }) : null
@@ -2958,8 +3804,43 @@ function Heatmap({
2958
3804
  ys.map((y, ri) => /* @__PURE__ */ jsx("text", { className: "twc-chart__hm-tick", x: padL - 8, y: padT + chh * ri + chh / 2 + 4, textAnchor: "end", children: labelText3(y) }, ri)),
2959
3805
  xs.map((x, ci) => /* @__PURE__ */ jsx("text", { className: "twc-chart__hm-tick", x: padL + cw * ci + cw / 2, y: padT + innerH + 15, textAnchor: "middle", children: labelText3(x) }, ci)),
2960
3806
  yLabel != null ? /* @__PURE__ */ jsx("text", { className: "twc-chart__hm-title", transform: `translate(11 ${padT + innerH / 2}) rotate(-90)`, textAnchor: "middle", children: labelText3(yLabel) }) : null,
2961
- xLabel != null ? /* @__PURE__ */ jsx("text", { className: "twc-chart__hm-title", x: padL + innerW / 2, y: H - 3, textAnchor: "middle", children: labelText3(xLabel) }) : null
3807
+ xLabel != null ? /* @__PURE__ */ jsx("text", { className: "twc-chart__hm-title", x: padL + innerW / 2, y: H - 3, textAnchor: "middle", children: labelText3(xLabel) }) : null,
3808
+ dragBand ? /* @__PURE__ */ jsx(
3809
+ "rect",
3810
+ {
3811
+ className: "twc-chart__zoomband",
3812
+ x: Math.min(drag.x0, drag.x1),
3813
+ y: Math.min(drag.y0, drag.y1),
3814
+ width: Math.abs(drag.x1 - drag.x0),
3815
+ height: Math.abs(drag.y1 - drag.y0)
3816
+ }
3817
+ ) : null,
3818
+ canZoom ? /* @__PURE__ */ jsx(
3819
+ "rect",
3820
+ {
3821
+ className: "twc-chart__overlay",
3822
+ "data-zoom": "true",
3823
+ "data-clickable": clickable || void 0,
3824
+ "data-panning": drag && drag.pan || void 0,
3825
+ x: padL,
3826
+ y: padT,
3827
+ width: Math.max(0, innerW),
3828
+ height: Math.max(0, innerH),
3829
+ onMouseDown: onOverlayDown,
3830
+ onMouseMove: onOverlayMove,
3831
+ onMouseUp: onOverlayUp,
3832
+ onMouseLeave: onOverlayLeave,
3833
+ onClick: onOverlayClick
3834
+ }
3835
+ ) : null
2962
3836
  ] }),
3837
+ canZoom && zoom ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-chart__zoom-reset", onClick: () => setZoom(null), "aria-label": "Reset zoom", children: [
3838
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3839
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
3840
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
3841
+ ] }),
3842
+ "Reset"
3843
+ ] }) : null,
2963
3844
  /* @__PURE__ */ jsx(ChartTooltip, { tip }),
2964
3845
  showLegend && span > 0 ? /* @__PURE__ */ jsxs("div", { className: "twc-chart__hm-scale", "aria-hidden": "true", children: [
2965
3846
  /* @__PURE__ */ jsx("span", { children: fmt(lo) }),
@@ -2977,10 +3858,10 @@ function Heatmap({
2977
3858
  {
2978
3859
  id: tableId,
2979
3860
  caption: caption ?? svgAriaLabel,
2980
- columns: xs.map(labelText3),
2981
- rows: ys.map((y) => ({
3861
+ columns: fullXs.map(labelText3),
3862
+ rows: fullYs.map((y) => ({
2982
3863
  label: labelText3(y),
2983
- values: xs.map((x) => {
3864
+ values: fullXs.map((x) => {
2984
3865
  const v = cellOf(x, y);
2985
3866
  return v === void 0 ? "\u2014" : fmt(v);
2986
3867
  })
@@ -2994,9 +3875,13 @@ function labelText3(label) {
2994
3875
  }
2995
3876
  var FUNNEL_CSS = `
2996
3877
  .twc-chart--funnel svg { overflow: visible; }
2997
- .twc-funnel__label { fill: var(--color-text-inverted); font-family: var(--font-sans); pointer-events: none; }
3878
+ .twc-funnel__label { fill: var(--color-chart-on-fill); paint-order: stroke; stroke: var(--color-chart-on-fill-halo); stroke-width: 3px; stroke-linejoin: round; vector-effect: non-scaling-stroke; font-family: var(--font-sans); pointer-events: none; }
2998
3879
  .twc-funnel__name { font-size: 12px; font-weight: 600; }
2999
- .twc-funnel__value { font-size: 11px; fill-opacity: 0.82; }
3880
+ .twc-funnel__value { font-size: 11px; fill-opacity: 0.9; }
3881
+ /* Explode: nudge the selected stage shape outward so it separates from the funnel
3882
+ (eases via the shared [data-mark] transform transition in _chart.js). */
3883
+ .twc-chart--funnel [data-mark][data-selected="true"] { transform: translateX(14px); }
3884
+ .twc-chart--funnel[data-orientation="horizontal"] [data-mark][data-selected="true"] { transform: translateY(-14px); }
3000
3885
  `;
3001
3886
  function FunnelChart({
3002
3887
  data,
@@ -3007,6 +3892,7 @@ function FunnelChart({
3007
3892
  height = 300,
3008
3893
  colors,
3009
3894
  valueFormat,
3895
+ onDataClick,
3010
3896
  ariaLabel,
3011
3897
  "aria-label": ariaLabelProp,
3012
3898
  tableFallback = true,
@@ -3020,6 +3906,8 @@ function FunnelChart({
3020
3906
  const tableId = tableFallback ? `${uid}-table` : void 0;
3021
3907
  const { containerRef, tip, show, hide } = useChartTooltip();
3022
3908
  const [active, setActive] = React51__default.useState(null);
3909
+ const [selected, setSelected] = React51__default.useState(null);
3910
+ const clickable = !!onDataClick;
3023
3911
  const rows = data || [];
3024
3912
  const n = rows.length;
3025
3913
  const fmt = valueFormat || fmtNumber;
@@ -3071,6 +3959,7 @@ function FunnelChart({
3071
3959
  fill: color,
3072
3960
  "data-mark": true,
3073
3961
  "data-active": active === i ? "true" : void 0,
3962
+ "data-selected": selected === i ? "true" : void 0,
3074
3963
  onMouseMove: (e) => {
3075
3964
  setActive(i);
3076
3965
  show({ title: d.label, items: [{ color, label: fmt(v), value: pctText(v) }] }, e);
@@ -3078,6 +3967,10 @@ function FunnelChart({
3078
3967
  onMouseLeave: () => {
3079
3968
  setActive(null);
3080
3969
  hide();
3970
+ },
3971
+ onClick: () => {
3972
+ if (onDataClick) onDataClick({ label: d.label, value: v, index: i, percent: pct(v) });
3973
+ setSelected((s) => s === i ? null : i);
3081
3974
  }
3082
3975
  }
3083
3976
  ),
@@ -3093,7 +3986,7 @@ function FunnelChart({
3093
3986
  )) }) : null
3094
3987
  ] }, i);
3095
3988
  });
3096
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--funnel ${className}`.trim(), "data-orientation": horizontal ? "horizontal" : "vertical", "data-hovering": active != null ? "true" : void 0, ...rest, children: [
3989
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--funnel ${className}`.trim(), "data-orientation": horizontal ? "horizontal" : "vertical", "data-hovering": active != null ? "true" : void 0, "data-has-selection": selected != null || void 0, "data-clickable": clickable || void 0, ...rest, children: [
3097
3990
  baseStyles,
3098
3991
  styles,
3099
3992
  /* @__PURE__ */ jsx("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: renderStages() }),
@@ -3110,8 +4003,8 @@ function FunnelChart({
3110
4003
  ] });
3111
4004
  }
3112
4005
  var TREEMAP_CSS = `
3113
- .twc-chart__tile-label { fill: var(--color-text-inverted); font-size: var(--text-xs); font-weight: var(--font-semibold); pointer-events: none; }
3114
- .twc-chart__tile-value { fill: var(--color-text-inverted); font-size: 11px; opacity: 0.82; pointer-events: none; }
4006
+ .twc-chart__tile-label { fill: var(--color-chart-on-fill); paint-order: stroke; stroke: var(--color-chart-on-fill-halo); stroke-width: 3px; stroke-linejoin: round; vector-effect: non-scaling-stroke; font-size: var(--text-xs); font-weight: var(--font-semibold); pointer-events: none; }
4007
+ .twc-chart__tile-value { fill: var(--color-chart-on-fill); paint-order: stroke; stroke: var(--color-chart-on-fill-halo); stroke-width: 3px; stroke-linejoin: round; vector-effect: non-scaling-stroke; font-size: 11px; opacity: 0.9; pointer-events: none; }
3115
4008
  .twc-chart__empty { fill: var(--color-text-subtle); font-size: var(--text-sm); }
3116
4009
  `;
3117
4010
  function Treemap({
@@ -3121,6 +4014,7 @@ function Treemap({
3121
4014
  height = 300,
3122
4015
  colors,
3123
4016
  gap = 2,
4017
+ onDataClick,
3124
4018
  ariaLabel,
3125
4019
  "aria-label": ariaLabelProp,
3126
4020
  tableFallback = true,
@@ -3134,16 +4028,18 @@ function Treemap({
3134
4028
  const tableId = tableFallback ? `${uid}-table` : void 0;
3135
4029
  const { containerRef, tip, show, hide } = useChartTooltip();
3136
4030
  const [hovered, setHovered] = React51__default.useState(null);
4031
+ const [selected, setSelected] = React51__default.useState(null);
3137
4032
  const rows = data || [];
3138
4033
  const fmt = valueFormat || fmtNumber;
3139
4034
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? "treemap";
4035
+ const clickable = !!onDataClick;
3140
4036
  const W = 600, H = height;
3141
4037
  const weights = rows.map((d) => Math.max(0, Number(d.value) || 0));
3142
4038
  const total = sum(weights);
3143
4039
  const items = total > 0 ? rows.map((d, i) => ({ i, w: weights[i] })).sort((a, b) => b.w - a.w).map((it) => ({ ...it, area: it.w / total * (W * H) })) : [];
3144
4040
  const tiles = squarify(items, { x: 0, y: 0, w: W, h: H });
3145
4041
  const g = Math.max(0, gap);
3146
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--treemap ${className}`.trim(), "data-hovering": hovered != null || void 0, ...rest, children: [
4042
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--treemap ${className}`.trim(), "data-hovering": hovered != null || void 0, "data-has-selection": selected != null || void 0, "data-clickable": clickable || void 0, ...rest, children: [
3147
4043
  baseStyles,
3148
4044
  styles,
3149
4045
  /* @__PURE__ */ jsx("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: tiles.length === 0 ? /* @__PURE__ */ jsx("text", { className: "twc-chart__empty", x: W / 2, y: H / 2, textAnchor: "middle", dominantBaseline: "middle", children: "No data" }) : tiles.map((t) => {
@@ -3166,6 +4062,7 @@ function Treemap({
3166
4062
  className: "twc-chart__tile twc-chart__anim-fade",
3167
4063
  "data-mark": true,
3168
4064
  "data-active": hovered === t.i || void 0,
4065
+ "data-selected": selected === t.i || void 0,
3169
4066
  x,
3170
4067
  y,
3171
4068
  width: w,
@@ -3179,6 +4076,10 @@ function Treemap({
3179
4076
  onMouseLeave: () => {
3180
4077
  setHovered(null);
3181
4078
  hide();
4079
+ },
4080
+ onClick: () => {
4081
+ if (onDataClick) onDataClick({ label: d.label, value: t.w, index: t.i, share: total > 0 ? t.w / total : 0 });
4082
+ setSelected((s) => s === t.i ? null : t.i);
3182
4083
  }
3183
4084
  }
3184
4085
  ),
@@ -3273,123 +4174,290 @@ function Candlestick({
3273
4174
  height = 300,
3274
4175
  showGrid = true,
3275
4176
  showAxis = true,
4177
+ zoomable = false,
3276
4178
  valueFormat,
4179
+ onDataClick,
3277
4180
  ariaLabel,
3278
4181
  "aria-label": ariaLabelProp,
3279
- tableFallback = true,
3280
- caption,
3281
- className = "",
3282
- ...rest
3283
- }) {
3284
- const baseStyles = useScopedStyles("twc-chart-base", CHART_BASE_CSS);
3285
- const styles = useScopedStyles("twc-candlestick-styles", CANDLE_CSS);
3286
- const uid = React51__default.useId();
3287
- const tableId = tableFallback ? `${uid}-table` : void 0;
3288
- const { containerRef, tip, show, hide } = useChartTooltip();
3289
- const rows = data || [];
3290
- const fmt = valueFormat || fmtNumber;
3291
- const W = 600, H = height;
3292
- const padL = showAxis ? 44 : 8;
3293
- const padR = 10;
3294
- const padT = 12;
3295
- const padB = showAxis ? 26 : 8;
3296
- const innerW = W - padL - padR, innerH = H - padT - padB;
3297
- const lows = rows.map((d) => Number(d.low)).filter(Number.isFinite);
3298
- const highs = rows.map((d) => Number(d.high)).filter(Number.isFinite);
3299
- const minLow = lows.length ? Math.min(...lows) : 0;
3300
- const maxHigh = highs.length ? Math.max(...highs) : 1;
3301
- const scale = niceScale(minLow, maxHigh, 5);
3302
- const span = scale.max - scale.min || 1;
3303
- const vPos = (v) => padT + innerH - (v - scale.min) / span * innerH;
3304
- const catBand = innerW / Math.max(1, rows.length);
3305
- const cx = (i) => padL + catBand * i + catBand / 2;
3306
- const bodyW = Math.max(1, Math.min(catBand * 0.7, 22));
3307
- const labelStep = Math.max(1, Math.ceil(rows.length / 12));
3308
- const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? "candlestick chart";
3309
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--candlestick ${className}`.trim(), ...rest, children: [
3310
- baseStyles,
3311
- styles,
3312
- /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
3313
- showGrid ? scale.ticks.map((t, i) => {
3314
- const p = vPos(t);
3315
- return /* @__PURE__ */ jsx("line", { className: "twc-candlestick__grid", x1: padL, y1: p, x2: W - padR, y2: p }, i);
3316
- }) : null,
3317
- showAxis ? scale.ticks.map((t, i) => /* @__PURE__ */ jsx("text", { className: "twc-candlestick__axis", x: padL - 8, y: vPos(t) + 4, textAnchor: "end", children: shortNum(t) }, i)) : null,
3318
- rows.map((d, i) => {
3319
- const open = Number(d.open) || 0;
3320
- const high = Number(d.high) || 0;
3321
- const low = Number(d.low) || 0;
3322
- const close = Number(d.close) || 0;
3323
- const up = close >= open;
3324
- const color = up ? upColor : downColor;
3325
- const x = cx(i);
3326
- const yTop = vPos(Math.max(open, close));
3327
- const yBot = vPos(Math.min(open, close));
3328
- const bh = Math.max(1, yBot - yTop);
3329
- const tipFor = {
3330
- title: labelText5(d.label),
3331
- items: [
3332
- { color, label: "Open", value: fmt(open) },
3333
- { label: "High", value: fmt(high) },
3334
- { label: "Low", value: fmt(low) },
3335
- { label: "Close", value: fmt(close) }
3336
- ]
3337
- };
3338
- return /* @__PURE__ */ jsxs(
3339
- "g",
3340
- {
3341
- className: "twc-chart__anim-fade",
3342
- "data-dir": up ? "up" : "down",
3343
- onMouseMove: (e) => show(tipFor, e),
3344
- onMouseLeave: hide,
3345
- children: [
3346
- /* @__PURE__ */ jsx("rect", { x: padL + catBand * i, y: padT, width: catBand, height: innerH, fill: "transparent" }),
3347
- /* @__PURE__ */ jsx(
3348
- "line",
3349
- {
3350
- className: "twc-candlestick__wick",
3351
- style: { stroke: color },
3352
- x1: x,
3353
- y1: vPos(high),
3354
- x2: x,
3355
- y2: vPos(low)
3356
- }
3357
- ),
3358
- /* @__PURE__ */ jsx(
3359
- "rect",
3360
- {
3361
- className: "twc-candlestick__body",
3362
- style: { fill: color },
3363
- x: x - bodyW / 2,
3364
- y: yTop,
3365
- width: bodyW,
3366
- height: bh,
3367
- rx: "1"
3368
- }
3369
- )
3370
- ]
3371
- },
3372
- i
3373
- );
3374
- }),
3375
- showAxis ? rows.map(
3376
- (d, i) => i % labelStep === 0 ? /* @__PURE__ */ jsx("text", { className: "twc-candlestick__axis", x: cx(i), y: H - 8, textAnchor: "middle", children: d.label }, i) : null
3377
- ) : null
3378
- ] }),
3379
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
3380
- tableFallback ? /* @__PURE__ */ jsx(
3381
- ChartTable,
3382
- {
3383
- id: tableId,
3384
- caption: caption ?? svgAriaLabel,
3385
- columns: ["open", "high", "low", "close"],
3386
- rows: rows.map((d) => ({
3387
- label: d.label,
3388
- values: [Number(d.open) || 0, Number(d.high) || 0, Number(d.low) || 0, Number(d.close) || 0].map(fmt)
3389
- }))
4182
+ tableFallback = true,
4183
+ caption,
4184
+ className = "",
4185
+ ...rest
4186
+ }) {
4187
+ const baseStyles = useScopedStyles("twc-chart-base", CHART_BASE_CSS);
4188
+ const styles = useScopedStyles("twc-candlestick-styles", CANDLE_CSS);
4189
+ const uid = React51__default.useId();
4190
+ const tableId = tableFallback ? `${uid}-table` : void 0;
4191
+ const { containerRef, tip, show, hide } = useChartTooltip();
4192
+ const [selected, setSelected] = React51__default.useState(null);
4193
+ const [zoom, setZoom] = React51__default.useState(null);
4194
+ const [drag, setDrag] = React51__default.useState(null);
4195
+ const svgRef = React51__default.useRef(null);
4196
+ const fmt = valueFormat || fmtNumber;
4197
+ const clickable = !!onDataClick;
4198
+ const allRows = data || [];
4199
+ const baseIdx = zoom ? zoom.s : 0;
4200
+ const rows = zoom ? allRows.slice(zoom.s, zoom.e + 1) : allRows;
4201
+ const W = 600, H = height;
4202
+ const padL = showAxis ? 44 : 8;
4203
+ const padR = 10;
4204
+ const padT = 12;
4205
+ const padB = showAxis ? 26 : 8;
4206
+ const innerW = W - padL - padR, innerH = H - padT - padB;
4207
+ const lows = rows.map((d) => Number(d.low)).filter(Number.isFinite);
4208
+ const highs = rows.map((d) => Number(d.high)).filter(Number.isFinite);
4209
+ const minLow = lows.length ? Math.min(...lows) : 0;
4210
+ const maxHigh = highs.length ? Math.max(...highs) : 1;
4211
+ const scale = niceScale(minLow, maxHigh, 5);
4212
+ const span = scale.max - scale.min || 1;
4213
+ const vPos = (v) => padT + innerH - (v - scale.min) / span * innerH;
4214
+ const catBand = innerW / Math.max(1, rows.length);
4215
+ const cx = (i) => padL + catBand * i + catBand / 2;
4216
+ const bodyW = Math.max(1, Math.min(catBand * 0.7, 22));
4217
+ const labelStep = Math.max(1, Math.ceil(rows.length / 12));
4218
+ const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? "candlestick chart";
4219
+ const tipFor = (d) => {
4220
+ const open = Number(d.open) || 0, high = Number(d.high) || 0;
4221
+ const low = Number(d.low) || 0, close = Number(d.close) || 0;
4222
+ const color = close >= open ? upColor : downColor;
4223
+ return {
4224
+ title: labelText5(d.label),
4225
+ items: [
4226
+ { color, label: "Open", value: fmt(open) },
4227
+ { label: "High", value: fmt(high) },
4228
+ { label: "Low", value: fmt(low) },
4229
+ { label: "Close", value: fmt(close) }
4230
+ ]
4231
+ };
4232
+ };
4233
+ const selectMark = (key) => setSelected((s) => s === key ? null : key);
4234
+ const clickCandle = (d, index) => {
4235
+ if (!onDataClick) return;
4236
+ onDataClick({
4237
+ label: d.label,
4238
+ open: Number(d.open) || 0,
4239
+ high: Number(d.high) || 0,
4240
+ low: Number(d.low) || 0,
4241
+ close: Number(d.close) || 0,
4242
+ index
4243
+ });
4244
+ };
4245
+ const applyZoom = (s, e) => {
4246
+ const n = allRows.length;
4247
+ if (e - s >= n - 1) {
4248
+ setZoom(null);
4249
+ return;
4250
+ }
4251
+ setZoom({ s: Math.max(0, s), e: Math.min(n - 1, e) });
4252
+ };
4253
+ const catFromEvent = (e) => {
4254
+ const svg = svgRef.current;
4255
+ if (!svg) return null;
4256
+ if (!rows.length) return null;
4257
+ const r2 = svg.getBoundingClientRect();
4258
+ if (!(r2.width > 0)) return null;
4259
+ const frac = Math.min(1, Math.max(0, ((e.clientX - r2.left) / r2.width * W - padL) / innerW));
4260
+ const idx = Math.floor(frac * rows.length);
4261
+ return Math.min(rows.length - 1, Math.max(0, idx));
4262
+ };
4263
+ const onOverlayMove = (e) => {
4264
+ const idx = catFromEvent(e);
4265
+ if (idx == null || !rows[idx]) return;
4266
+ show(tipFor(rows[idx]), e);
4267
+ if (drag) {
4268
+ if (drag.pan) {
4269
+ const spanZ = drag.z0.e - drag.z0.s;
4270
+ const s = Math.min(allRows.length - 1 - spanZ, Math.max(0, drag.z0.s + (drag.start - idx)));
4271
+ applyZoom(s, s + spanZ);
4272
+ } else {
4273
+ setDrag({ ...drag, end: idx });
3390
4274
  }
3391
- ) : null
3392
- ] });
4275
+ }
4276
+ };
4277
+ const onOverlayDown = (e) => {
4278
+ const idx = catFromEvent(e);
4279
+ if (idx == null) return;
4280
+ setDrag({ start: idx, end: idx, pan: e.shiftKey, z0: zoom || { s: 0, e: allRows.length - 1 } });
4281
+ };
4282
+ const onOverlayUp = () => {
4283
+ if (drag && !drag.pan) {
4284
+ const lo = Math.min(drag.start, drag.end), hi = Math.max(drag.start, drag.end);
4285
+ if (hi - lo >= 1) applyZoom(baseIdx + lo, baseIdx + hi);
4286
+ }
4287
+ setDrag(null);
4288
+ };
4289
+ const onOverlayClick = (e) => {
4290
+ if (!onDataClick) return;
4291
+ const idx = catFromEvent(e);
4292
+ if (idx == null || !rows[idx]) return;
4293
+ clickCandle(rows[idx], baseIdx + idx);
4294
+ selectMark(baseIdx + idx);
4295
+ };
4296
+ React51__default.useEffect(() => {
4297
+ if (!zoomable) return void 0;
4298
+ const svg = svgRef.current;
4299
+ if (!svg) return void 0;
4300
+ const onWheel = (e) => {
4301
+ const n = allRows.length;
4302
+ if (!n) return;
4303
+ e.preventDefault();
4304
+ const cur = zoom || { s: 0, e: n - 1 };
4305
+ const spanZ = cur.e - cur.s;
4306
+ const r2 = svg.getBoundingClientRect();
4307
+ const frac = r2.width > 0 ? Math.min(1, Math.max(0, ((e.clientX - r2.left) / r2.width * W - padL) / innerW)) : 0.5;
4308
+ const center = cur.s + Math.round(frac * spanZ);
4309
+ const newSpan = Math.min(n - 1, Math.max(1, Math.round(spanZ * (e.deltaY < 0 ? 0.8 : 1.25))));
4310
+ const s = Math.min(n - 1 - newSpan, Math.max(0, center - Math.round(newSpan * frac)));
4311
+ applyZoom(s, s + newSpan);
4312
+ };
4313
+ svg.addEventListener("wheel", onWheel, { passive: false });
4314
+ return () => svg.removeEventListener("wheel", onWheel);
4315
+ }, [zoomable, zoom, allRows.length]);
4316
+ const renderZones = () => rows.map((d, i) => {
4317
+ const gi = baseIdx + i;
4318
+ return /* @__PURE__ */ jsx(
4319
+ "rect",
4320
+ {
4321
+ className: "twc-chart__zone",
4322
+ x: padL + catBand * i,
4323
+ y: padT,
4324
+ width: catBand,
4325
+ height: innerH,
4326
+ "data-clickable": clickable || void 0,
4327
+ onMouseMove: (e) => show(tipFor(d), e),
4328
+ onMouseLeave: hide,
4329
+ onClick: () => {
4330
+ if (onDataClick) clickCandle(d, gi);
4331
+ }
4332
+ },
4333
+ i
4334
+ );
4335
+ });
4336
+ const dragBand = zoomable && drag && !drag.pan && drag.end !== drag.start;
4337
+ return /* @__PURE__ */ jsxs(
4338
+ "div",
4339
+ {
4340
+ ref: containerRef,
4341
+ className: `twc-chart twc-chart--candlestick ${className}`.trim(),
4342
+ "data-clickable": clickable || void 0,
4343
+ "data-has-selection": selected != null || void 0,
4344
+ ...rest,
4345
+ children: [
4346
+ baseStyles,
4347
+ styles,
4348
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
4349
+ showGrid ? scale.ticks.map((t, i) => {
4350
+ const p = vPos(t);
4351
+ return /* @__PURE__ */ jsx("line", { className: "twc-candlestick__grid", x1: padL, y1: p, x2: W - padR, y2: p }, i);
4352
+ }) : null,
4353
+ showAxis ? scale.ticks.map((t, i) => /* @__PURE__ */ jsx("text", { className: "twc-candlestick__axis", x: padL - 8, y: vPos(t) + 4, textAnchor: "end", children: shortNum(t) }, i)) : null,
4354
+ !zoomable ? renderZones() : null,
4355
+ rows.map((d, i) => {
4356
+ const open = Number(d.open) || 0;
4357
+ const high = Number(d.high) || 0;
4358
+ const low = Number(d.low) || 0;
4359
+ const close = Number(d.close) || 0;
4360
+ const up = close >= open;
4361
+ const color = up ? upColor : downColor;
4362
+ const x = cx(i);
4363
+ const gi = baseIdx + i;
4364
+ const yTop = vPos(Math.max(open, close));
4365
+ const yBot = vPos(Math.min(open, close));
4366
+ const bh = Math.max(1, yBot - yTop);
4367
+ return /* @__PURE__ */ jsxs(
4368
+ "g",
4369
+ {
4370
+ className: "twc-chart__anim-fade",
4371
+ "data-dir": up ? "up" : "down",
4372
+ onMouseMove: (e) => show(tipFor(d), e),
4373
+ onMouseLeave: hide,
4374
+ onClick: () => {
4375
+ clickCandle(d, gi);
4376
+ selectMark(gi);
4377
+ },
4378
+ children: [
4379
+ /* @__PURE__ */ jsx(
4380
+ "line",
4381
+ {
4382
+ className: "twc-candlestick__wick",
4383
+ style: { stroke: color },
4384
+ x1: x,
4385
+ y1: vPos(high),
4386
+ x2: x,
4387
+ y2: vPos(low)
4388
+ }
4389
+ ),
4390
+ /* @__PURE__ */ jsx(
4391
+ "rect",
4392
+ {
4393
+ className: "twc-candlestick__body",
4394
+ style: { fill: color },
4395
+ "data-mark": true,
4396
+ "data-selected": selected === gi || void 0,
4397
+ x: x - bodyW / 2,
4398
+ y: yTop,
4399
+ width: bodyW,
4400
+ height: bh,
4401
+ rx: "1"
4402
+ }
4403
+ )
4404
+ ]
4405
+ },
4406
+ i
4407
+ );
4408
+ }),
4409
+ showAxis ? rows.map(
4410
+ (d, i) => i % labelStep === 0 ? /* @__PURE__ */ jsx("text", { className: "twc-candlestick__axis", x: cx(i), y: H - 8, textAnchor: "middle", children: d.label }, i) : null
4411
+ ) : null,
4412
+ dragBand ? (() => {
4413
+ const lo = Math.min(drag.start, drag.end), hi = Math.max(drag.start, drag.end);
4414
+ const x0 = padL + catBand * lo;
4415
+ const x1 = padL + catBand * (hi + 1);
4416
+ return /* @__PURE__ */ jsx("rect", { className: "twc-chart__zoomband", x: x0, y: padT, width: Math.max(0, x1 - x0), height: innerH });
4417
+ })() : null,
4418
+ zoomable ? /* @__PURE__ */ jsx(
4419
+ "rect",
4420
+ {
4421
+ className: "twc-chart__overlay",
4422
+ "data-clickable": clickable || void 0,
4423
+ x: padL,
4424
+ y: padT,
4425
+ width: innerW,
4426
+ height: innerH,
4427
+ onMouseMove: onOverlayMove,
4428
+ onMouseLeave: () => {
4429
+ hide();
4430
+ setDrag(null);
4431
+ },
4432
+ onMouseDown: onOverlayDown,
4433
+ onMouseUp: onOverlayUp,
4434
+ onClick: onOverlayClick
4435
+ }
4436
+ ) : null
4437
+ ] }),
4438
+ zoomable && zoom ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-chart__zoom-reset", onClick: () => setZoom(null), "aria-label": "Reset zoom", children: [
4439
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4440
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
4441
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
4442
+ ] }),
4443
+ "Reset"
4444
+ ] }) : null,
4445
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
4446
+ tableFallback ? /* @__PURE__ */ jsx(
4447
+ ChartTable,
4448
+ {
4449
+ id: tableId,
4450
+ caption: caption ?? svgAriaLabel,
4451
+ columns: ["open", "high", "low", "close"],
4452
+ rows: allRows.map((d) => ({
4453
+ label: d.label,
4454
+ values: [Number(d.open) || 0, Number(d.high) || 0, Number(d.low) || 0, Number(d.close) || 0].map(fmt)
4455
+ }))
4456
+ }
4457
+ ) : null
4458
+ ]
4459
+ }
4460
+ );
3393
4461
  }
3394
4462
  function labelText5(label) {
3395
4463
  return typeof label === "string" || typeof label === "number" ? String(label) : "";
@@ -3409,7 +4477,9 @@ function Boxplot({
3409
4477
  height = 300,
3410
4478
  showGrid = true,
3411
4479
  showAxis = true,
4480
+ zoomable = false,
3412
4481
  valueFormat,
4482
+ onDataClick,
3413
4483
  ariaLabel,
3414
4484
  "aria-label": ariaLabelProp,
3415
4485
  tableFallback = true,
@@ -3422,9 +4492,28 @@ function Boxplot({
3422
4492
  const uid = React51__default.useId();
3423
4493
  const tableId = tableFallback ? `${uid}-table` : void 0;
3424
4494
  const { containerRef, tip, show, hide } = useChartTooltip();
3425
- const rows = data || [];
4495
+ const [selected, setSelected] = React51__default.useState(null);
4496
+ const [zoom, setZoom] = React51__default.useState(null);
4497
+ const [drag, setDrag] = React51__default.useState(null);
4498
+ const svgRef = React51__default.useRef(null);
4499
+ const allRows = data || [];
4500
+ const baseIdx = zoom ? zoom.s : 0;
4501
+ const rows = zoom ? allRows.slice(zoom.s, zoom.e + 1) : allRows;
3426
4502
  const fmt = valueFormat || fmtNumber;
3427
4503
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? "box plot";
4504
+ const clickable = !!onDataClick;
4505
+ const clickBox = (d, i) => {
4506
+ if (onDataClick) onDataClick({
4507
+ label: d.label,
4508
+ min: Number(d.min),
4509
+ q1: Number(d.q1),
4510
+ median: Number(d.median),
4511
+ q3: Number(d.q3),
4512
+ max: Number(d.max),
4513
+ index: baseIdx + i
4514
+ });
4515
+ setSelected((s) => s === i ? null : i);
4516
+ };
3428
4517
  const tipFor = (d) => ({
3429
4518
  title: labelText6(d.label),
3430
4519
  items: [
@@ -3457,69 +4546,184 @@ function Boxplot({
3457
4546
  const band = innerW / Math.max(1, rows.length);
3458
4547
  const bandX = (i) => padL + band * i;
3459
4548
  const boxW = Math.min(band * 0.5, 46);
3460
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--boxplot ${className}`.trim(), ...rest, children: [
3461
- baseStyles,
3462
- styles,
3463
- /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
3464
- showGrid ? scale.ticks.map((t, i) => {
3465
- const p = vPos(t);
3466
- return /* @__PURE__ */ jsx("line", { className: "twc-boxplot__grid", x1: padL, y1: p, x2: W - padR, y2: p }, i);
3467
- }) : null,
3468
- showAxis ? scale.ticks.map((t, i) => {
3469
- const p = vPos(t);
3470
- return /* @__PURE__ */ jsx("text", { className: "twc-boxplot__axis", x: padL - 8, y: p + 4, textAnchor: "end", children: shortNum(t) }, i);
3471
- }) : null,
3472
- rows.map((d, i) => {
3473
- const cx = bandX(i) + band / 2;
3474
- const x = cx - boxW / 2;
3475
- const yMin = vPos(d.min), yMax = vPos(d.max);
3476
- const yQ1 = vPos(d.q1), yQ3 = vPos(d.q3), yMed = vPos(d.median);
3477
- const boxTop = Math.min(yQ1, yQ3), boxBot = Math.max(yQ1, yQ3);
3478
- const cap = boxW / 3;
3479
- const hoverProps = {
3480
- onMouseMove: (e) => show(tipFor(d), e),
3481
- onMouseLeave: hide
3482
- };
3483
- return /* @__PURE__ */ jsxs("g", { className: "twc-chart__anim-fade", style: { color }, children: [
3484
- /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx, y1: yMax, x2: cx, y2: boxTop }),
3485
- /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx, y1: boxBot, x2: cx, y2: yMin }),
3486
- /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx - cap, y1: yMax, x2: cx + cap, y2: yMax }),
3487
- /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx - cap, y1: yMin, x2: cx + cap, y2: yMin }),
3488
- /* @__PURE__ */ jsx(
4549
+ const applyZoom = (s, e) => {
4550
+ const n = allRows.length;
4551
+ if (e - s >= n - 1) {
4552
+ setZoom(null);
4553
+ return;
4554
+ }
4555
+ setZoom({ s: Math.max(0, s), e: Math.min(n - 1, e) });
4556
+ };
4557
+ const catFromEvent = (e) => {
4558
+ const svg = svgRef.current;
4559
+ if (!svg) return null;
4560
+ const r2 = svg.getBoundingClientRect();
4561
+ if (!(r2.width > 0)) return null;
4562
+ const frac = Math.min(1, Math.max(0, ((e.clientX - r2.left) / r2.width * W - padL) / innerW));
4563
+ const idx = Math.floor(frac * rows.length);
4564
+ return Math.min(rows.length - 1, Math.max(0, idx));
4565
+ };
4566
+ const onOverlayMove = (e) => {
4567
+ const idx = catFromEvent(e);
4568
+ if (idx == null) return;
4569
+ show(tipFor(rows[idx]), e);
4570
+ if (drag) {
4571
+ if (drag.pan) {
4572
+ const s0 = drag.z0.e - drag.z0.s;
4573
+ const s = Math.min(allRows.length - 1 - s0, Math.max(0, drag.z0.s + (drag.start - idx)));
4574
+ applyZoom(s, s + s0);
4575
+ } else {
4576
+ setDrag({ ...drag, end: idx });
4577
+ }
4578
+ }
4579
+ };
4580
+ const onOverlayDown = (e) => {
4581
+ const idx = catFromEvent(e);
4582
+ if (idx == null) return;
4583
+ setDrag({ start: idx, end: idx, pan: e.shiftKey, z0: zoom || { s: 0, e: allRows.length - 1 } });
4584
+ };
4585
+ const onOverlayUp = () => {
4586
+ if (drag && !drag.pan) {
4587
+ const lo = Math.min(drag.start, drag.end), hi = Math.max(drag.start, drag.end);
4588
+ if (hi - lo >= 1) applyZoom(baseIdx + lo, baseIdx + hi);
4589
+ }
4590
+ setDrag(null);
4591
+ };
4592
+ const onOverlayClick = (e) => {
4593
+ if (drag && drag.end !== drag.start) return;
4594
+ const idx = catFromEvent(e);
4595
+ if (idx == null) return;
4596
+ clickBox(rows[idx], idx);
4597
+ };
4598
+ React51__default.useEffect(() => {
4599
+ if (!zoomable) return void 0;
4600
+ const svg = svgRef.current;
4601
+ if (!svg) return void 0;
4602
+ const onWheel = (e) => {
4603
+ e.preventDefault();
4604
+ const n = allRows.length;
4605
+ const cur = zoom || { s: 0, e: n - 1 };
4606
+ const wSpan = cur.e - cur.s;
4607
+ const r2 = svg.getBoundingClientRect();
4608
+ const frac = r2.width > 0 ? Math.min(1, Math.max(0, ((e.clientX - r2.left) / r2.width * W - padL) / innerW)) : 0.5;
4609
+ const center = cur.s + Math.round(frac * wSpan);
4610
+ const newSpan = Math.min(n - 1, Math.max(1, Math.round(wSpan * (e.deltaY < 0 ? 0.8 : 1.25))));
4611
+ const s = Math.min(n - 1 - newSpan, Math.max(0, center - Math.round(newSpan * frac)));
4612
+ applyZoom(s, s + newSpan);
4613
+ };
4614
+ svg.addEventListener("wheel", onWheel, { passive: false });
4615
+ return () => svg.removeEventListener("wheel", onWheel);
4616
+ }, [zoomable, zoom, allRows.length]);
4617
+ const dragBand = zoomable && drag && !drag.pan && drag.end !== drag.start;
4618
+ return /* @__PURE__ */ jsxs(
4619
+ "div",
4620
+ {
4621
+ ref: containerRef,
4622
+ className: `twc-chart twc-chart--boxplot ${className}`.trim(),
4623
+ "data-has-selection": selected != null || void 0,
4624
+ "data-clickable": clickable || void 0,
4625
+ ...rest,
4626
+ children: [
4627
+ baseStyles,
4628
+ styles,
4629
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
4630
+ showGrid ? scale.ticks.map((t, i) => {
4631
+ const p = vPos(t);
4632
+ return /* @__PURE__ */ jsx("line", { className: "twc-boxplot__grid", x1: padL, y1: p, x2: W - padR, y2: p }, i);
4633
+ }) : null,
4634
+ showAxis ? scale.ticks.map((t, i) => {
4635
+ const p = vPos(t);
4636
+ return /* @__PURE__ */ jsx("text", { className: "twc-boxplot__axis", x: padL - 8, y: p + 4, textAnchor: "end", children: shortNum(t) }, i);
4637
+ }) : null,
4638
+ rows.map((d, i) => {
4639
+ const cx = bandX(i) + band / 2;
4640
+ const x = cx - boxW / 2;
4641
+ const yMin = vPos(d.min), yMax = vPos(d.max);
4642
+ const yQ1 = vPos(d.q1), yQ3 = vPos(d.q3), yMed = vPos(d.median);
4643
+ const boxTop = Math.min(yQ1, yQ3), boxBot = Math.max(yQ1, yQ3);
4644
+ const cap = boxW / 3;
4645
+ const hoverProps = {
4646
+ onMouseMove: (e) => show(tipFor(d), e),
4647
+ onMouseLeave: hide
4648
+ };
4649
+ return /* @__PURE__ */ jsxs("g", { className: "twc-chart__anim-fade", style: { color }, children: [
4650
+ /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx, y1: yMax, x2: cx, y2: boxTop }),
4651
+ /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx, y1: boxBot, x2: cx, y2: yMin }),
4652
+ /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx - cap, y1: yMax, x2: cx + cap, y2: yMax }),
4653
+ /* @__PURE__ */ jsx("line", { className: "twc-boxplot__whisker", style: { stroke: color }, x1: cx - cap, y1: yMin, x2: cx + cap, y2: yMin }),
4654
+ /* @__PURE__ */ jsx(
4655
+ "rect",
4656
+ {
4657
+ className: "twc-boxplot__box",
4658
+ "data-mark": true,
4659
+ "data-selected": selected === i || void 0,
4660
+ style: selected === i ? { fill: color } : { fill: color, stroke: color },
4661
+ x,
4662
+ y: boxTop,
4663
+ width: boxW,
4664
+ height: Math.max(1, boxBot - boxTop),
4665
+ rx: "2",
4666
+ ...hoverProps,
4667
+ onClick: () => clickBox(d, i)
4668
+ }
4669
+ ),
4670
+ /* @__PURE__ */ jsx("line", { className: "twc-boxplot__median", style: { stroke: color }, x1: x, y1: yMed, x2: x + boxW, y2: yMed }),
4671
+ (Array.isArray(d.outliers) ? d.outliers : []).map(
4672
+ (o, oi) => isFinite(Number(o)) ? /* @__PURE__ */ jsx("circle", { className: "twc-boxplot__outlier", style: { stroke: color }, cx, cy: vPos(o), r: "2.5", ...hoverProps }, oi) : null
4673
+ )
4674
+ ] }, i);
4675
+ }),
4676
+ showAxis ? rows.map((d, i) => /* @__PURE__ */ jsx("text", { className: "twc-boxplot__axis", x: bandX(i) + band / 2, y: H - 8, textAnchor: "middle", children: d.label }, i)) : null,
4677
+ dragBand ? (() => {
4678
+ const lo = Math.min(drag.start, drag.end), hi = Math.max(drag.start, drag.end);
4679
+ const x0 = bandX(lo), x1 = bandX(hi + 1);
4680
+ return /* @__PURE__ */ jsx("rect", { className: "twc-chart__zoomband", x: x0, y: padT, width: Math.max(0, x1 - x0), height: innerH });
4681
+ })() : null,
4682
+ zoomable ? /* @__PURE__ */ jsx(
3489
4683
  "rect",
3490
4684
  {
3491
- className: "twc-boxplot__box",
3492
- style: { fill: color, stroke: color },
3493
- x,
3494
- y: boxTop,
3495
- width: boxW,
3496
- height: Math.max(1, boxBot - boxTop),
3497
- rx: "2",
3498
- ...hoverProps
4685
+ className: "twc-chart__overlay",
4686
+ "data-zoom": "true",
4687
+ "data-clickable": clickable || void 0,
4688
+ "data-panning": drag && drag.pan || void 0,
4689
+ x: padL,
4690
+ y: padT,
4691
+ width: innerW,
4692
+ height: innerH,
4693
+ onMouseMove: onOverlayMove,
4694
+ onMouseLeave: () => {
4695
+ hide();
4696
+ setDrag(null);
4697
+ },
4698
+ onMouseDown: onOverlayDown,
4699
+ onMouseUp: onOverlayUp,
4700
+ onClick: onOverlayClick
3499
4701
  }
3500
- ),
3501
- /* @__PURE__ */ jsx("line", { className: "twc-boxplot__median", style: { stroke: color }, x1: x, y1: yMed, x2: x + boxW, y2: yMed }),
3502
- (Array.isArray(d.outliers) ? d.outliers : []).map(
3503
- (o, oi) => isFinite(Number(o)) ? /* @__PURE__ */ jsx("circle", { className: "twc-boxplot__outlier", style: { stroke: color }, cx, cy: vPos(o), r: "2.5", ...hoverProps }, oi) : null
3504
- )
3505
- ] }, i);
3506
- }),
3507
- showAxis ? rows.map((d, i) => /* @__PURE__ */ jsx("text", { className: "twc-boxplot__axis", x: bandX(i) + band / 2, y: H - 8, textAnchor: "middle", children: d.label }, i)) : null
3508
- ] }),
3509
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
3510
- tableFallback ? /* @__PURE__ */ jsx(
3511
- ChartTable,
3512
- {
3513
- id: tableId,
3514
- caption: caption ?? svgAriaLabel,
3515
- columns: ["min", "q1", "median", "q3", "max"],
3516
- rows: rows.map((d) => ({
3517
- label: d.label,
3518
- values: [d.min, d.q1, d.median, d.q3, d.max].map((v) => fmt(Number(v)))
3519
- }))
3520
- }
3521
- ) : null
3522
- ] });
4702
+ ) : null
4703
+ ] }),
4704
+ zoomable && zoom ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-chart__zoom-reset", onClick: () => setZoom(null), "aria-label": "Reset zoom", children: [
4705
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4706
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
4707
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
4708
+ ] }),
4709
+ "Reset"
4710
+ ] }) : null,
4711
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
4712
+ tableFallback ? /* @__PURE__ */ jsx(
4713
+ ChartTable,
4714
+ {
4715
+ id: tableId,
4716
+ caption: caption ?? svgAriaLabel,
4717
+ columns: ["min", "q1", "median", "q3", "max"],
4718
+ rows: allRows.map((d) => ({
4719
+ label: d.label,
4720
+ values: [d.min, d.q1, d.median, d.q3, d.max].map((v) => fmt(Number(v)))
4721
+ }))
4722
+ }
4723
+ ) : null
4724
+ ]
4725
+ }
4726
+ );
3523
4727
  }
3524
4728
  function labelText6(label) {
3525
4729
  return typeof label === "string" || typeof label === "number" ? String(label) : "";
@@ -3539,8 +4743,10 @@ function RangeChart({
3539
4743
  height = 300,
3540
4744
  showGrid = true,
3541
4745
  showAxis = true,
4746
+ zoomable = false,
3542
4747
  colors,
3543
4748
  valueFormat,
4749
+ onDataClick,
3544
4750
  ariaLabel,
3545
4751
  "aria-label": ariaLabelProp,
3546
4752
  tableFallback = true,
@@ -3553,10 +4759,17 @@ function RangeChart({
3553
4759
  const uid = React51__default.useId();
3554
4760
  const tableId = tableFallback ? `${uid}-table` : void 0;
3555
4761
  const { containerRef, tip, show, hide } = useChartTooltip();
4762
+ const [selected, setSelected] = React51__default.useState(null);
4763
+ const [zoom, setZoom] = React51__default.useState(null);
4764
+ const [drag, setDrag] = React51__default.useState(null);
4765
+ const svgRef = React51__default.useRef(null);
3556
4766
  const isArea = type === "area";
3557
- const rows = data || [];
3558
- const n = rows.length;
4767
+ const clickable = !!onDataClick;
3559
4768
  const fmt = valueFormat || fmtNumber;
4769
+ const allRows = data || [];
4770
+ const baseIdx = zoom ? zoom.s : 0;
4771
+ const rows = zoom ? allRows.slice(zoom.s, zoom.e + 1) : allRows;
4772
+ const n = rows.length;
3560
4773
  const W = 600, H = height;
3561
4774
  const padL = showAxis ? isArea ? 44 : 84 : 8;
3562
4775
  const padR = 12;
@@ -3572,6 +4785,97 @@ function RangeChart({
3572
4785
  const svgAriaLabel = ariaLabelProp ?? ariaLabel ?? `range ${type} chart`;
3573
4786
  const xPos = (v) => padL + frac(v) * innerW;
3574
4787
  const catBandH = innerH / Math.max(1, n);
4788
+ const lineX = (i) => n > 1 ? padL + innerW / (n - 1) * i : padL + innerW / 2;
4789
+ const yPos = (v) => padT + innerH - frac(v) * innerH;
4790
+ const colorAt = (d, i) => isArea ? paletteAt(colors, 0) : d.color || paletteAt(colors, i);
4791
+ const rangeVal = (d) => `${fmt(Number(d.min) || 0)} \u2013 ${fmt(Number(d.max) || 0)}`;
4792
+ const tipFor = (d, i) => ({ title: labelText7(d.label), items: [{ color: colorAt(d, i), label: "", value: rangeVal(d) }] });
4793
+ const clickDatum = (d, localIdx) => {
4794
+ if (!onDataClick) return;
4795
+ onDataClick({ label: d.label, min: Number(d.min) || 0, max: Number(d.max) || 0, index: baseIdx + localIdx });
4796
+ };
4797
+ const selectMark = (key) => setSelected((s) => s === key ? null : key);
4798
+ const clickAt = (localIdx) => {
4799
+ clickDatum(rows[localIdx], localIdx);
4800
+ selectMark(baseIdx + localIdx);
4801
+ };
4802
+ const applyZoom = (s, e) => {
4803
+ const nAll = allRows.length;
4804
+ if (e - s >= nAll - 1) {
4805
+ setZoom(null);
4806
+ return;
4807
+ }
4808
+ setZoom({ s: Math.max(0, s), e: Math.min(nAll - 1, e) });
4809
+ };
4810
+ const fracFromEvent = (e) => {
4811
+ const svg = svgRef.current;
4812
+ if (!svg) return null;
4813
+ const rect = svg.getBoundingClientRect();
4814
+ if (isArea) {
4815
+ if (!(rect.width > 0)) return null;
4816
+ return Math.min(1, Math.max(0, ((e.clientX - rect.left) / rect.width * W - padL) / innerW));
4817
+ }
4818
+ if (!(rect.height > 0)) return null;
4819
+ return Math.min(1, Math.max(0, ((e.clientY - rect.top) / rect.height * H - padT) / innerH));
4820
+ };
4821
+ const catFromEvent = (e) => {
4822
+ if (!rows.length) return null;
4823
+ const f = fracFromEvent(e);
4824
+ if (f == null) return null;
4825
+ const idx = isArea ? Math.round(f * Math.max(1, rows.length - 1)) : Math.floor(f * rows.length);
4826
+ return Math.min(rows.length - 1, Math.max(0, idx));
4827
+ };
4828
+ const onOverlayMove = (e) => {
4829
+ const idx = catFromEvent(e);
4830
+ if (idx == null) return;
4831
+ show(tipFor(rows[idx], idx), e);
4832
+ if (drag) {
4833
+ if (drag.pan) {
4834
+ const spanD = drag.z0.e - drag.z0.s;
4835
+ const s = Math.min(allRows.length - 1 - spanD, Math.max(0, drag.z0.s + (drag.start - idx)));
4836
+ applyZoom(s, s + spanD);
4837
+ } else {
4838
+ setDrag({ ...drag, end: idx });
4839
+ }
4840
+ }
4841
+ };
4842
+ const onOverlayDown = (e) => {
4843
+ const idx = catFromEvent(e);
4844
+ if (idx == null) return;
4845
+ setDrag({ start: idx, end: idx, pan: e.shiftKey, z0: zoom || { s: 0, e: allRows.length - 1 } });
4846
+ };
4847
+ const onOverlayUp = () => {
4848
+ if (drag && !drag.pan) {
4849
+ const loI = Math.min(drag.start, drag.end), hiI = Math.max(drag.start, drag.end);
4850
+ if (hiI - loI >= 1) applyZoom(baseIdx + loI, baseIdx + hiI);
4851
+ }
4852
+ setDrag(null);
4853
+ };
4854
+ const onOverlayClick = (e) => {
4855
+ if (!onDataClick) return;
4856
+ const idx = catFromEvent(e);
4857
+ if (idx == null) return;
4858
+ clickAt(idx);
4859
+ };
4860
+ React51__default.useEffect(() => {
4861
+ if (!zoomable) return void 0;
4862
+ const svg = svgRef.current;
4863
+ if (!svg) return void 0;
4864
+ const onWheel = (e) => {
4865
+ e.preventDefault();
4866
+ const nAll = allRows.length;
4867
+ const cur = zoom || { s: 0, e: nAll - 1 };
4868
+ const spanC = cur.e - cur.s;
4869
+ const f = fracFromEvent(e);
4870
+ const frc = f == null ? 0.5 : f;
4871
+ const center = cur.s + Math.round(frc * spanC);
4872
+ const newSpan = Math.min(nAll - 1, Math.max(1, Math.round(spanC * (e.deltaY < 0 ? 0.8 : 1.25))));
4873
+ const s = Math.min(nAll - 1 - newSpan, Math.max(0, center - Math.round(newSpan * frc)));
4874
+ applyZoom(s, s + newSpan);
4875
+ };
4876
+ svg.addEventListener("wheel", onWheel, { passive: false });
4877
+ return () => svg.removeEventListener("wheel", onWheel);
4878
+ }, [zoomable, zoom, allRows.length]);
3575
4879
  const renderBars = () => {
3576
4880
  const gap = 0.34;
3577
4881
  const slot = catBandH * (1 - gap);
@@ -3580,27 +4884,29 @@ function RangeChart({
3580
4884
  const x0 = xPos(Math.min(min, max));
3581
4885
  const x1 = xPos(Math.max(min, max));
3582
4886
  const y = padT + catBandH * i + catBandH * gap / 2;
3583
- const color = d.color || paletteAt(colors, i);
4887
+ const color = colorAt(d, i);
4888
+ const key = baseIdx + i;
3584
4889
  return /* @__PURE__ */ jsx(
3585
4890
  "rect",
3586
4891
  {
3587
4892
  className: "twc-rangechart__bar twc-chart__anim-bar",
4893
+ "data-mark": true,
3588
4894
  "data-horizontal": "true",
4895
+ "data-selected": selected === key || void 0,
3589
4896
  style: { fill: color },
3590
4897
  x: x0,
3591
4898
  y,
3592
4899
  width: Math.max(1, x1 - x0),
3593
4900
  height: Math.max(1, slot),
3594
4901
  rx: "3",
3595
- onMouseMove: (e) => show({ title: labelText7(d.label), items: [{ color, label: "", value: `${fmt(min)} \u2013 ${fmt(max)}` }] }, e),
3596
- onMouseLeave: hide
4902
+ onMouseMove: (e) => show(tipFor(d, i), e),
4903
+ onMouseLeave: hide,
4904
+ onClick: () => clickAt(i)
3597
4905
  },
3598
4906
  i
3599
4907
  );
3600
4908
  });
3601
4909
  };
3602
- const lineX = (i) => n > 1 ? padL + innerW / (n - 1) * i : padL + innerW / 2;
3603
- const yPos = (v) => padT + innerH - frac(v) * innerH;
3604
4910
  const renderBand = () => {
3605
4911
  const maxPts = rows.map((d, i) => [lineX(i), yPos(Number(d.max) || 0)]);
3606
4912
  const minPts = rows.map((d, i) => [lineX(i), yPos(Number(d.min) || 0)]);
@@ -3615,44 +4921,126 @@ function RangeChart({
3615
4921
  rows.map((d, i) => /* @__PURE__ */ jsx(
3616
4922
  "rect",
3617
4923
  {
4924
+ "data-mark": true,
4925
+ "data-selected": selected === baseIdx + i || void 0,
3618
4926
  fill: "transparent",
3619
4927
  x: lineX(i) - hitW / 2,
3620
4928
  y: padT,
3621
4929
  width: hitW,
3622
4930
  height: innerH,
3623
- onMouseMove: (e) => show({ title: labelText7(d.label), items: [{ color, label: "", value: `${fmt(Number(d.min) || 0)} \u2013 ${fmt(Number(d.max) || 0)}` }] }, e),
3624
- onMouseLeave: hide
4931
+ onMouseMove: (e) => show(tipFor(d, i), e),
4932
+ onMouseLeave: hide,
4933
+ onClick: () => clickAt(i)
3625
4934
  },
3626
4935
  i
3627
4936
  ))
3628
4937
  ] });
3629
4938
  };
3630
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: `twc-chart twc-chart--range ${className}`.trim(), "data-type": type, ...rest, children: [
3631
- baseStyles,
3632
- styles,
3633
- /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
3634
- showGrid ? scale.ticks.map(
3635
- (t, i) => isArea ? /* @__PURE__ */ jsx("line", { className: "twc-rangechart__grid", x1: padL, y1: yPos(t), x2: W - padR, y2: yPos(t) }, i) : /* @__PURE__ */ jsx("line", { className: "twc-rangechart__grid", x1: xPos(t), y1: padT, x2: xPos(t), y2: padT + innerH }, i)
3636
- ) : null,
3637
- showAxis ? scale.ticks.map(
3638
- (t, i) => isArea ? /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: padL - 8, y: yPos(t) + 4, textAnchor: "end", children: shortNum(t) }, i) : /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: xPos(t), y: H - 8, textAnchor: "middle", children: shortNum(t) }, i)
3639
- ) : null,
3640
- isArea ? renderBand() : renderBars(),
3641
- showAxis ? rows.map(
3642
- (d, i) => isArea ? /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: lineX(i), y: H - 8, textAnchor: "middle", children: d.label }, i) : /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: padL - 8, y: padT + catBandH * i + catBandH / 2 + 4, textAnchor: "end", children: d.label }, i)
3643
- ) : null
3644
- ] }),
3645
- /* @__PURE__ */ jsx(ChartTooltip, { tip }),
3646
- tableFallback ? /* @__PURE__ */ jsx(
3647
- ChartTable,
4939
+ const renderZones = () => rows.map((d, i) => {
4940
+ let x, y, w, hgt;
4941
+ if (isArea) {
4942
+ const x0 = i === 0 ? padL : (lineX(i - 1) + lineX(i)) / 2;
4943
+ const x1 = i === rows.length - 1 ? padL + innerW : (lineX(i) + lineX(i + 1)) / 2;
4944
+ x = x0;
4945
+ w = x1 - x0;
4946
+ y = padT;
4947
+ hgt = innerH;
4948
+ } else {
4949
+ x = padL;
4950
+ w = innerW;
4951
+ y = padT + catBandH * i;
4952
+ hgt = catBandH;
4953
+ }
4954
+ return /* @__PURE__ */ jsx(
4955
+ "rect",
3648
4956
  {
3649
- id: tableId,
3650
- caption: caption ?? svgAriaLabel,
3651
- columns: ["min", "max"],
3652
- rows: rows.map((d) => ({ label: d.label, values: [fmt(Number(d.min) || 0), fmt(Number(d.max) || 0)] }))
3653
- }
3654
- ) : null
3655
- ] });
4957
+ className: "twc-chart__zone",
4958
+ x,
4959
+ y,
4960
+ width: Math.max(0, w),
4961
+ height: Math.max(0, hgt),
4962
+ "data-clickable": onDataClick ? true : void 0,
4963
+ onMouseMove: (e) => show(tipFor(d, i), e),
4964
+ onMouseLeave: hide,
4965
+ onClick: () => clickDatum(d, i)
4966
+ },
4967
+ i
4968
+ );
4969
+ });
4970
+ const dragBand = zoomable && drag && !drag.pan && drag.end !== drag.start;
4971
+ return /* @__PURE__ */ jsxs(
4972
+ "div",
4973
+ {
4974
+ ref: containerRef,
4975
+ className: `twc-chart twc-chart--range ${className}`.trim(),
4976
+ "data-type": type,
4977
+ "data-clickable": clickable || void 0,
4978
+ "data-has-selection": selected != null || void 0,
4979
+ ...rest,
4980
+ children: [
4981
+ baseStyles,
4982
+ styles,
4983
+ /* @__PURE__ */ jsxs("svg", { ref: svgRef, viewBox: `0 0 ${W} ${H}`, role: "img", "aria-label": svgAriaLabel, "aria-describedby": tableId, preserveAspectRatio: "none", children: [
4984
+ showGrid ? scale.ticks.map(
4985
+ (t, i) => isArea ? /* @__PURE__ */ jsx("line", { className: "twc-rangechart__grid", x1: padL, y1: yPos(t), x2: W - padR, y2: yPos(t) }, i) : /* @__PURE__ */ jsx("line", { className: "twc-rangechart__grid", x1: xPos(t), y1: padT, x2: xPos(t), y2: padT + innerH }, i)
4986
+ ) : null,
4987
+ showAxis ? scale.ticks.map(
4988
+ (t, i) => isArea ? /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: padL - 8, y: yPos(t) + 4, textAnchor: "end", children: shortNum(t) }, i) : /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: xPos(t), y: H - 8, textAnchor: "middle", children: shortNum(t) }, i)
4989
+ ) : null,
4990
+ !zoomable ? renderZones() : null,
4991
+ isArea ? renderBand() : renderBars(),
4992
+ showAxis ? rows.map(
4993
+ (d, i) => isArea ? /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: lineX(i), y: H - 8, textAnchor: "middle", children: d.label }, i) : /* @__PURE__ */ jsx("text", { className: "twc-rangechart__axis", x: padL - 8, y: padT + catBandH * i + catBandH / 2 + 4, textAnchor: "end", children: d.label }, i)
4994
+ ) : null,
4995
+ dragBand ? (() => {
4996
+ const loI = Math.min(drag.start, drag.end), hiI = Math.max(drag.start, drag.end);
4997
+ if (isArea) {
4998
+ const x0 = lineX(loI), x1 = lineX(hiI);
4999
+ return /* @__PURE__ */ jsx("rect", { className: "twc-chart__zoomband", x: x0, y: padT, width: Math.max(0, x1 - x0), height: innerH });
5000
+ }
5001
+ const y0 = padT + catBandH * loI, y1 = padT + catBandH * (hiI + 1);
5002
+ return /* @__PURE__ */ jsx("rect", { className: "twc-chart__zoomband", x: padL, y: y0, width: innerW, height: Math.max(0, y1 - y0) });
5003
+ })() : null,
5004
+ zoomable ? /* @__PURE__ */ jsx(
5005
+ "rect",
5006
+ {
5007
+ className: "twc-chart__overlay",
5008
+ "data-clickable": clickable || void 0,
5009
+ x: padL,
5010
+ y: padT,
5011
+ width: innerW,
5012
+ height: innerH,
5013
+ onMouseMove: onOverlayMove,
5014
+ onMouseLeave: () => {
5015
+ hide();
5016
+ setDrag(null);
5017
+ },
5018
+ onMouseDown: onOverlayDown,
5019
+ onMouseUp: onOverlayUp,
5020
+ onClick: onOverlayClick
5021
+ }
5022
+ ) : null
5023
+ ] }),
5024
+ zoomable && zoom ? /* @__PURE__ */ jsxs("button", { type: "button", className: "twc-chart__zoom-reset", onClick: () => setZoom(null), "aria-label": "Reset zoom", children: [
5025
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
5026
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
5027
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
5028
+ ] }),
5029
+ "Reset"
5030
+ ] }) : null,
5031
+ /* @__PURE__ */ jsx(ChartTooltip, { tip }),
5032
+ tableFallback ? /* @__PURE__ */ jsx(
5033
+ ChartTable,
5034
+ {
5035
+ id: tableId,
5036
+ caption: caption ?? svgAriaLabel,
5037
+ columns: ["min", "max"],
5038
+ rows: allRows.map((d) => ({ label: d.label, values: [fmt(Number(d.min) || 0), fmt(Number(d.max) || 0)] }))
5039
+ }
5040
+ ) : null
5041
+ ]
5042
+ }
5043
+ );
3656
5044
  }
3657
5045
  function labelText7(label) {
3658
5046
  return typeof label === "string" || typeof label === "number" ? String(label) : "";
@@ -7352,6 +8740,7 @@ function Datatable({
7352
8740
  serverMode = false,
7353
8741
  rowCount,
7354
8742
  onServerChange,
8743
+ onColumnVisibilityChange,
7355
8744
  batchActions = [],
7356
8745
  showExport = false,
7357
8746
  showDensity = false,
@@ -7658,6 +9047,19 @@ function Datatable({
7658
9047
  }, [exportOpen]);
7659
9048
  const colByField = React51__default.useMemo(() => Object.fromEntries(cols.map((c) => [c.field, c])), [cols]);
7660
9049
  const visibleCols = cols.filter((c) => !hidden.has(c.field));
9050
+ const colFieldsKey = JSON.stringify(
9051
+ cols.filter((c) => typeof c.field === "string" && c.type !== "actions").map((c) => c.field)
9052
+ );
9053
+ const colFields = React51__default.useMemo(() => JSON.parse(colFieldsKey), [colFieldsKey]);
9054
+ const hiddenKey = JSON.stringify([...hidden].filter((f) => typeof f === "string").sort());
9055
+ const visibleColumns = React51__default.useMemo(() => {
9056
+ const h = new Set(JSON.parse(hiddenKey));
9057
+ return colFields.filter((f) => !h.has(f));
9058
+ }, [colFields, hiddenKey]);
9059
+ const hiddenColumns = React51__default.useMemo(() => {
9060
+ const h = new Set(JSON.parse(hiddenKey));
9061
+ return colFields.filter((f) => h.has(f));
9062
+ }, [colFields, hiddenKey]);
7661
9063
  const ordered = React51__default.useMemo(() => {
7662
9064
  const orderIdx = (f) => {
7663
9065
  const i = order.indexOf(f);
@@ -8000,11 +9402,27 @@ function Datatable({
8000
9402
  pageSize: sizeVal,
8001
9403
  sort,
8002
9404
  filters: filters.map(({ id, ...f }) => f),
8003
- quickFilter: quick.trim()
9405
+ quickFilter: quick.trim(),
9406
+ visibleColumns,
9407
+ hiddenColumns
9408
+ // #191: server can project only the visible columns
8004
9409
  });
8005
9410
  }, 250);
8006
9411
  return () => clearTimeout(t);
8007
- }, [serverMode, pageVal, sizeVal, sort, filters, quick]);
9412
+ }, [serverMode, pageVal, sizeVal, sort, filters, quick, visibleColumns, hiddenColumns]);
9413
+ const onColVisRef = React51__default.useRef(onColumnVisibilityChange);
9414
+ onColVisRef.current = onColumnVisibilityChange;
9415
+ const lastVisibleKey = React51__default.useRef(null);
9416
+ React51__default.useEffect(() => {
9417
+ const key = JSON.stringify(visibleColumns);
9418
+ if (lastVisibleKey.current === null) {
9419
+ lastVisibleKey.current = key;
9420
+ return;
9421
+ }
9422
+ if (lastVisibleKey.current === key) return;
9423
+ lastVisibleKey.current = key;
9424
+ onColVisRef.current?.(visibleColumns);
9425
+ }, [visibleColumns]);
8008
9426
  function cycleSort(field) {
8009
9427
  setSort((s) => !s || s.field !== field ? { field, dir: "asc" } : s.dir === "asc" ? { field, dir: "desc" } : null);
8010
9428
  }
@@ -14027,6 +15445,12 @@ var SHELL_CSS = `
14027
15445
  min-height: 60px; padding-inline: var(--space-5); border-bottom: var(--border-thin) solid var(--color-border); background: var(--color-surface); }
14028
15446
  .twc-shell__content { flex: 1 1 auto; min-height: 0; overflow: auto; }
14029
15447
  .twc-shell__content[data-padded="true"] { padding: var(--space-6) var(--space-5); }
15448
+ /* #190: <main> is the skip-link target (tabindex="-1") \u2014 a programmatic focus target,
15449
+ not tab-reachable and not an interactive control. Clicking content mouse-focuses it,
15450
+ then the first keypress flips to keyboard modality and :focus-visible would draw a UA
15451
+ outline around the ENTIRE content area. Suppress it \u2014 the skip LINK keeps its own
15452
+ visible ring (.twc-shell__skip:focus-visible) and SR focus announcement is unaffected. */
15453
+ .twc-shell__content:focus, .twc-shell__content:focus-visible { outline: none; }
14030
15454
  /* Mobile: stack the shell so the sidebar (rendered as an off-canvas overlay) no
14031
15455
  longer reserves a column \u2014 the main content spans the full width. */
14032
15456
  @media (max-width: 720px) {
@@ -14161,8 +15585,10 @@ var Container = React51__default.forwardRef(function Container2({
14161
15585
  style: {
14162
15586
  width: "100%",
14163
15587
  maxWidth: typeof max === "number" ? `${max}px` : max,
14164
- marginInline: "auto",
14165
- paddingInline: padded ? "var(--space-5)" : void 0,
15588
+ marginInlineStart: "auto",
15589
+ marginInlineEnd: "auto",
15590
+ paddingInlineStart: padded ? "var(--space-5)" : void 0,
15591
+ paddingInlineEnd: padded ? "var(--space-5)" : void 0,
14166
15592
  ...style,
14167
15593
  ...flatStyle
14168
15594
  },
@@ -14412,7 +15838,10 @@ var Heading = React51__default.forwardRef(function Heading2({
14412
15838
  className: `twc-heading ${className}`.trim(),
14413
15839
  "data-twc-sx": sxAttr,
14414
15840
  style: {
14415
- margin: 0,
15841
+ marginTop: 0,
15842
+ marginRight: 0,
15843
+ marginBottom: 0,
15844
+ marginLeft: 0,
14416
15845
  fontFamily: "var(--font-sans)",
14417
15846
  fontWeight: "var(--font-bold)",
14418
15847
  color: "var(--color-text)",
@@ -14477,7 +15906,10 @@ var Text = React51__default.forwardRef(function Text2({
14477
15906
  className: `twc-text ${className}`.trim(),
14478
15907
  "data-twc-sx": sxAttr,
14479
15908
  style: {
14480
- margin: 0,
15909
+ marginTop: 0,
15910
+ marginRight: 0,
15911
+ marginBottom: 0,
15912
+ marginLeft: 0,
14481
15913
  fontFamily: "var(--font-sans)",
14482
15914
  lineHeight: 1.6,
14483
15915
  fontSize: `var(--text-${size})`,