@automattic/charts 4.9.0 → 4.10.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.cjs +159 -66
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +14 -0
  5. package/dist/index.d.cts +27 -22
  6. package/dist/index.d.ts +27 -22
  7. package/dist/index.js +159 -66
  8. package/dist/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/charts/area-chart/area-chart.tsx +2 -1
  11. package/src/charts/area-chart/test/area-chart.test.tsx +0 -8
  12. package/src/charts/area-chart/types.ts +0 -2
  13. package/src/charts/bar-chart/bar-chart.tsx +7 -2
  14. package/src/charts/bar-chart/test/bar-chart.test.tsx +0 -8
  15. package/src/charts/bar-chart/types.ts +0 -2
  16. package/src/charts/bar-list-chart/bar-list-chart.tsx +3 -0
  17. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +23 -7
  18. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +22 -0
  19. package/src/charts/conversion-funnel-chart/types.ts +1 -1
  20. package/src/charts/geo-chart/geo-chart.tsx +17 -2
  21. package/src/charts/geo-chart/test/geo-chart.test.tsx +9 -4
  22. package/src/charts/geo-chart/types.ts +1 -1
  23. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +0 -6
  24. package/src/charts/heatmap-chart/types.ts +0 -2
  25. package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
  26. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +6 -0
  27. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +34 -19
  28. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +27 -1
  29. package/src/charts/leaderboard-chart/types.ts +1 -0
  30. package/src/charts/line-chart/line-chart.tsx +3 -0
  31. package/src/charts/line-chart/test/line-chart.test.tsx +0 -8
  32. package/src/charts/line-chart/types.ts +0 -2
  33. package/src/charts/pie-chart/pie-chart.module.scss +3 -0
  34. package/src/charts/pie-chart/pie-chart.tsx +0 -5
  35. package/src/charts/pie-chart/test/pie-chart.test.tsx +4 -8
  36. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +3 -0
  37. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +0 -5
  38. package/src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx +8 -3
  39. package/src/charts/test/accessible-name.test.tsx +181 -0
  40. package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +63 -3
  41. package/src/components/legend/hooks/use-chart-legend-items.ts +63 -1
  42. package/src/components/legend/private/base-legend.tsx +27 -15
  43. package/src/components/legend/test/legend.test.tsx +42 -0
  44. package/src/components/legend/types.ts +2 -0
  45. package/src/hooks/test/use-xychart-theme.test.tsx +22 -0
  46. package/src/hooks/use-xychart-theme.ts +33 -2
  47. package/src/index.ts +1 -0
  48. package/src/types.ts +19 -1
  49. package/src/utils/resolve-font-size.ts +9 -10
  50. package/src/utils/test/resolve-font-size.test.ts +11 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.10.0] - 2026-10-14
9
+ ### Added
10
+ - Give the geo, leaderboard, conversion funnel and bar list charts an accessible name, and accept `ariaLabel` on every chart except Sparkline. [#53516]
11
+ - Legend: Add a comparisonSeriesItems option that adds one static item per comparison series, named after its metric and hidden along with it. [#53457]
12
+
13
+ ### Changed
14
+ - Reject invalid values like `12.px` when resolving theme font sizes. [#53475]
15
+
16
+ ### Fixed
17
+ - Conversion funnel chart: Tighten the spacing on a short chart so its bars stay visible. [#53554]
18
+ - Ignore invalid theme label font sizes, such as negative values, so axis labels no longer clip. Accept a leading `+` sign, as CSS does. [#53498]
19
+ - Pie Chart, Pie Semi-Circle Chart: Paint the tooltip above content passed as children, such as a center total. [#53449]
20
+
8
21
  ## [4.9.0] - 2026-10-12
9
22
  ### Added
10
23
  - Heatmap chart: Add a fitCells option that grows compact cells to fill the chart, wrapping column groups onto more rows when that makes the cells larger, and draw column group labels at 13px in the body text color. [#53341]
@@ -1174,6 +1187,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1174
1187
  - Fixed lints following ESLint rule changes for TS [#40584]
1175
1188
  - Fixing a bug in Chart storybook data. [#40640]
1176
1189
 
1190
+ [4.10.0]: https://github.com/Automattic/charts/compare/v4.9.0...v4.10.0
1177
1191
  [4.9.0]: https://github.com/Automattic/charts/compare/v4.8.0...v4.9.0
1178
1192
  [4.8.0]: https://github.com/Automattic/charts/compare/v4.7.0...v4.8.0
1179
1193
  [4.7.0]: https://github.com/Automattic/charts/compare/v4.6.0...v4.7.0
package/dist/index.cjs CHANGED
@@ -671,16 +671,17 @@ function readCustomProperty(varName, styles) {
671
671
  }
672
672
  //#endregion
673
673
  //#region src/utils/resolve-font-size.ts
674
+ const toFontSize = (size) => Number.isFinite(size) && size >= 0 ? size : void 0;
674
675
  /**
675
676
  * Resolve a theme `fontSize` value into a plain number suitable for
676
677
  * canvas-based measurement (e.g. `getStringWidth`).
677
678
  *
678
679
  * Accepts:
679
- * - A number — returned as-is
680
+ * - A finite, non-negative number — returned as-is
680
681
  * - A pixel string like `"12px"` — parsed and returned as a number
681
682
  *
682
- * Returns `undefined` for any other input (missing value, NaN, or
683
- * relative units like `rem`/`em`/`%`/`vh`) so callers can fall back to
683
+ * Returns `undefined` for any other input (missing value, a non-finite or
684
+ * negative size, or relative units like `rem`/`em`/`%`/`vh`) so callers can fall back to
684
685
  * their own default. Relative units are intentionally rejected because
685
686
  * we cannot resolve them to absolute pixels here without a parent
686
687
  * computed style, and silently returning the unitless prefix
@@ -690,12 +691,10 @@ function readCustomProperty(varName, styles) {
690
691
  * @return Parsed numeric font size in pixels, or `undefined` when unresolvable
691
692
  */
692
693
  const resolveFontSize = (val) => {
693
- if (typeof val === "number") return isNaN(val) ? void 0 : val;
694
+ if (typeof val === "number") return toFontSize(val);
694
695
  if (typeof val === "string") {
695
- const match = val.trim().match(/^(-?\d+\.?\d*|-?\.\d+)(px)?$/);
696
- if (!match) return;
697
- const parsed = parseFloat(match[1]);
698
- return isNaN(parsed) ? void 0 : parsed;
696
+ const match = val.trim().match(/^\+?((?:\d*\.)?\d+)(px)?$/);
697
+ return match ? toFontSize(parseFloat(match[1])) : void 0;
699
698
  }
700
699
  };
701
700
  //#endregion
@@ -1574,6 +1573,20 @@ const useChartScopeElement = () => (0, react.useContext)(ChartScopeContext);
1574
1573
  const useStandaloneScopeClass = () => (0, react.useContext)(GlobalChartsContext) ? void 0 : CHART_SCOPE_CLASS;
1575
1574
  //#endregion
1576
1575
  //#region src/hooks/use-xychart-theme.ts
1576
+ const STARTS_LIKE_A_NUMBER = /^[+-]?[\d.]/;
1577
+ const NON_NEGATIVE_CSS_NUMBER = /^\+?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:[a-z]+|%)?$/i;
1578
+ const isRenderableFontSize = (fontSize) => {
1579
+ if (typeof fontSize === "number") return resolveFontSize(fontSize) !== void 0;
1580
+ const value = fontSize.trim();
1581
+ if (!STARTS_LIKE_A_NUMBER.test(value)) return true;
1582
+ return NON_NEGATIVE_CSS_NUMBER.test(value) && Number.isFinite(parseFloat(value));
1583
+ };
1584
+ const withRenderableFontSize = (style) => {
1585
+ if (style?.fontSize === void 0 || isRenderableFontSize(style.fontSize)) return style;
1586
+ const rest = { ...style };
1587
+ delete rest.fontSize;
1588
+ return rest;
1589
+ };
1577
1590
  const useXYChartTheme = (data) => {
1578
1591
  const theme = useGlobalChartsTheme();
1579
1592
  const scopeElement = useChartScopeElement();
@@ -1604,9 +1617,10 @@ const useXYChartTheme = (data) => {
1604
1617
  yAxisLineStyles: { stroke: CATALOG_POINTERS.axisY },
1605
1618
  yTickLineStyles: { stroke: CATALOG_POINTERS.tickY },
1606
1619
  svgLabelSmall: {
1607
- ...theme.svgLabelSmall,
1620
+ ...withRenderableFontSize(theme.svgLabelSmall),
1608
1621
  fill: CATALOG_POINTERS.labelAxis
1609
- }
1622
+ },
1623
+ svgLabelBig: withRenderableFontSize(theme.svgLabelBig)
1610
1624
  });
1611
1625
  }, [
1612
1626
  theme,
@@ -2119,7 +2133,7 @@ const getLegendItemAriaLabel = (text, value, visible, interactive) => {
2119
2133
  if (visible) return;
2120
2134
  return (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n._x)("%s: hidden", "hidden non-interactive legend item", "jetpack-charts"), accessibleText);
2121
2135
  };
2122
- const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "horizontal", alignment = "center", shape = "rect", fill = valueOrIdentityString, size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform, itemStyles, itemClassName, labelStyles, labelClassName, shapeStyles, render, interactive = false, chartId }, ref) => {
2136
+ const BaseLegend = (0, react.forwardRef)(({ items: allItems, className, orientation = "horizontal", alignment = "center", shape = "rect", fill = valueOrIdentityString, size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform, itemStyles, itemClassName, labelStyles, labelClassName, shapeStyles, render, interactive = false, chartId }, ref) => {
2123
2137
  const { margin: itemMargin = "0", flexDirection: itemDirection = "row" } = itemStyles ?? {};
2124
2138
  const { justifyContent: labelJustifyContent = "flex-start", flex: labelFlex = "0 0 auto", margin: labelMargin = "0 4px", maxWidth, textOverflow = "wrap" } = labelStyles ?? {};
2125
2139
  const { width: shapeWidth = 16, height: shapeHeight = 16, margin: shapeMargin = "2px 4px 2px 0" } = shapeStyles ?? {};
@@ -2127,6 +2141,16 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
2127
2141
  const context = (0, react.useContext)(GlobalChartsContext);
2128
2142
  const chartInstanceContext = (0, react.useContext)(ChartInstanceContext);
2129
2143
  const standaloneScopeClass = useStandaloneScopeClass();
2144
+ const isSeriesVisible = (0, react.useCallback)((seriesLabel) => {
2145
+ if (chartInstanceContext?.isSeriesVisible) return chartInstanceContext.isSeriesVisible(seriesLabel);
2146
+ if (!chartId || !context) return true;
2147
+ return context.isSeriesVisible(chartId, seriesLabel);
2148
+ }, [
2149
+ chartId,
2150
+ chartInstanceContext,
2151
+ context
2152
+ ]);
2153
+ const items = (0, react.useMemo)(() => allItems.filter((item) => item.interactive !== false || !item.describesSeries || isSeriesVisible(item.describesSeries)), [allItems, isSeriesVisible]);
2130
2154
  const domain = items.map((item) => item.label);
2131
2155
  const legendScale = (0, _visx_scale.scaleOrdinal)({
2132
2156
  domain,
@@ -2149,15 +2173,6 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
2149
2173
  chartId,
2150
2174
  context
2151
2175
  ]);
2152
- const isSeriesVisible = (0, react.useCallback)((seriesLabel) => {
2153
- if (chartInstanceContext?.isSeriesVisible) return chartInstanceContext.isSeriesVisible(seriesLabel);
2154
- if (!chartId || !context) return true;
2155
- return context.isSeriesVisible(chartId, seriesLabel);
2156
- }, [
2157
- chartId,
2158
- chartInstanceContext,
2159
- context
2160
- ]);
2161
2176
  const createClickHandler = (0, react.useCallback)((seriesLabels, itemInteractive) => {
2162
2177
  if (!itemInteractive) return;
2163
2178
  return () => handleLegendClick(seriesLabels);
@@ -2432,6 +2447,34 @@ function buildComparisonLegendItem(seriesData, items, label, getElementStyles, l
2432
2447
  };
2433
2448
  }
2434
2449
  /**
2450
+ * Builds one static item per comparison series folded into another series' item, named after
2451
+ * that item and styled like the comparison series itself.
2452
+ * @param seriesData - The series data to search for comparison series
2453
+ * @param items - The legend items already built from the series
2454
+ * @param formatLabel - Names an item after the label of the item it was folded into
2455
+ * @param getElementStyles - Function to get element styles
2456
+ * @param legendShape - The shape type for legend items (string literal or React component)
2457
+ * @return The legend items, in series order
2458
+ */
2459
+ function buildPerSeriesComparisonItems(seriesData, items, formatLabel, getElementStyles, legendShape) {
2460
+ return seriesData.flatMap((series, index) => {
2461
+ const owner = items.find((item) => item.seriesLabels?.includes(series.label));
2462
+ if (series.options?.type !== "comparison" || !owner || owner.seriesLabels?.[0] === series.label) return [];
2463
+ const { color, shapeStyles } = getElementStyles({
2464
+ data: series,
2465
+ index,
2466
+ legendShape
2467
+ });
2468
+ return [{
2469
+ label: formatLabel(owner.label),
2470
+ color,
2471
+ shapeStyle: shapeStyles,
2472
+ interactive: false,
2473
+ describesSeries: series.label
2474
+ }];
2475
+ });
2476
+ }
2477
+ /**
2435
2478
  * Processes point data into legend items
2436
2479
  * @param pointData - The point data to process
2437
2480
  * @param getElementStyles - Function to get element styles
@@ -2467,13 +2510,14 @@ function processPointData(pointData, getElementStyles, showValues, legendValueDi
2467
2510
  * @return Array of legend items ready for display
2468
2511
  */
2469
2512
  function useChartLegendItems(data, options = {}, legendShape) {
2470
- const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, comparisonItem = false, renderGlyph } = options;
2513
+ const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, comparisonItem = false, comparisonSeriesItems, renderGlyph } = options;
2471
2514
  const { getElementStyles } = useGlobalChartsContext();
2472
2515
  return (0, react.useMemo)(() => {
2473
2516
  if (!data || !Array.isArray(data) || data.length === 0) return [];
2474
2517
  if ("data" in data[0]) {
2475
2518
  const seriesData = data;
2476
2519
  const items = processSeriesData(seriesData, getElementStyles, showValues, withGlyph, glyphSize, collapseGroups, renderGlyph, legendShape);
2520
+ if (comparisonSeriesItems) return [...items, ...buildPerSeriesComparisonItems(seriesData, items, comparisonSeriesItems, getElementStyles, legendShape)];
2477
2521
  const comparison = comparisonItem ? buildComparisonLegendItem(seriesData, items, typeof comparisonItem === "string" ? comparisonItem : (0, _wordpress_i18n.__)("Comparison period", "jetpack-charts"), getElementStyles, legendShape) : null;
2478
2522
  return comparison ? [...items, comparison] : items;
2479
2523
  }
@@ -2487,6 +2531,7 @@ function useChartLegendItems(data, options = {}, legendShape) {
2487
2531
  glyphSize,
2488
2532
  collapseGroups,
2489
2533
  comparisonItem,
2534
+ comparisonSeriesItems,
2490
2535
  renderGlyph,
2491
2536
  legendShape
2492
2537
  ]);
@@ -4728,6 +4773,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: pro
4728
4773
  const legendInteractive = legend.interactive ?? false;
4729
4774
  const legendCollapseGroups = legend.collapseGroups ?? false;
4730
4775
  const legendComparisonItem = legend.comparisonItem ?? false;
4776
+ const legendComparisonSeriesItems = legend.comparisonSeriesItems;
4731
4777
  const legendShape = legend.shape ?? "line";
4732
4778
  const legendPosition = legend.position ?? "bottom";
4733
4779
  const formatting = useChartFormatting();
@@ -4912,12 +4958,14 @@ const LineChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: pro
4912
4958
  glyphSize: Math.max(0, toNumber(glyphStyle?.radius) ?? 4),
4913
4959
  collapseGroups: legendCollapseGroups,
4914
4960
  comparisonItem: legendComparisonItem,
4961
+ comparisonSeriesItems: legendComparisonSeriesItems,
4915
4962
  renderGlyph
4916
4963
  }), [
4917
4964
  withLegendGlyph,
4918
4965
  glyphStyle?.radius,
4919
4966
  legendCollapseGroups,
4920
4967
  legendComparisonItem,
4968
+ legendComparisonSeriesItems,
4921
4969
  renderGlyph
4922
4970
  ]), legendShape);
4923
4971
  const chartMetadata = (0, react.useMemo)(() => ({
@@ -5386,8 +5434,13 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: pro
5386
5434
  withGlyph: false,
5387
5435
  glyphSize: 0,
5388
5436
  collapseGroups: legend.collapseGroups ?? false,
5389
- comparisonItem: legend.comparisonItem ?? false
5390
- }), [legend.collapseGroups, legend.comparisonItem]), legendShape);
5437
+ comparisonItem: legend.comparisonItem ?? false,
5438
+ comparisonSeriesItems: legend.comparisonSeriesItems
5439
+ }), [
5440
+ legend.collapseGroups,
5441
+ legend.comparisonItem,
5442
+ legend.comparisonSeriesItems
5443
+ ]), legendShape);
5391
5444
  const chartMetadata = (0, react.useMemo)(() => ({
5392
5445
  stacked,
5393
5446
  stackOffset,
@@ -6380,6 +6433,7 @@ const BarChartInternal = ({ data, ariaLabel, chartId: providedChartId, width, he
6380
6433
  const legendInteractive = legend.interactive ?? false;
6381
6434
  const legendCollapseGroups = legend.collapseGroups ?? false;
6382
6435
  const legendComparisonItem = legend.comparisonItem ?? false;
6436
+ const legendComparisonSeriesItems = legend.comparisonSeriesItems;
6383
6437
  const horizontal = orientation === "horizontal";
6384
6438
  const chartId = useChartId(providedChartId);
6385
6439
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -6392,8 +6446,13 @@ const BarChartInternal = ({ data, ariaLabel, chartId: providedChartId, width, he
6392
6446
  });
6393
6447
  const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
6394
6448
  collapseGroups: legendCollapseGroups,
6395
- comparisonItem: legendComparisonItem
6396
- }), [legendCollapseGroups, legendComparisonItem]));
6449
+ comparisonItem: legendComparisonItem,
6450
+ comparisonSeriesItems: legendComparisonSeriesItems
6451
+ }), [
6452
+ legendCollapseGroups,
6453
+ legendComparisonItem,
6454
+ legendComparisonSeriesItems
6455
+ ]));
6397
6456
  const { getElementStyles } = useGlobalChartsContext();
6398
6457
  const isSeriesRendered = (0, react.useCallback)((series) => isSeriesVisible(series.label), [isSeriesVisible]);
6399
6458
  const hasWholeNumberValues = (0, react.useMemo)(() => hasOnlyWholeNumbers(dataSorted.filter(isSeriesRendered)), [dataSorted, isSeriesRendered]);
@@ -6904,7 +6963,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
6904
6963
  right: 20,
6905
6964
  bottom: 0,
6906
6965
  top: 0
6907
- }, className, ...rest }) => {
6966
+ }, className, ariaLabel, ...rest }) => {
6908
6967
  const { getElementStyles } = useGlobalChartsContext();
6909
6968
  const tintedData = (0, react.useMemo)(() => {
6910
6969
  if (data.length > 1) return data;
@@ -6979,6 +7038,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
6979
7038
  xScale: chartOptions.xScale,
6980
7039
  yScale: chartOptions.yScale
6981
7040
  },
7041
+ ariaLabel: ariaLabel ?? (0, _wordpress_i18n.__)("Bar list chart", "jetpack-charts"),
6982
7042
  ...rest
6983
7043
  });
6984
7044
  };
@@ -7054,6 +7114,8 @@ const useFunnelSelection = (hideTooltip) => {
7054
7114
  };
7055
7115
  //#endregion
7056
7116
  //#region src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx
7117
+ /** Chart height (px) below which the funnel tightens its gaps so the bars keep their room. */
7118
+ const SHORT_CHART_HEIGHT = 180;
7057
7119
  /**
7058
7120
  * Internal ConversionFunnelChart component with chart registration
7059
7121
  *
@@ -7065,6 +7127,7 @@ const useFunnelSelection = (hideTooltip) => {
7065
7127
  * @param props.loading - Whether the chart is in loading state
7066
7128
  * @param props.animation - Whether to show chart animation on initial render or not
7067
7129
  * @param props.className - Additional CSS class name
7130
+ * @param props.ariaLabel - Accessible name for the chart group
7068
7131
  * @param props.height - Height of the chart container. Falls back to style.height if set, otherwise defaults to "100%".
7069
7132
  * @param props.style - Custom styling
7070
7133
  * @param props.renderStepLabel - Custom render function for step labels
@@ -7074,7 +7137,7 @@ const useFunnelSelection = (hideTooltip) => {
7074
7137
  * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
7075
7138
  * @return JSX element representing the conversion funnel chart
7076
7139
  */
7077
- const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
7140
+ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, ariaLabel, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
7078
7141
  const chartId = useChartId(providedChartId);
7079
7142
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
7080
7143
  const chartRef = (0, react.useRef)(null);
@@ -7083,10 +7146,13 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7083
7146
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
7084
7147
  const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
7085
7148
  const standaloneScopeClass = useStandaloneScopeClass();
7149
+ const [measureChartRef, , chartHeight] = useElementSize();
7150
+ const gap = chartHeight > 0 && chartHeight < SHORT_CHART_HEIGHT ? "sm" : "xl";
7086
7151
  const setChartRef = (0, react.useCallback)((node) => {
7087
7152
  chartRef.current = node;
7088
7153
  setScopeNode(node);
7089
- }, []);
7154
+ measureChartRef(node);
7155
+ }, [measureChartRef]);
7090
7156
  const clearSelectionAndRef = (0, react.useCallback)(() => {
7091
7157
  clearSelection();
7092
7158
  selectedBarRef.current = null;
@@ -7234,7 +7300,9 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7234
7300
  const maxRate = Math.max(...steps.map((step) => step.rate));
7235
7301
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7236
7302
  direction: "column",
7237
- gap: "xl",
7303
+ gap,
7304
+ role: "group",
7305
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Conversion funnel chart", "jetpack-charts"),
7238
7306
  ref: setChartRef,
7239
7307
  className: (0, clsx.default)("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
7240
7308
  style: {
@@ -7265,7 +7333,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7265
7333
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7266
7334
  direction: "column",
7267
7335
  className: (0, clsx.default)(conversion_funnel_chart_module_default["funnel-step"], isColorPaletteResolved && conversion_funnel_chart_module_default["funnel-step--animated"], isBlurred && conversion_funnel_chart_module_default["funnel-step--blurred"]),
7268
- gap: "xl",
7336
+ gap,
7269
7337
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7270
7338
  direction: "column",
7271
7339
  gap: "xs",
@@ -7459,6 +7527,7 @@ function normalizeGeoChartError(eventArgs) {
7459
7527
  *
7460
7528
  * @param props - The props for the GeoChart component
7461
7529
  * @param props.data - Data in Google Charts format (array of arrays with headers)
7530
+ * @param props.ariaLabel - Accessible name of the chart
7462
7531
  * @param props.width - Width of the chart in pixels
7463
7532
  * @param props.height - Height of the chart in pixels
7464
7533
  * @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
@@ -7470,7 +7539,7 @@ function normalizeGeoChartError(eventArgs) {
7470
7539
  * @param props.renderPlaceholder - Optional render function for the loading placeholder
7471
7540
  * @return A React component displaying an interactive map with data visualization
7472
7541
  */
7473
- const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", displayMode = "regions", domain, onError, renderPlaceholder }) => {
7542
+ const GeoChartInternal = ({ className, data, ariaLabel, width, height, region = "world", resolution = "countries", displayMode = "regions", domain, onError, renderPlaceholder }) => {
7474
7543
  const { getElementStyles } = useGlobalChartsContext();
7475
7544
  const scopeElement = useChartScopeElement();
7476
7545
  const containerRef = (0, react.useRef)(null);
@@ -7558,8 +7627,14 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7558
7627
  sanitizedData.hasHtmlTooltips
7559
7628
  ]);
7560
7629
  const chartEvents = (0, react.useMemo)(() => {
7561
- if (!onError) return;
7562
- return [{
7630
+ const readyEvent = {
7631
+ eventName: "ready",
7632
+ callback: () => {
7633
+ containerRef.current?.querySelectorAll("svg").forEach((svg) => svg.setAttribute("aria-hidden", "true"));
7634
+ }
7635
+ };
7636
+ if (!onError) return [readyEvent];
7637
+ return [readyEvent, {
7563
7638
  eventName: "error",
7564
7639
  callback: ({ eventArgs }) => {
7565
7640
  onError(normalizeGeoChartError(eventArgs));
@@ -7568,6 +7643,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7568
7643
  }, [onError]);
7569
7644
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
7570
7645
  ref: containerRef,
7646
+ role: "group",
7647
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Geo chart", "jetpack-charts"),
7571
7648
  className: (0, clsx.default)("geo-chart", geo_chart_module_default.container, className),
7572
7649
  style: {
7573
7650
  width,
@@ -8122,7 +8199,7 @@ const CELL_VALUE_MODIFIER = {
8122
8199
  white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
8123
8200
  };
8124
8201
  const NO_ROW_LABELS = [];
8125
- const TOOLTIP_BOX_STYLE = { zIndex: 3 };
8202
+ const TOOLTIP_BOX_STYLE$1 = { zIndex: 3 };
8126
8203
  const largest = (root, selector, size) => Math.max(0, ...Array.from(root.querySelectorAll(selector), (el) => el[size]));
8127
8204
  const gridBox = (chart, grid) => {
8128
8205
  let height = chart.clientHeight;
@@ -8145,9 +8222,9 @@ const cellName = (info) => info.cellLabel || [
8145
8222
  const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, fitCells = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipStyle, children }) => {
8146
8223
  const chartId = useChartId(providedChartId);
8147
8224
  const tooltipBoxStyle = tooltipStyle ? {
8148
- ...TOOLTIP_BOX_STYLE,
8225
+ ...TOOLTIP_BOX_STYLE$1,
8149
8226
  ...tooltipStyle
8150
- } : TOOLTIP_BOX_STYLE;
8227
+ } : TOOLTIP_BOX_STYLE$1;
8151
8228
  const { getElementStyles, theme } = useGlobalChartsContext();
8152
8229
  const scopeElement = useChartScopeElement();
8153
8230
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -11637,8 +11714,8 @@ const SUBPIXEL_TOLERANCE = .5;
11637
11714
  * depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
11638
11715
  * whatever a caller renders as a label, so any restated number would drift.
11639
11716
  *
11640
- * Every row is a direct grid child marked with `data-row-index`; interactive rows
11641
- * use a button wrapper and non-interactive rows use a div wrapper.
11717
+ * Every row is a `listitem` div, a direct grid child marked with `data-row-index`;
11718
+ * interactive rows hold a button inside it.
11642
11719
  *
11643
11720
  * @param enabled - Whether to measure at all. When false every row fits.
11644
11721
  * @param rowCount - Total number of rows rendered.
@@ -11780,6 +11857,7 @@ var leaderboard_chart_module_default = {
11780
11857
  "overlayLabel": "a8ccharts-GovfoW-overlayLabel",
11781
11858
  "row": "a8ccharts-GovfoW-row",
11782
11859
  "rowButton": "a8ccharts-GovfoW-rowButton",
11860
+ "rowItem": "a8ccharts-GovfoW-rowItem",
11783
11861
  "stretch": "a8ccharts-GovfoW-stretch",
11784
11862
  "valueContainer": "a8ccharts-GovfoW-valueContainer"
11785
11863
  };
@@ -11892,9 +11970,10 @@ const BarWithLabel = ({ entry, withComparison, withOverlayLabel, primaryColor, s
11892
11970
  * @param props.children - Child components for composition API
11893
11971
  * @param props.className - Additional CSS class name
11894
11972
  * @param props.style - Custom styling for the chart container
11973
+ * @param props.ariaLabel - Accessible name for the list, defaulting to "Leaderboard chart"
11895
11974
  * @return JSX element representing the leaderboard chart
11896
11975
  */
11897
- const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, withComparison = false, withOverlayLabel = false, primaryColor, secondaryColor, valueFormatter = defaultValueFormatter, deltaFormatter = defaultDeltaFormatter, animation, loading = false, fitRows = false, showLegend = false, legend = {}, legendLabels, gap = "md", className, style, children }) => {
11976
+ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, withComparison = false, withOverlayLabel = false, primaryColor, secondaryColor, valueFormatter = defaultValueFormatter, deltaFormatter = defaultDeltaFormatter, animation, loading = false, fitRows = false, showLegend = false, legend = {}, legendLabels, gap = "md", ariaLabel, className, style, children }) => {
11898
11977
  const legendInteractive = legend.interactive ?? false;
11899
11978
  const legendPosition = legend.position ?? "bottom";
11900
11979
  const chartId = useChartId(providedChartId);
@@ -12025,6 +12104,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
12025
12104
  className: leaderboard_chart_module_default.emptyState,
12026
12105
  children: getAllHiddenMessage(legendInteractive, "series")
12027
12106
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default, {
12107
+ role: "list",
12108
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Leaderboard chart", "jetpack-charts"),
12028
12109
  templateColumns: "minmax(0, 1fr) auto",
12029
12110
  rowGap: ROW_GAP,
12030
12111
  columnGap: COLUMN_GAP,
@@ -12072,40 +12153,49 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
12072
12153
  })
12073
12154
  ]
12074
12155
  })] });
12075
- if (entry.onClick && entry.hasInteractiveLabel) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12156
+ if (entry.onClick && entry.hasInteractiveLabel) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12157
+ role: "listitem",
12076
12158
  "data-row-index": rowIndex,
12077
12159
  style: rowStyle,
12078
- className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow, leaderboard_chart_module_default.layeredRow),
12079
- onClick: forwardCellClickToRowButton,
12080
- children: [
12081
- rowCells,
12082
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
12083
- type: "button",
12084
- className: leaderboard_chart_module_default.rowButton,
12085
- onClick: entry.onClick,
12086
- "aria-label": entry.ariaLabel
12087
- }),
12088
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
12089
- className: leaderboard_chart_module_default.chevron,
12090
- icon: _wordpress_icons.chevronRight,
12091
- size: 24
12092
- })
12093
- ]
12160
+ className: leaderboard_chart_module_default.rowItem,
12161
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12162
+ className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow, leaderboard_chart_module_default.layeredRow),
12163
+ onClick: forwardCellClickToRowButton,
12164
+ children: [
12165
+ rowCells,
12166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
12167
+ type: "button",
12168
+ className: leaderboard_chart_module_default.rowButton,
12169
+ onClick: entry.onClick,
12170
+ "aria-label": entry.ariaLabel
12171
+ }),
12172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
12173
+ className: leaderboard_chart_module_default.chevron,
12174
+ icon: _wordpress_icons.chevronRight,
12175
+ size: 24
12176
+ })
12177
+ ]
12178
+ })
12094
12179
  }, entry.id);
12095
- if (entry.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12096
- type: "button",
12180
+ if (entry.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12181
+ role: "listitem",
12097
12182
  "data-row-index": rowIndex,
12098
12183
  style: rowStyle,
12099
- className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow),
12100
- onClick: entry.onClick,
12101
- "aria-label": entry.ariaLabel,
12102
- children: [rowCells, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
12103
- className: leaderboard_chart_module_default.chevron,
12104
- icon: _wordpress_icons.chevronRight,
12105
- size: 24
12106
- })]
12184
+ className: leaderboard_chart_module_default.rowItem,
12185
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12186
+ type: "button",
12187
+ className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow),
12188
+ onClick: entry.onClick,
12189
+ "aria-label": entry.ariaLabel,
12190
+ children: [rowCells, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
12191
+ className: leaderboard_chart_module_default.chevron,
12192
+ icon: _wordpress_icons.chevronRight,
12193
+ size: 24
12194
+ })]
12195
+ })
12107
12196
  }, entry.id);
12108
12197
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12198
+ role: "listitem",
12109
12199
  "data-row-index": rowIndex,
12110
12200
  style: rowStyle,
12111
12201
  className: leaderboard_chart_module_default.row,
@@ -12142,11 +12232,13 @@ const PieSelectionAnnouncement = ({ data }) => /* @__PURE__ */ (0, react_jsx_run
12142
12232
  });
12143
12233
  //#endregion
12144
12234
  //#region src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx
12235
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
12145
12236
  const PieTooltip = ({ top, left, selectedIndex, tooltipRef, children }) => {
12146
12237
  const standaloneScopeClass = useStandaloneScopeClass();
12147
12238
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12148
12239
  top,
12149
12240
  left,
12241
+ style: TOOLTIP_BOX_STYLE,
12150
12242
  children: selectedIndex === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12151
12243
  className: standaloneScopeClass,
12152
12244
  role: "tooltip",
@@ -12369,6 +12461,7 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
12369
12461
  //#endregion
12370
12462
  //#region src/charts/pie-chart/pie-chart.module.scss
12371
12463
  var pie_chart_module_default = {
12464
+ "pie-chart": "a8ccharts-gnszbG-pie-chart",
12372
12465
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
12373
12466
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
12374
12467
  "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",