@automattic/charts 4.8.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 (75) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/index.cjs +766 -277
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +72 -8
  5. package/dist/index.d.cts +54 -15
  6. package/dist/index.d.ts +54 -15
  7. package/dist/index.js +770 -281
  8. package/dist/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/charts/area-chart/area-chart.tsx +7 -3
  11. package/src/charts/area-chart/test/area-chart.test.tsx +15 -14
  12. package/src/charts/area-chart/test/tooltip-resolution.test.tsx +2 -2
  13. package/src/charts/bar-chart/bar-chart.tsx +12 -4
  14. package/src/charts/bar-chart/test/bar-chart.test.tsx +74 -63
  15. package/src/charts/bar-list-chart/bar-list-chart.tsx +3 -0
  16. package/src/charts/bar-list-chart/test/bar-list-chart.test.tsx +3 -1
  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/heatmap-chart.module.scss +10 -4
  24. package/src/charts/heatmap-chart/heatmap-chart.tsx +144 -37
  25. package/src/charts/heatmap-chart/private/column-groups.ts +79 -8
  26. package/src/charts/heatmap-chart/private/fit-compact-cells.ts +72 -0
  27. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +14 -2
  28. package/src/charts/heatmap-chart/private/index.ts +4 -2
  29. package/src/charts/heatmap-chart/test/column-groups.test.ts +41 -4
  30. package/src/charts/heatmap-chart/test/fit-compact-cells.test.ts +59 -0
  31. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +132 -7
  32. package/src/charts/heatmap-chart/types.ts +2 -2
  33. package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
  34. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +52 -4
  35. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +60 -9
  36. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +80 -1
  37. package/src/charts/leaderboard-chart/types.ts +7 -1
  38. package/src/charts/line-chart/line-chart.tsx +8 -2
  39. package/src/charts/line-chart/private/line-chart-annotation-label-popover.tsx +19 -9
  40. package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +1 -1
  41. package/src/charts/line-chart/test/line-chart.test.tsx +37 -35
  42. package/src/charts/line-chart/test/tooltip-resolution.test.tsx +2 -2
  43. package/src/charts/line-chart/types.ts +3 -3
  44. package/src/charts/pie-chart/pie-chart.module.scss +3 -0
  45. package/src/charts/pie-chart/pie-chart.tsx +144 -139
  46. package/src/charts/pie-chart/test/pie-chart.test.tsx +167 -0
  47. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +3 -0
  48. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +117 -120
  49. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +54 -0
  50. package/src/charts/private/chart-composition/use-chart-children.ts +11 -6
  51. package/src/charts/private/pie-keyboard-navigation/index.ts +4 -0
  52. package/src/charts/private/pie-keyboard-navigation/order-arcs-for-navigation.ts +10 -0
  53. package/src/charts/private/pie-keyboard-navigation/pie-keyboard-navigation.module.scss +16 -0
  54. package/src/charts/private/pie-keyboard-navigation/pie-selection-announcement.tsx +14 -0
  55. package/src/charts/private/pie-keyboard-navigation/pie-selection-output.tsx +82 -0
  56. package/src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx +49 -0
  57. package/src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx +47 -0
  58. package/src/charts/private/pie-keyboard-navigation/test/order-arcs-for-navigation.test.ts +34 -0
  59. package/src/charts/private/pie-keyboard-navigation/test/selected-segment-ring.test.ts +14 -0
  60. package/src/charts/private/pie-keyboard-navigation/use-pie-keyboard-navigation.ts +139 -0
  61. package/src/charts/private/x-zoom/test/x-zoom.test.tsx +3 -2
  62. package/src/charts/test/accessible-name.test.tsx +181 -0
  63. package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +63 -3
  64. package/src/components/legend/hooks/use-chart-legend-items.ts +63 -1
  65. package/src/components/legend/private/base-legend.tsx +27 -15
  66. package/src/components/legend/test/legend.test.tsx +42 -0
  67. package/src/components/legend/types.ts +2 -0
  68. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +20 -19
  69. package/src/components/tooltip/xy-chart-tooltip.tsx +3 -3
  70. package/src/hooks/test/use-xychart-theme.test.tsx +22 -0
  71. package/src/hooks/use-xychart-theme.ts +33 -2
  72. package/src/index.ts +1 -0
  73. package/src/types.ts +37 -3
  74. package/src/utils/resolve-font-size.ts +9 -10
  75. package/src/utils/test/resolve-font-size.test.ts +11 -2
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
  ]);
@@ -3082,7 +3127,7 @@ const XYChartTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocu
3082
3127
  /** @deprecated Use `XYChartTooltip`. */
3083
3128
  const AccessibleTooltip = XYChartTooltip;
3084
3129
  const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
3085
- const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
3130
+ const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"application\"]") ?? chartRef.current, [chartRef]);
3086
3131
  const focusWithoutScrollIfNeeded = (0, react.useCallback)((element) => {
3087
3132
  if (preventTooltipScroll) element?.focus({ preventScroll: true });
3088
3133
  else element?.focus();
@@ -3297,11 +3342,13 @@ function useChartChildren(children, chartType) {
3297
3342
  }
3298
3343
  const displayName = child.type?.displayName;
3299
3344
  if (displayName === `${chartType}.SVG` || displayName === "Chart.SVG") {
3300
- if (child.props?.children) react.Children.forEach(child.props.children, (svgChild) => {
3345
+ const { children: svgChildren } = child.props ?? {};
3346
+ if (svgChildren) react.Children.forEach(svgChildren, (svgChild) => {
3301
3347
  svg.push(svgChild);
3302
3348
  });
3303
3349
  } else if (displayName === `${chartType}.HTML` || displayName === "Chart.HTML") {
3304
- if (child.props?.children) react.Children.forEach(child.props.children, (htmlChild) => {
3350
+ const { children: htmlChildren } = child.props ?? {};
3351
+ if (htmlChildren) react.Children.forEach(htmlChildren, (htmlChild) => {
3305
3352
  html.push(htmlChild);
3306
3353
  });
3307
3354
  } else if (child.type === _visx_group.Group) svg.push(child);
@@ -4123,6 +4170,7 @@ var line_chart_module_default = {
4123
4170
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
4124
4171
  "rise": "a8ccharts-inuQka-rise"
4125
4172
  };
4173
+ const isReact18 = react.version.startsWith("18.");
4126
4174
  const CloseIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4127
4175
  width: "16",
4128
4176
  height: "16",
@@ -4165,7 +4213,7 @@ const LineChartAnnotationLabelWithPopover = ({ title, subtitle, renderLabel, ren
4165
4213
  className: line_chart_module_default["line-chart__annotation-label"],
4166
4214
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4167
4215
  ref: buttonRef,
4168
- popovertarget: popoverId,
4216
+ ...isReact18 ? { popovertarget: popoverId } : { popoverTarget: popoverId },
4169
4217
  className: line_chart_module_default["line-chart__annotation-label-trigger-button"],
4170
4218
  style: {
4171
4219
  width: `44px`,
@@ -4193,8 +4241,13 @@ const LineChartAnnotationLabelWithPopover = ({ title, subtitle, renderLabel, ren
4193
4241
  subtitle
4194
4242
  })
4195
4243
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4196
- popovertarget: popoverId,
4197
- popovertargetaction: "hide",
4244
+ ...isReact18 ? {
4245
+ popovertarget: popoverId,
4246
+ popovertargetaction: "hide"
4247
+ } : {
4248
+ popoverTarget: popoverId,
4249
+ popoverTargetAction: "hide"
4250
+ },
4198
4251
  className: line_chart_module_default["line-chart__annotation-label-popover-close-button"],
4199
4252
  "aria-label": (0, _wordpress_i18n.__)("Close", "jetpack-charts"),
4200
4253
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon, {})
@@ -4716,10 +4769,11 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
4716
4769
  ]);
4717
4770
  return null;
4718
4771
  };
4719
- const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, tooltipPlacement, tooltipStyle, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
4772
+ const LineChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, tooltipPlacement, tooltipStyle, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
4720
4773
  const legendInteractive = legend.interactive ?? false;
4721
4774
  const legendCollapseGroups = legend.collapseGroups ?? false;
4722
4775
  const legendComparisonItem = legend.comparisonItem ?? false;
4776
+ const legendComparisonSeriesItems = legend.comparisonSeriesItems;
4723
4777
  const legendShape = legend.shape ?? "line";
4724
4778
  const legendPosition = legend.position ?? "bottom";
4725
4779
  const formatting = useChartFormatting();
@@ -4904,12 +4958,14 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4904
4958
  glyphSize: Math.max(0, toNumber(glyphStyle?.radius) ?? 4),
4905
4959
  collapseGroups: legendCollapseGroups,
4906
4960
  comparisonItem: legendComparisonItem,
4961
+ comparisonSeriesItems: legendComparisonSeriesItems,
4907
4962
  renderGlyph
4908
4963
  }), [
4909
4964
  withLegendGlyph,
4910
4965
  glyphStyle?.radius,
4911
4966
  legendCollapseGroups,
4912
4967
  legendComparisonItem,
4968
+ legendComparisonSeriesItems,
4913
4969
  renderGlyph
4914
4970
  ]), legendShape);
4915
4971
  const chartMetadata = (0, react.useMemo)(() => ({
@@ -4984,8 +5040,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4984
5040
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4985
5041
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4986
5042
  ref: chartRef,
4987
- role: "grid",
4988
- "aria-label": (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
5043
+ role: "application",
5044
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
4989
5045
  tabIndex: 0,
4990
5046
  onKeyDown: onChartKeyDown,
4991
5047
  onFocus: onChartFocus,
@@ -4993,6 +5049,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4993
5049
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4994
5050
  className: xy_plot_module_default["xy-plot"],
4995
5051
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5052
+ accessibilityLabel: "",
4996
5053
  theme,
4997
5054
  width,
4998
5055
  height: chartHeight,
@@ -5233,7 +5290,7 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
5233
5290
  };
5234
5291
  //#endregion
5235
5292
  //#region src/charts/area-chart/area-chart.tsx
5236
- const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
5293
+ const AreaChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
5237
5294
  const legendInteractive = legend.interactive ?? false;
5238
5295
  const legendShape = legend.shape ?? "rect";
5239
5296
  const legendPosition = legend.position ?? "bottom";
@@ -5377,8 +5434,13 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
5377
5434
  withGlyph: false,
5378
5435
  glyphSize: 0,
5379
5436
  collapseGroups: legend.collapseGroups ?? false,
5380
- comparisonItem: legend.comparisonItem ?? false
5381
- }), [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);
5382
5444
  const chartMetadata = (0, react.useMemo)(() => ({
5383
5445
  stacked,
5384
5446
  stackOffset,
@@ -5504,8 +5566,8 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
5504
5566
  const chartHeight = contentHeight > 0 ? contentHeight : height;
5505
5567
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5506
5568
  ref: chartRef,
5507
- role: "grid",
5508
- "aria-label": (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
5569
+ role: "application",
5570
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
5509
5571
  tabIndex: 0,
5510
5572
  onKeyDown: onChartKeyDown,
5511
5573
  onFocus: onChartFocus,
@@ -5513,6 +5575,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
5513
5575
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5514
5576
  className: xy_plot_module_default["xy-plot"],
5515
5577
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5578
+ accessibilityLabel: "",
5516
5579
  theme,
5517
5580
  width,
5518
5581
  height: chartHeight,
@@ -6365,11 +6428,12 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ (0, react_jsx_runtime
6365
6428
  className: bar_chart_module_default["bar-chart__tooltip-row"],
6366
6429
  children: (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("%1$s: %2$s", "jetpack-charts"), label, value)
6367
6430
  });
6368
- const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, tooltipStyle, barClassName, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
6431
+ const BarChartInternal = ({ data, ariaLabel, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, tooltipStyle, barClassName, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
6369
6432
  if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
6370
6433
  const legendInteractive = legend.interactive ?? false;
6371
6434
  const legendCollapseGroups = legend.collapseGroups ?? false;
6372
6435
  const legendComparisonItem = legend.comparisonItem ?? false;
6436
+ const legendComparisonSeriesItems = legend.comparisonSeriesItems;
6373
6437
  const horizontal = orientation === "horizontal";
6374
6438
  const chartId = useChartId(providedChartId);
6375
6439
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -6382,8 +6446,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6382
6446
  });
6383
6447
  const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
6384
6448
  collapseGroups: legendCollapseGroups,
6385
- comparisonItem: legendComparisonItem
6386
- }), [legendCollapseGroups, legendComparisonItem]));
6449
+ comparisonItem: legendComparisonItem,
6450
+ comparisonSeriesItems: legendComparisonSeriesItems
6451
+ }), [
6452
+ legendCollapseGroups,
6453
+ legendComparisonItem,
6454
+ legendComparisonSeriesItems
6455
+ ]));
6387
6456
  const { getElementStyles } = useGlobalChartsContext();
6388
6457
  const isSeriesRendered = (0, react.useCallback)((series) => isSeriesVisible(series.label), [isSeriesVisible]);
6389
6458
  const hasWholeNumberValues = (0, react.useMemo)(() => hasOnlyWholeNumbers(dataSorted.filter(isSeriesRendered)), [dataSorted, isSeriesRendered]);
@@ -6673,9 +6742,9 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6673
6742
  children: ({ contentHeight }) => {
6674
6743
  const chartHeight = contentHeight > 0 ? contentHeight : height;
6675
6744
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6676
- role: "grid",
6745
+ role: "application",
6677
6746
  ref: chartRef,
6678
- "aria-label": (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
6747
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
6679
6748
  tabIndex: 0,
6680
6749
  onKeyDown: onChartKeyDown,
6681
6750
  onFocus: onChartFocus,
@@ -6683,6 +6752,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6683
6752
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6684
6753
  className: xy_plot_module_default["xy-plot"],
6685
6754
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
6755
+ accessibilityLabel: "",
6686
6756
  theme,
6687
6757
  width,
6688
6758
  height: chartHeight,
@@ -6893,7 +6963,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
6893
6963
  right: 20,
6894
6964
  bottom: 0,
6895
6965
  top: 0
6896
- }, className, ...rest }) => {
6966
+ }, className, ariaLabel, ...rest }) => {
6897
6967
  const { getElementStyles } = useGlobalChartsContext();
6898
6968
  const tintedData = (0, react.useMemo)(() => {
6899
6969
  if (data.length > 1) return data;
@@ -6968,6 +7038,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
6968
7038
  xScale: chartOptions.xScale,
6969
7039
  yScale: chartOptions.yScale
6970
7040
  },
7041
+ ariaLabel: ariaLabel ?? (0, _wordpress_i18n.__)("Bar list chart", "jetpack-charts"),
6971
7042
  ...rest
6972
7043
  });
6973
7044
  };
@@ -7043,6 +7114,8 @@ const useFunnelSelection = (hideTooltip) => {
7043
7114
  };
7044
7115
  //#endregion
7045
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;
7046
7119
  /**
7047
7120
  * Internal ConversionFunnelChart component with chart registration
7048
7121
  *
@@ -7054,6 +7127,7 @@ const useFunnelSelection = (hideTooltip) => {
7054
7127
  * @param props.loading - Whether the chart is in loading state
7055
7128
  * @param props.animation - Whether to show chart animation on initial render or not
7056
7129
  * @param props.className - Additional CSS class name
7130
+ * @param props.ariaLabel - Accessible name for the chart group
7057
7131
  * @param props.height - Height of the chart container. Falls back to style.height if set, otherwise defaults to "100%".
7058
7132
  * @param props.style - Custom styling
7059
7133
  * @param props.renderStepLabel - Custom render function for step labels
@@ -7063,7 +7137,7 @@ const useFunnelSelection = (hideTooltip) => {
7063
7137
  * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
7064
7138
  * @return JSX element representing the conversion funnel chart
7065
7139
  */
7066
- 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 }) => {
7067
7141
  const chartId = useChartId(providedChartId);
7068
7142
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
7069
7143
  const chartRef = (0, react.useRef)(null);
@@ -7072,10 +7146,13 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7072
7146
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
7073
7147
  const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
7074
7148
  const standaloneScopeClass = useStandaloneScopeClass();
7149
+ const [measureChartRef, , chartHeight] = useElementSize();
7150
+ const gap = chartHeight > 0 && chartHeight < SHORT_CHART_HEIGHT ? "sm" : "xl";
7075
7151
  const setChartRef = (0, react.useCallback)((node) => {
7076
7152
  chartRef.current = node;
7077
7153
  setScopeNode(node);
7078
- }, []);
7154
+ measureChartRef(node);
7155
+ }, [measureChartRef]);
7079
7156
  const clearSelectionAndRef = (0, react.useCallback)(() => {
7080
7157
  clearSelection();
7081
7158
  selectedBarRef.current = null;
@@ -7223,7 +7300,9 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7223
7300
  const maxRate = Math.max(...steps.map((step) => step.rate));
7224
7301
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7225
7302
  direction: "column",
7226
- gap: "xl",
7303
+ gap,
7304
+ role: "group",
7305
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Conversion funnel chart", "jetpack-charts"),
7227
7306
  ref: setChartRef,
7228
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),
7229
7308
  style: {
@@ -7254,7 +7333,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7254
7333
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7255
7334
  direction: "column",
7256
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"]),
7257
- gap: "xl",
7336
+ gap,
7258
7337
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7259
7338
  direction: "column",
7260
7339
  gap: "xs",
@@ -7448,6 +7527,7 @@ function normalizeGeoChartError(eventArgs) {
7448
7527
  *
7449
7528
  * @param props - The props for the GeoChart component
7450
7529
  * @param props.data - Data in Google Charts format (array of arrays with headers)
7530
+ * @param props.ariaLabel - Accessible name of the chart
7451
7531
  * @param props.width - Width of the chart in pixels
7452
7532
  * @param props.height - Height of the chart in pixels
7453
7533
  * @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
@@ -7459,7 +7539,7 @@ function normalizeGeoChartError(eventArgs) {
7459
7539
  * @param props.renderPlaceholder - Optional render function for the loading placeholder
7460
7540
  * @return A React component displaying an interactive map with data visualization
7461
7541
  */
7462
- 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 }) => {
7463
7543
  const { getElementStyles } = useGlobalChartsContext();
7464
7544
  const scopeElement = useChartScopeElement();
7465
7545
  const containerRef = (0, react.useRef)(null);
@@ -7547,8 +7627,14 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7547
7627
  sanitizedData.hasHtmlTooltips
7548
7628
  ]);
7549
7629
  const chartEvents = (0, react.useMemo)(() => {
7550
- if (!onError) return;
7551
- 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, {
7552
7638
  eventName: "error",
7553
7639
  callback: ({ eventArgs }) => {
7554
7640
  onError(normalizeGeoChartError(eventArgs));
@@ -7557,6 +7643,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7557
7643
  }, [onError]);
7558
7644
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
7559
7645
  ref: containerRef,
7646
+ role: "group",
7647
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Geo chart", "jetpack-charts"),
7560
7648
  className: (0, clsx.default)("geo-chart", geo_chart_module_default.container, className),
7561
7649
  style: {
7562
7650
  width,
@@ -7688,6 +7776,7 @@ var heatmap_chart_module_default = {
7688
7776
  "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
7689
7777
  "heatmap-chart__legend-scale--bar": "a8ccharts-O3YMOW-heatmap-chart__legend-scale--bar",
7690
7778
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
7779
+ "heatmap-chart__legend-swatch--empty": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch--empty",
7691
7780
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
7692
7781
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
7693
7782
  "heatmap-chart--height-capped": "a8ccharts-O3YMOW-heatmap-chart--height-capped"
@@ -7785,8 +7874,9 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7785
7874
  const context = (0, react.useContext)(HeatmapContext);
7786
7875
  const { legend } = useGlobalChartsTheme();
7787
7876
  if (!context) return null;
7788
- const { fillVars } = context;
7877
+ const { extent, fillVars } = context;
7789
7878
  const labelStyle = legend.labelStyles;
7879
+ const showsEmptyCell = isEmptyValue(0, extent);
7790
7880
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7791
7881
  direction: "row",
7792
7882
  gap: "xs",
@@ -7798,12 +7888,12 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7798
7888
  style: labelStyle,
7799
7889
  children: lessLabel ?? (0, _wordpress_i18n.__)("Less", "jetpack-charts")
7800
7890
  }),
7801
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
7891
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7802
7892
  direction: "row",
7803
7893
  gap: variant === "bar" ? void 0 : "xs",
7804
7894
  "aria-hidden": "true",
7805
7895
  className: variant === "bar" ? heatmap_chart_module_default["heatmap-chart__legend-scale--bar"] : void 0,
7806
- children: Array.from({ length: steps }, (_, index) => {
7896
+ children: [showsEmptyCell && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__legend-swatch"], heatmap_chart_module_default["heatmap-chart__legend-swatch--empty"]) }), Array.from({ length: steps }, (_, index) => {
7807
7897
  const intensity = steps <= 1 ? 1 : index / (steps - 1);
7808
7898
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7809
7899
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
@@ -7812,7 +7902,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7812
7902
  "--a8c-charts-heatmap-cell-intensity": intensity
7813
7903
  }
7814
7904
  }, index);
7815
- })
7905
+ })]
7816
7906
  }),
7817
7907
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
7818
7908
  variant: "body-sm",
@@ -7838,9 +7928,11 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7838
7928
  const ungrouped = () => ({
7839
7929
  columns: Array.from({ length: columnCount }, (_, index) => ({
7840
7930
  line: firstLine + index,
7931
+ band: 0,
7841
7932
  gapBefore: false
7842
7933
  })),
7843
- groups: []
7934
+ groups: [],
7935
+ bands: 1
7844
7936
  });
7845
7937
  if (!groups || groups.length === 0 || columnCount === 0) return ungrouped();
7846
7938
  const spans = groups.map((group) => group.span);
@@ -7856,11 +7948,13 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7856
7948
  if (groupIndex > 0) line += 1;
7857
7949
  laidOut.push({
7858
7950
  ...group,
7859
- line
7951
+ line,
7952
+ band: 0
7860
7953
  });
7861
7954
  for (let offset = 0; offset < group.span; offset++) {
7862
7955
  columns.push({
7863
7956
  line,
7957
+ band: 0,
7864
7958
  gapBefore: groupIndex > 0 && offset === 0,
7865
7959
  group: groupIndex
7866
7960
  });
@@ -7870,13 +7964,99 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7870
7964
  while (columns.length < columnCount) {
7871
7965
  columns.push({
7872
7966
  line,
7967
+ band: 0,
7873
7968
  gapBefore: false
7874
7969
  });
7875
7970
  line += 1;
7876
7971
  }
7877
7972
  return {
7878
7973
  columns,
7879
- groups: laidOut
7974
+ groups: laidOut,
7975
+ bands: 1
7976
+ };
7977
+ };
7978
+ /**
7979
+ * Wraps the groups onto bands of equal group count, each band starting back at
7980
+ * the first data line so the groups line up in slots down the bands.
7981
+ *
7982
+ * @param layout - The unwrapped layout from `resolveColumnGroups`.
7983
+ * @param bands - Bands to aim for; the groups split as evenly as whole bands allow.
7984
+ * @return The wrapped layout, or null when there is nothing to wrap: one band, one group, or a column outside every group.
7985
+ */
7986
+ const wrapColumnGroups = (layout, bands) => {
7987
+ const { columns, groups } = layout;
7988
+ if (bands < 2 || groups.length < 2 || columns.some((column) => column.group === void 0)) return null;
7989
+ const perBand = Math.ceil(groups.length / bands);
7990
+ const slotSpans = Array(perBand).fill(0);
7991
+ groups.forEach((group, index) => {
7992
+ const slot = index % perBand;
7993
+ slotSpans[slot] = Math.max(slotSpans[slot], group.span);
7994
+ });
7995
+ const slotLines = [];
7996
+ let line = columns[0].line;
7997
+ slotSpans.forEach((span, slot) => {
7998
+ if (slot > 0) line += 1;
7999
+ slotLines.push(line);
8000
+ line += span;
8001
+ });
8002
+ const wrappedGroups = groups.map((group, index) => ({
8003
+ ...group,
8004
+ line: slotLines[index % perBand],
8005
+ band: Math.floor(index / perBand)
8006
+ }));
8007
+ return {
8008
+ columns: columns.map((column) => {
8009
+ const group = column.group;
8010
+ const offset = column.line - groups[group].line;
8011
+ return {
8012
+ ...column,
8013
+ line: wrappedGroups[group].line + offset,
8014
+ band: wrappedGroups[group].band,
8015
+ gapBefore: offset === 0 && group % perBand > 0
8016
+ };
8017
+ }),
8018
+ groups: wrappedGroups,
8019
+ bands: Math.ceil(groups.length / perBand),
8020
+ slotSpans
8021
+ };
8022
+ };
8023
+ //#endregion
8024
+ //#region src/charts/heatmap-chart/private/fit-compact-cells.ts
8025
+ /**
8026
+ * Largest whole-pixel square cell, and the band count giving it, for a compact grid in a box.
8027
+ *
8028
+ * @param input - The box, the grid's layout, and its fixed gaps and label sizes.
8029
+ * @return The fit; one band at `minCellSize` when no band count reaches it.
8030
+ */
8031
+ const fitCompactCells = (input) => {
8032
+ const { width, height, layout, rows, cellGap, groupGap, minCellSize } = input;
8033
+ const columns = layout.columns.length;
8034
+ if (columns === 0 || rows === 0) return {
8035
+ bands: 1,
8036
+ cellSize: minCellSize
8037
+ };
8038
+ const maxBands = Math.max(1, layout.groups.length);
8039
+ const labelHeights = (input.columnLabelHeight ?? 0) + (input.groupLabelHeight ?? 0);
8040
+ const bandTracks = (input.columnLabelHeight === null ? 0 : 1) + rows + (input.groupLabelHeight === null ? 0 : 1);
8041
+ let best = null;
8042
+ for (let target = 1; target <= maxBands; target++) {
8043
+ const wrapped = target > 1 ? wrapColumnGroups(layout, target) : null;
8044
+ const { bands } = wrapped ?? layout;
8045
+ const cellColumns = wrapped ? wrapped.slotSpans.reduce((sum, span) => sum + span, 0) : columns;
8046
+ const gapTracks = wrapped ? wrapped.slotSpans.length - 1 : layout.columns.filter((column) => column.gapBefore).length;
8047
+ const columnTracks = 1 + cellColumns + gapTracks;
8048
+ const byWidth = (width - input.rowLabelWidth - gapTracks * groupGap - (columnTracks - 1) * cellGap) / cellColumns;
8049
+ const rowTracks = bands * bandTracks + (bands - 1);
8050
+ const byHeight = (height - (bands * labelHeights + (bands - 1) * groupGap + (rowTracks - 1) * cellGap)) / (bands * rows);
8051
+ const cellSize = Math.floor(Math.min(byWidth, byHeight));
8052
+ if (cellSize >= minCellSize && (!best || cellSize > best.cellSize)) best = {
8053
+ bands,
8054
+ cellSize
8055
+ };
8056
+ }
8057
+ return best ?? {
8058
+ bands: 1,
8059
+ cellSize: minCellSize
7880
8060
  };
7881
8061
  };
7882
8062
  //#endregion
@@ -8019,18 +8199,32 @@ const CELL_VALUE_MODIFIER = {
8019
8199
  white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
8020
8200
  };
8021
8201
  const NO_ROW_LABELS = [];
8022
- const TOOLTIP_BOX_STYLE = { zIndex: 3 };
8202
+ const TOOLTIP_BOX_STYLE$1 = { zIndex: 3 };
8203
+ const largest = (root, selector, size) => Math.max(0, ...Array.from(root.querySelectorAll(selector), (el) => el[size]));
8204
+ const gridBox = (chart, grid) => {
8205
+ let height = chart.clientHeight;
8206
+ const rowGap = parseFloat(getComputedStyle(chart).rowGap) || 0;
8207
+ Array.from(chart.children).forEach((child) => {
8208
+ const { display, position } = getComputedStyle(child);
8209
+ if (child === grid || display === "none" || position === "absolute" || position === "fixed") return;
8210
+ height -= child.offsetHeight + rowGap;
8211
+ });
8212
+ return {
8213
+ width: chart.clientWidth,
8214
+ height
8215
+ };
8216
+ };
8023
8217
  const cellName = (info) => info.cellLabel || [
8024
8218
  info.groupLabel,
8025
8219
  info.columnLabel,
8026
8220
  info.rowLabel
8027
8221
  ].filter(Boolean).join(" ");
8028
- const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipStyle, children }) => {
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 }) => {
8029
8223
  const chartId = useChartId(providedChartId);
8030
8224
  const tooltipBoxStyle = tooltipStyle ? {
8031
- ...TOOLTIP_BOX_STYLE,
8225
+ ...TOOLTIP_BOX_STYLE$1,
8032
8226
  ...tooltipStyle
8033
- } : TOOLTIP_BOX_STYLE;
8227
+ } : TOOLTIP_BOX_STYLE$1;
8034
8228
  const { getElementStyles, theme } = useGlobalChartsContext();
8035
8229
  const scopeElement = useChartScopeElement();
8036
8230
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -8039,8 +8233,9 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8039
8233
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
8040
8234
  const standaloneScopeClass = useStandaloneScopeClass();
8041
8235
  const containerRef = (0, react.useRef)(null);
8236
+ const chartRef = (0, react.useRef)(null);
8042
8237
  const [labelRoles, setLabelRoles] = (0, react.useState)(null);
8043
- const getTooltipOrigin = (0, react.useCallback)(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
8238
+ const getTooltipOrigin = (0, react.useCallback)(() => chartRef.current?.getBoundingClientRect() ?? null, []);
8044
8239
  const { color: primaryColorHex } = getElementStyles({
8045
8240
  index: 0,
8046
8241
  overrideColor: primaryColor
@@ -8082,6 +8277,49 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8082
8277
  const groupLayout = (0, react.useMemo)(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
8083
8278
  const { compactCellGap, compactCellSize, groupGap } = heatmapChartSettings;
8084
8279
  const drawValues = showValues ?? !compact;
8280
+ const hasColumnLabels = data.some((column) => Boolean(column.label));
8281
+ const hasGroups = groupLayout.groups.length > 0;
8282
+ const [fit, setFit] = (0, react.useState)(null);
8283
+ const hasSummary = data.some((column) => column.summary);
8284
+ const fitting = compact && fitCells && !hasSummary;
8285
+ if (fitCells && !fitting) warnOnce("heatmap:fitCells", "fitCells applies only in compact mode without summary columns, so the cells keep their size.");
8286
+ useIsomorphicLayoutEffect(() => {
8287
+ const chart = chartRef.current;
8288
+ const grid = containerRef.current;
8289
+ if (!fitting || !chart || !grid) {
8290
+ setFit(null);
8291
+ return;
8292
+ }
8293
+ const measure = () => {
8294
+ const next = fitCompactCells({
8295
+ ...gridBox(chart, grid),
8296
+ layout: groupLayout,
8297
+ rows,
8298
+ cellGap: compactCellGap,
8299
+ groupGap,
8300
+ rowLabelWidth: largest(grid, `.${heatmap_chart_module_default["heatmap-chart__row-label"]}`, "offsetWidth"),
8301
+ columnLabelHeight: hasColumnLabels ? largest(grid, `.${heatmap_chart_module_default["heatmap-chart__col-label"]}`, "offsetHeight") : null,
8302
+ groupLabelHeight: hasGroups ? largest(grid, `.${heatmap_chart_module_default["heatmap-chart__group-label"]}`, "offsetHeight") : null,
8303
+ minCellSize: compactCellSize
8304
+ });
8305
+ setFit((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
8306
+ };
8307
+ measure();
8308
+ if (typeof ResizeObserver === "undefined") return;
8309
+ const observer = new ResizeObserver(() => (0, react_dom.flushSync)(measure));
8310
+ observer.observe(chart);
8311
+ return () => observer.disconnect();
8312
+ }, [
8313
+ fitting,
8314
+ groupLayout,
8315
+ rows,
8316
+ hasColumnLabels,
8317
+ hasGroups,
8318
+ compactCellGap,
8319
+ compactCellSize,
8320
+ groupGap
8321
+ ]);
8322
+ const wrapped = (0, react.useMemo)(() => fit ? wrapColumnGroups(groupLayout, fit.bands) : null, [fit, groupLayout]);
8085
8323
  const buildTooltipData = (0, react.useCallback)((columnIndex, rowIndex) => {
8086
8324
  const cell = data[columnIndex]?.data[rowIndex];
8087
8325
  const group = groupLayout.columns[columnIndex]?.group;
@@ -8221,21 +8459,25 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8221
8459
  });
8222
8460
  const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
8223
8461
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
8224
- const hasColumnLabels = data.some((column) => Boolean(column.label));
8225
- const hasGroups = groupLayout.groups.length > 0;
8226
- const columnLine = (columnIndex) => groupLayout.columns[columnIndex].line;
8227
- const firstDataRow = hasColumnLabels ? 2 : 1;
8462
+ const placed = wrapped ?? groupLayout;
8463
+ const columnLine = (columnIndex) => placed.columns[columnIndex].line;
8464
+ const bandOf = (columnIndex) => placed.columns[columnIndex].band;
8465
+ const { bands } = placed;
8466
+ const bandRowCount = (hasColumnLabels ? 1 : 0) + rows + (hasGroups ? 1 : 0) + 1;
8467
+ const labelRowOf = (band) => 1 + band * bandRowCount;
8468
+ const dataRowOf = (band, rowIndex) => labelRowOf(band) + (hasColumnLabels ? 1 : 0) + rowIndex;
8228
8469
  const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
8229
8470
  const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
8230
- const columnTracks = data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ");
8471
+ const columnTracks = wrapped ? wrapped.slotSpans.map((span, slot) => [...slot > 0 ? [gapTrack] : [], ...Array(span).fill(columnTrack)].join(" ")).join(" ") : data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ");
8472
+ const bandTracks = `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`;
8231
8473
  const gridStyle = {
8232
8474
  ...fillVars,
8233
8475
  gridTemplateColumns: `auto ${columnTracks}`,
8234
- gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
8476
+ gridTemplateRows: Array(bands).fill(bandTracks).join(` ${groupGap}px `)
8235
8477
  };
8236
8478
  if (compact) {
8237
8479
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
8238
- gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
8480
+ gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${fit?.cellSize ?? compactCellSize}px`;
8239
8481
  }
8240
8482
  const summaryGaps = (columnIndex) => {
8241
8483
  if (!data[columnIndex]?.summary) return {};
@@ -8255,6 +8497,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8255
8497
  legendChildren: [],
8256
8498
  trailingContent: nonLegendChildren,
8257
8499
  gap,
8500
+ rootRef: chartRef,
8258
8501
  className: (0, clsx.default)("heatmap-chart", heatmap_chart_module_default["heatmap-chart"], className, { [heatmap_chart_module_default["heatmap-chart--height-capped"]]: heightCapped }),
8259
8502
  style: {
8260
8503
  width: width || void 0,
@@ -8284,11 +8527,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8284
8527
  className: heatmap_chart_module_default["heatmap-chart__row"],
8285
8528
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
8286
8529
  gridColumn: 1,
8287
- gridRow: 1
8530
+ gridRow: labelRowOf(0)
8288
8531
  } }), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8289
8532
  style: {
8290
8533
  gridColumn: columnLine(columnIndex),
8291
- gridRow: 1
8534
+ gridRow: labelRowOf(bandOf(columnIndex))
8292
8535
  },
8293
8536
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
8294
8537
  [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
@@ -8299,24 +8542,23 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8299
8542
  }),
8300
8543
  Array.from({ length: rows }).map((_row, rowIndex) => {
8301
8544
  const labelVisible = !compact || rowIndex % 2 === 0;
8302
- const gridRow = firstDataRow + rowIndex;
8303
8545
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8304
8546
  role: "row",
8305
8547
  "aria-rowindex": rowIndex + 1,
8306
8548
  className: heatmap_chart_module_default["heatmap-chart__row"],
8307
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8549
+ children: [Array.from({ length: bands }).map((_band, band) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8308
8550
  "aria-hidden": "true",
8309
8551
  className: heatmap_chart_module_default["heatmap-chart__row-label"],
8310
8552
  style: {
8311
8553
  gridColumn: 1,
8312
- gridRow
8554
+ gridRow: dataRowOf(band, rowIndex)
8313
8555
  },
8314
8556
  children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
8315
- }), data.map((column, columnIndex) => {
8557
+ }, `row-label-${band}`)), data.map((column, columnIndex) => {
8316
8558
  const cell = column.data[rowIndex];
8317
8559
  const placement = {
8318
8560
  gridColumn: columnLine(columnIndex),
8319
- gridRow
8561
+ gridRow: dataRowOf(bandOf(columnIndex), rowIndex)
8320
8562
  };
8321
8563
  if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8322
8564
  "aria-hidden": "true",
@@ -8366,11 +8608,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8366
8608
  role: "row",
8367
8609
  "aria-hidden": "true",
8368
8610
  className: heatmap_chart_module_default["heatmap-chart__row"],
8369
- children: groupLayout.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8611
+ children: placed.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8370
8612
  className: heatmap_chart_module_default["heatmap-chart__group-label"],
8371
8613
  style: {
8372
8614
  gridColumn: `${group.line} / span ${group.span}`,
8373
- gridRow: firstDataRow + rows
8615
+ gridRow: dataRowOf(group.band, rows)
8374
8616
  },
8375
8617
  children: group.label
8376
8618
  }, `group-${groupIndex}`))
@@ -11472,8 +11714,8 @@ const SUBPIXEL_TOLERANCE = .5;
11472
11714
  * depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
11473
11715
  * whatever a caller renders as a label, so any restated number would drift.
11474
11716
  *
11475
- * Every row is a direct grid child marked with `data-row-index`; interactive rows
11476
- * 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.
11477
11719
  *
11478
11720
  * @param enabled - Whether to measure at all. When false every row fits.
11479
11721
  * @param rowCount - Total number of rows rendered.
@@ -11605,6 +11847,7 @@ var leaderboard_chart_module_default = {
11605
11847
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
11606
11848
  "is-overlay": "a8ccharts-GovfoW-is-overlay",
11607
11849
  "label": "a8ccharts-GovfoW-label",
11850
+ "layeredRow": "a8ccharts-GovfoW-layeredRow",
11608
11851
  "leaderboardChart": "a8ccharts-GovfoW-leaderboardChart",
11609
11852
  "leaderboardChart__content": "a8ccharts-GovfoW-leaderboardChart__content",
11610
11853
  "leaderboardChart__content--fit": "a8ccharts-GovfoW-leaderboardChart__content--fit",
@@ -11613,6 +11856,8 @@ var leaderboard_chart_module_default = {
11613
11856
  "overlap": "a8ccharts-GovfoW-overlap",
11614
11857
  "overlayLabel": "a8ccharts-GovfoW-overlayLabel",
11615
11858
  "row": "a8ccharts-GovfoW-row",
11859
+ "rowButton": "a8ccharts-GovfoW-rowButton",
11860
+ "rowItem": "a8ccharts-GovfoW-rowItem",
11616
11861
  "stretch": "a8ccharts-GovfoW-stretch",
11617
11862
  "valueContainer": "a8ccharts-GovfoW-valueContainer"
11618
11863
  };
@@ -11663,6 +11908,16 @@ const defaultDeltaFormatter = (value) => {
11663
11908
  const getBarWidth = (share) => `calc(${share}% - var(--a8c-charts-dimension-leaderboard-bar-hover-inset, 0px) * ${share} / 100)`;
11664
11909
  const hasPreviousValue = (entry) => entry.previousValue != null && entry.previousShare != null;
11665
11910
  const hasDeltaValue = (entry) => hasPreviousValue(entry) && entry.delta != null;
11911
+ /**
11912
+ * A layered row's cell that takes pointer events of its own (a value tooltip)
11913
+ * sits above the row button; pass its clicks on so they still drill down.
11914
+ *
11915
+ * @param event - The click on the row.
11916
+ */
11917
+ function forwardCellClickToRowButton(event) {
11918
+ if (event.target.closest("a, button")) return;
11919
+ event.currentTarget.querySelector(":scope > button")?.click();
11920
+ }
11666
11921
  const BarLabel = ({ label }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: typeof label === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
11667
11922
  className: leaderboard_chart_module_default.label,
11668
11923
  children: label
@@ -11715,9 +11970,10 @@ const BarWithLabel = ({ entry, withComparison, withOverlayLabel, primaryColor, s
11715
11970
  * @param props.children - Child components for composition API
11716
11971
  * @param props.className - Additional CSS class name
11717
11972
  * @param props.style - Custom styling for the chart container
11973
+ * @param props.ariaLabel - Accessible name for the list, defaulting to "Leaderboard chart"
11718
11974
  * @return JSX element representing the leaderboard chart
11719
11975
  */
11720
- 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 }) => {
11721
11977
  const legendInteractive = legend.interactive ?? false;
11722
11978
  const legendPosition = legend.position ?? "bottom";
11723
11979
  const chartId = useChartId(providedChartId);
@@ -11848,6 +12104,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
11848
12104
  className: leaderboard_chart_module_default.emptyState,
11849
12105
  children: getAllHiddenMessage(legendInteractive, "series")
11850
12106
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default, {
12107
+ role: "list",
12108
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Leaderboard chart", "jetpack-charts"),
11851
12109
  templateColumns: "minmax(0, 1fr) auto",
11852
12110
  rowGap: ROW_GAP,
11853
12111
  columnGap: COLUMN_GAP,
@@ -11895,20 +12153,49 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
11895
12153
  })
11896
12154
  ]
11897
12155
  })] });
11898
- if (entry.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11899
- type: "button",
12156
+ if (entry.onClick && entry.hasInteractiveLabel) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12157
+ role: "listitem",
11900
12158
  "data-row-index": rowIndex,
11901
12159
  style: rowStyle,
11902
- className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow),
11903
- onClick: entry.onClick,
11904
- "aria-label": entry.ariaLabel,
11905
- children: [rowCells, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
11906
- className: leaderboard_chart_module_default.chevron,
11907
- icon: _wordpress_icons.chevronRight,
11908
- size: 24
11909
- })]
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
+ })
12179
+ }, entry.id);
12180
+ if (entry.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12181
+ role: "listitem",
12182
+ "data-row-index": rowIndex,
12183
+ style: rowStyle,
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
+ })
11910
12196
  }, entry.id);
11911
12197
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12198
+ role: "listitem",
11912
12199
  "data-row-index": rowIndex,
11913
12200
  style: rowStyle,
11914
12201
  className: leaderboard_chart_module_default.row,
@@ -11928,6 +12215,212 @@ LeaderboardChartWithProvider.displayName = "LeaderboardChart";
11928
12215
  const LeaderboardChart = attachSubComponents(LeaderboardChartWithProvider, { Legend });
11929
12216
  const LeaderboardChartResponsive = attachSubComponents(withResponsive(LeaderboardChartWithProvider), { Legend });
11930
12217
  //#endregion
12218
+ //#region src/charts/private/pie-keyboard-navigation/order-arcs-for-navigation.ts
12219
+ /**
12220
+ * Orders arcs the way they appear: clockwise from 12 o'clock, which for a semi-circle is left to right.
12221
+ *
12222
+ * @param arcs - Arcs from visx's `pie` factory, in data order.
12223
+ * @return A copy of `arcs` sorted by angular midpoint.
12224
+ */
12225
+ const orderArcsForNavigation = (arcs) => [...arcs].sort((a, b) => a.startAngle + a.endAngle - (b.startAngle + b.endAngle));
12226
+ //#endregion
12227
+ //#region src/charts/private/pie-keyboard-navigation/pie-selection-announcement.tsx
12228
+ const PieSelectionAnnouncement = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.VisuallyHidden, {
12229
+ role: "status",
12230
+ "aria-atomic": "true",
12231
+ children: data && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelValueContent, { data })
12232
+ });
12233
+ //#endregion
12234
+ //#region src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx
12235
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
12236
+ const PieTooltip = ({ top, left, selectedIndex, tooltipRef, children }) => {
12237
+ const standaloneScopeClass = useStandaloneScopeClass();
12238
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12239
+ top,
12240
+ left,
12241
+ style: TOOLTIP_BOX_STYLE,
12242
+ children: selectedIndex === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12243
+ className: standaloneScopeClass,
12244
+ role: "tooltip",
12245
+ children
12246
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12247
+ ref: tooltipRef,
12248
+ tabIndex: -1,
12249
+ role: "tooltip",
12250
+ "aria-atomic": "true",
12251
+ className: standaloneScopeClass,
12252
+ children
12253
+ }, `chart-tooltip-${selectedIndex}`)
12254
+ });
12255
+ };
12256
+ //#endregion
12257
+ //#region src/charts/private/pie-keyboard-navigation/pie-selection-output.tsx
12258
+ /**
12259
+ * Reports the selected segment: a focused tooltip for a keyboard selection, a pointer tooltip on hover, or a screen reader announcement without tooltips.
12260
+ *
12261
+ * @param {PieSelectionOutputProps} props - Component props
12262
+ * @return {JSX.Element} The announcement region and the tooltip, if any
12263
+ */
12264
+ const PieSelectionOutput = ({ withTooltips, selectedIndex, selectedData, keyboardTooltipAnchor, pointerTooltip, chartRef, svgRef, tooltipRef, renderTooltip }) => {
12265
+ const [svgOffset, setSvgOffset] = (0, react.useState)({
12266
+ top: 0,
12267
+ left: 0
12268
+ });
12269
+ const showKeyboardTooltip = withTooltips && selectedData && keyboardTooltipAnchor;
12270
+ (0, react.useLayoutEffect)(() => {
12271
+ const chartBounds = chartRef.current?.getBoundingClientRect();
12272
+ const svgBounds = svgRef.current?.getBoundingClientRect();
12273
+ if (!showKeyboardTooltip || !chartBounds || !svgBounds) return;
12274
+ const top = svgBounds.top - chartBounds.top;
12275
+ const left = svgBounds.left - chartBounds.left;
12276
+ setSvgOffset((current) => current.top === top && current.left === left ? current : {
12277
+ top,
12278
+ left
12279
+ });
12280
+ });
12281
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
12282
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieSelectionAnnouncement, { data: withTooltips ? void 0 : selectedData }),
12283
+ showKeyboardTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieTooltip, {
12284
+ top: svgOffset.top + keyboardTooltipAnchor.top,
12285
+ left: svgOffset.left + keyboardTooltipAnchor.left,
12286
+ selectedIndex,
12287
+ tooltipRef,
12288
+ children: renderTooltip({ tooltipData: selectedData })
12289
+ }),
12290
+ withTooltips && !selectedData && pointerTooltip?.data && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieTooltip, {
12291
+ top: pointerTooltip.top,
12292
+ left: pointerTooltip.left,
12293
+ children: renderTooltip({ tooltipData: pointerTooltip.data })
12294
+ })
12295
+ ] });
12296
+ };
12297
+ //#endregion
12298
+ //#region src/charts/private/pie-keyboard-navigation/pie-keyboard-navigation.module.scss
12299
+ var pie_keyboard_navigation_module_default = {
12300
+ "selected-ring__contrast": "a8ccharts-V2HDKq-selected-ring__contrast",
12301
+ "selected-ring__focus": "a8ccharts-V2HDKq-selected-ring__focus"
12302
+ };
12303
+ //#endregion
12304
+ //#region src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx
12305
+ /**
12306
+ * Encodes `value` for an SVG id, losslessly so distinct chart ids never share a clip path.
12307
+ *
12308
+ * @param value - Any string.
12309
+ * @return `value` with each character outside `[A-Za-z0-9-]` written as `_<hex code point>_`.
12310
+ */
12311
+ const toSvgId = (value) => Array.from(value, (character) => /[A-Za-z0-9-]/.test(character) ? character : `_${character.codePointAt(0)?.toString(16)}_`).join("");
12312
+ /**
12313
+ * Marks the selected segment with a two-tone ring drawn inside its edge, so the SVG edge cannot cut it off.
12314
+ *
12315
+ * @param {SelectedSegmentRingProps} props - Component props
12316
+ * @return {JSX.Element} The ring
12317
+ */
12318
+ const SelectedSegmentRing = ({ chartId, d }) => {
12319
+ const clipId = `pie-selected-ring-clip-${toSvgId(chartId)}`;
12320
+ const clipPath = `url(#${clipId})`;
12321
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
12322
+ pointerEvents: "none",
12323
+ "aria-hidden": "true",
12324
+ children: [
12325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("clipPath", {
12326
+ id: clipId,
12327
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
12328
+ }),
12329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12330
+ d,
12331
+ clipPath,
12332
+ className: pie_keyboard_navigation_module_default["selected-ring__contrast"]
12333
+ }),
12334
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12335
+ d,
12336
+ clipPath,
12337
+ className: pie_keyboard_navigation_module_default["selected-ring__focus"]
12338
+ })
12339
+ ]
12340
+ });
12341
+ };
12342
+ //#endregion
12343
+ //#region src/charts/private/pie-keyboard-navigation/use-pie-keyboard-navigation.ts
12344
+ const usePieKeyboardNavigation = ({ navigationArcs, withTooltips, tooltipOffsetX, tooltipOffsetY }) => {
12345
+ const chartRef = (0, react.useRef)(null);
12346
+ const svgRef = (0, react.useRef)(null);
12347
+ const [selectedIndex, setSelectedIndex] = (0, react.useState)(void 0);
12348
+ const [isNavigating, setIsNavigating] = (0, react.useState)(false);
12349
+ const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
12350
+ const { tooltipRef: focusTooltip, onChartFocus, onChartBlur, onChartKeyDown, onChartPointerMove } = useKeyboardNavigation({
12351
+ selectedIndex,
12352
+ setSelectedIndex,
12353
+ isNavigating,
12354
+ setIsNavigating,
12355
+ chartRef,
12356
+ totalPoints: navigationArcs.length,
12357
+ visibleSeriesKey: JSON.stringify(navigationArcs.map((arcDatum) => arcDatum.data.label))
12358
+ });
12359
+ const selectedArc = selectedIndex === void 0 ? void 0 : navigationArcs[selectedIndex];
12360
+ const keyboardTooltip = (0, react.useRef)(null);
12361
+ const tooltipLostFocus = (0, react.useRef)(false);
12362
+ const tooltipRef = (0, react.useCallback)((element) => {
12363
+ if (element) {
12364
+ keyboardTooltip.current = element;
12365
+ focusTooltip(element);
12366
+ } else if (keyboardTooltip.current?.ownerDocument.activeElement === keyboardTooltip.current) tooltipLostFocus.current = true;
12367
+ }, [focusTooltip]);
12368
+ (0, react.useLayoutEffect)(() => {
12369
+ if (!tooltipLostFocus.current) return;
12370
+ tooltipLostFocus.current = false;
12371
+ const { activeElement, body } = chartRef.current?.ownerDocument ?? document;
12372
+ if (activeElement === null || activeElement === body) chartRef.current?.focus();
12373
+ });
12374
+ const getSegmentHandlers = (data, navigationIndex) => ({
12375
+ onMouseMove: (event) => {
12376
+ onChartPointerMove(navigationIndex);
12377
+ if (!withTooltips) return;
12378
+ const bounds = chartRef.current?.getBoundingClientRect();
12379
+ if (!bounds) return;
12380
+ showTooltip({
12381
+ tooltipData: data,
12382
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12383
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12384
+ });
12385
+ },
12386
+ onMouseLeave: withTooltips ? hideTooltip : void 0
12387
+ });
12388
+ return {
12389
+ chartRef,
12390
+ svgRef,
12391
+ selectedArc,
12392
+ getSegmentHandlers,
12393
+ getKeyboardTooltipAnchor: (0, react.useCallback)((originX, originY, [x, y]) => withTooltips ? {
12394
+ left: originX + x + tooltipOffsetX,
12395
+ top: originY + y + tooltipOffsetY
12396
+ } : void 0, [
12397
+ withTooltips,
12398
+ tooltipOffsetX,
12399
+ tooltipOffsetY
12400
+ ]),
12401
+ selectionOutputProps: {
12402
+ withTooltips,
12403
+ selectedIndex,
12404
+ selectedData: selectedArc?.data,
12405
+ chartRef,
12406
+ svgRef,
12407
+ tooltipRef,
12408
+ pointerTooltip: tooltipOpen ? {
12409
+ data: tooltipData,
12410
+ left: tooltipLeft || 0,
12411
+ top: tooltipTop || 0
12412
+ } : void 0
12413
+ },
12414
+ chartProps: {
12415
+ role: "application",
12416
+ tabIndex: 0,
12417
+ onKeyDown: onChartKeyDown,
12418
+ onFocus: onChartFocus,
12419
+ onBlur: onChartBlur
12420
+ }
12421
+ };
12422
+ };
12423
+ //#endregion
11931
12424
  //#region src/charts/private/radial-wipe-animation/radial-wipe-animation.tsx
11932
12425
  /**
11933
12426
  * Renders a SVG mask that creates a radial wipe animation effect.
@@ -11968,6 +12461,7 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
11968
12461
  //#endregion
11969
12462
  //#region src/charts/pie-chart/pie-chart.module.scss
11970
12463
  var pie_chart_module_default = {
12464
+ "pie-chart": "a8ccharts-gnszbG-pie-chart",
11971
12465
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
11972
12466
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
11973
12467
  "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",
@@ -12023,20 +12517,13 @@ const LABEL_TEXT_MODIFIER = {
12023
12517
  * @param {PieChartProps} props - Component props
12024
12518
  * @return {JSX.Element} The rendered chart component
12025
12519
  */
12026
- const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false, className, showLegend = false, legend = {}, width: propWidth, height: propHeight, size, animation, thickness = 1, padding = 0, gapScale = 0, cornerScale = 0, showLabels = true, legendValueDisplay = "percentage", children = null, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieTooltip, gap = "md" }) => {
12520
+ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false, className, showLegend = false, legend = {}, width: propWidth, height: propHeight, size, animation, thickness = 1, padding = 0, gapScale = 0, cornerScale = 0, showLabels = true, legendValueDisplay = "percentage", children = null, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieTooltip, ariaLabel, gap = "md" }) => {
12027
12521
  const legendInteractive = legend.interactive ?? false;
12028
12522
  const legendPosition = legend.position ?? "bottom";
12029
12523
  const providerTheme = useGlobalChartsTheme();
12030
12524
  const chartId = useChartId(providedChartId);
12031
- const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
12032
- const standaloneScopeClass = useStandaloneScopeClass();
12033
- const containerRef = (0, react.useRef)(null);
12034
12525
  const rootRef = (0, react.useRef)(null);
12035
12526
  const [labelRoles, setLabelRoles] = (0, react.useState)(null);
12036
- const onMouseLeave = (0, react.useCallback)(() => {
12037
- if (!withTooltips) return;
12038
- hideTooltip();
12039
- }, [withTooltips, hideTooltip]);
12040
12527
  const { getElementStyles, isSeriesVisible, isColorPaletteResolved } = useGlobalChartsContext();
12041
12528
  const { isValid, message } = validateData$1(data);
12042
12529
  (0, react.useLayoutEffect)(() => {
@@ -12080,13 +12567,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
12080
12567
  metadata: chartMetadata
12081
12568
  });
12082
12569
  const prefersReducedMotion = usePrefersReducedMotion();
12083
- if (!isValid) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12084
- className: (0, clsx.default)("pie-chart", pie_chart_module_default["pie-chart"], className),
12085
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12086
- className: pie_chart_module_default["error-message"],
12087
- children: message
12088
- })
12089
- });
12570
+ const wipeMask = animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null;
12090
12571
  const padAngle = gapScale * (2 * Math.PI / data.length);
12091
12572
  const dataWithIndex = visibleData.map((d) => {
12092
12573
  const originalIndex = data.findIndex((item) => item.label === d.label);
@@ -12104,6 +12585,24 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
12104
12585
  }).color;
12105
12586
  }
12106
12587
  };
12588
+ const arcs = (0, _visx_shape.pie)({
12589
+ value: accessors.value,
12590
+ padAngle
12591
+ })(dataWithIndex);
12592
+ const navigationArcs = orderArcsForNavigation(arcs);
12593
+ const { chartRef, svgRef, selectedArc, getSegmentHandlers, getKeyboardTooltipAnchor, selectionOutputProps, chartProps } = usePieKeyboardNavigation({
12594
+ navigationArcs,
12595
+ withTooltips,
12596
+ tooltipOffsetX,
12597
+ tooltipOffsetY
12598
+ });
12599
+ if (!isValid) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12600
+ className: (0, clsx.default)("pie-chart", pie_chart_module_default["pie-chart"], className),
12601
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12602
+ className: pie_chart_module_default["error-message"],
12603
+ children: message
12604
+ })
12605
+ });
12107
12606
  const legendElement = showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Legend, {
12108
12607
  orientation: legend.orientation ?? "horizontal",
12109
12608
  position: legendPosition,
@@ -12142,102 +12641,96 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
12142
12641
  const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
12143
12642
  const maxCornerRadius = (outerRadius - innerRadius) / 2;
12144
12643
  const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
12644
+ const path = (0, _visx_shape.arc)({
12645
+ innerRadius,
12646
+ outerRadius,
12647
+ cornerRadius
12648
+ });
12649
+ const svgLabelSmall = providerTheme.svgLabelSmall;
12650
+ const fontSize = resolveFontSize(svgLabelSmall?.fontSize) ?? 12;
12651
+ const renderLabel = (arcDatum, index) => {
12652
+ if (arcDatum.endAngle - arcDatum.startAngle < .25) return null;
12653
+ const [centroidX, centroidY] = path.centroid(arcDatum);
12654
+ const backgroundWidth = (0, _visx_text.getStringWidth)(arcDatum.data.label, {
12655
+ fontSize,
12656
+ fontFamily: svgLabelSmall?.fontFamily,
12657
+ fontWeight: svgLabelSmall?.fontWeight
12658
+ }) + 12;
12659
+ const backgroundHeight = fontSize + 12;
12660
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
12661
+ className: pie_chart_module_default["pie-chart__label-plate"],
12662
+ x: centroidX - backgroundWidth / 2,
12663
+ y: centroidY - backgroundHeight / 2,
12664
+ width: backgroundWidth,
12665
+ height: backgroundHeight,
12666
+ rx: 4,
12667
+ ry: 4,
12668
+ pointerEvents: "none"
12669
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
12670
+ className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(accessors.fill(arcDatum.data), labelRoles, "label-inverse")]),
12671
+ x: centroidX,
12672
+ y: centroidY,
12673
+ dy: ".33em",
12674
+ fontSize,
12675
+ textAnchor: "middle",
12676
+ pointerEvents: "none",
12677
+ children: arcDatum.data.label
12678
+ })] }, `label-${index}`);
12679
+ };
12145
12680
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
12146
- ref: containerRef,
12681
+ ref: chartRef,
12147
12682
  className: pie_chart_module_default["pie-chart__plot"],
12683
+ ...chartProps,
12684
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Pie chart", "jetpack-charts"),
12148
12685
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
12686
+ ref: svgRef,
12149
12687
  viewBox: `0 0 ${width} ${height}`,
12150
12688
  preserveAspectRatio: "xMidYMid meet",
12151
12689
  width,
12152
12690
  height,
12153
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12154
- id: `radial-wipe-${chartId}`,
12155
- radius: outerRadius,
12156
- innerRadius
12157
- }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, {
12158
- top: centerY,
12159
- left: centerX,
12160
- mask: animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null,
12161
- children: [allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12162
- x: 0,
12163
- y: 0,
12164
- width,
12165
- height,
12166
- children: getAllHiddenMessage(legendInteractive, "segments")
12167
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_shape.Pie, {
12168
- data: dataWithIndex,
12169
- pieValue: accessors.value,
12170
- outerRadius,
12171
- innerRadius,
12172
- padAngle,
12173
- cornerRadius,
12174
- children: (pie) => {
12175
- return pie.arcs.map((arc, index) => {
12176
- const [centroidX, centroidY] = pie.path.centroid(arc);
12177
- const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
12178
- const handleMouseMove = (event) => {
12179
- if (!withTooltips) return;
12180
- const bounds = containerRef.current?.getBoundingClientRect();
12181
- if (!bounds) return;
12182
- showTooltip({
12183
- tooltipData: arc.data,
12184
- tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12185
- tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12186
- });
12187
- };
12188
- const fill = accessors.fill(arc.data);
12189
- const pathProps = {
12190
- d: pie.path(arc) || "",
12191
- fill,
12192
- "data-testid": "pie-segment"
12193
- };
12194
- const groupProps = {};
12195
- if (withTooltips) {
12196
- groupProps.onMouseMove = handleMouseMove;
12197
- groupProps.onMouseLeave = onMouseLeave;
12198
- }
12199
- const svgLabelSmall = providerTheme.svgLabelSmall;
12200
- const fontSize = resolveFontSize(svgLabelSmall?.fontSize) ?? 12;
12201
- const backgroundWidth = (0, _visx_text.getStringWidth)(arc.data.label, {
12202
- fontSize,
12203
- fontFamily: svgLabelSmall?.fontFamily,
12204
- fontWeight: svgLabelSmall?.fontWeight
12205
- }) + 12;
12206
- const backgroundHeight = fontSize + 12;
12207
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
12208
- ...groupProps,
12209
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
12210
- className: pie_chart_module_default["pie-chart__label-plate"],
12211
- x: centroidX - backgroundWidth / 2,
12212
- y: centroidY - backgroundHeight / 2,
12213
- width: backgroundWidth,
12214
- height: backgroundHeight,
12215
- rx: 4,
12216
- ry: 4,
12217
- pointerEvents: "none"
12218
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
12219
- className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
12220
- x: centroidX,
12221
- y: centroidY,
12222
- dy: ".33em",
12223
- fontSize,
12224
- textAnchor: "middle",
12225
- pointerEvents: "none",
12226
- children: arc.data.label
12227
- })] })]
12228
- }, `arc-${index}`);
12229
- });
12230
- }
12231
- }), !allSegmentsHidden && svgChildren]
12232
- })]
12233
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12234
- top: tooltipTop || 0,
12235
- left: tooltipLeft || 0,
12236
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12237
- className: standaloneScopeClass,
12238
- role: "tooltip",
12239
- children: renderTooltip({ tooltipData })
12240
- })
12691
+ children: [
12692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12693
+ id: `radial-wipe-${chartId}`,
12694
+ radius: outerRadius,
12695
+ innerRadius
12696
+ }) }),
12697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12698
+ top: centerY,
12699
+ left: centerX,
12700
+ mask: wipeMask,
12701
+ children: allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12702
+ x: 0,
12703
+ y: 0,
12704
+ width,
12705
+ height,
12706
+ children: getAllHiddenMessage(legendInteractive, "segments")
12707
+ }) : arcs.map((arcDatum, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
12708
+ ...getSegmentHandlers(arcDatum.data, navigationArcs.indexOf(arcDatum)),
12709
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12710
+ d: path(arcDatum) || "",
12711
+ fill: accessors.fill(arcDatum.data)
12712
+ })
12713
+ }, `arc-${index}`))
12714
+ }),
12715
+ selectedArc && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12716
+ top: centerY,
12717
+ left: centerX,
12718
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedSegmentRing, {
12719
+ chartId,
12720
+ d: path(selectedArc) || ""
12721
+ })
12722
+ }),
12723
+ !allSegmentsHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, {
12724
+ top: centerY,
12725
+ left: centerX,
12726
+ mask: wipeMask,
12727
+ children: [showLabels && arcs.map(renderLabel), svgChildren]
12728
+ })
12729
+ ]
12730
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieSelectionOutput, {
12731
+ ...selectionOutputProps,
12732
+ keyboardTooltipAnchor: selectedArc && getKeyboardTooltipAnchor(centerX, centerY, path.centroid(selectedArc)),
12733
+ renderTooltip
12241
12734
  })]
12242
12735
  });
12243
12736
  }
@@ -12305,32 +12798,10 @@ const validateData = (data) => {
12305
12798
  message: ""
12306
12799
  };
12307
12800
  };
12308
- const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, thickness = .4, clockwise = true, withTooltips = false, showLegend = false, legend = {}, legendValueDisplay = "percentage", label, animation, note, className, children, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieSemiCircleTooltip, gap = "md" }) => {
12801
+ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, thickness = .4, clockwise = true, withTooltips = false, showLegend = false, legend = {}, legendValueDisplay = "percentage", label, animation, note, className, children, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieSemiCircleTooltip, ariaLabel, gap = "md" }) => {
12309
12802
  const legendInteractive = legend.interactive ?? false;
12310
12803
  const legendPosition = legend.position ?? "bottom";
12311
12804
  const chartId = useChartId(providedChartId);
12312
- const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
12313
- const standaloneScopeClass = useStandaloneScopeClass();
12314
- const containerRef = (0, react.useRef)(null);
12315
- const handleMouseMove = (0, react.useCallback)((event, arc) => {
12316
- const bounds = containerRef.current?.getBoundingClientRect();
12317
- if (!bounds) return;
12318
- showTooltip({
12319
- tooltipData: arc.data,
12320
- tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12321
- tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12322
- });
12323
- }, [
12324
- showTooltip,
12325
- tooltipOffsetX,
12326
- tooltipOffsetY
12327
- ]);
12328
- const handleMouseLeave = (0, react.useCallback)(() => {
12329
- hideTooltip();
12330
- }, [hideTooltip]);
12331
- const handleArcMouseMove = (0, react.useCallback)((arc) => (event) => {
12332
- handleMouseMove(event, arc);
12333
- }, [handleMouseMove]);
12334
12805
  const { isValid, message } = validateData(data);
12335
12806
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
12336
12807
  const dataWithPercentages = useDataWithPercentages(data);
@@ -12364,7 +12835,31 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12364
12835
  metadata: chartMetadata
12365
12836
  });
12366
12837
  const prefersReducedMotion = usePrefersReducedMotion();
12838
+ const wipeMask = animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null;
12367
12839
  const effectiveWidth = propWidth || DEFAULT_WIDTH$1;
12840
+ const dataWithIndex = visibleData.map((d) => {
12841
+ const originalIndex = data.findIndex((item) => item.label === d.label);
12842
+ return {
12843
+ ...d,
12844
+ index: originalIndex >= 0 ? originalIndex : 0
12845
+ };
12846
+ });
12847
+ const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
12848
+ const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
12849
+ const arcs = (0, _visx_shape.pie)({
12850
+ value: accessors.value,
12851
+ sort: accessors.sort,
12852
+ padAngle: PAD_ANGLE,
12853
+ startAngle,
12854
+ endAngle
12855
+ })(dataWithIndex);
12856
+ const navigationArcs = orderArcsForNavigation(arcs);
12857
+ const { chartRef, svgRef, selectedArc, getSegmentHandlers, getKeyboardTooltipAnchor, selectionOutputProps, chartProps } = usePieKeyboardNavigation({
12858
+ navigationArcs,
12859
+ withTooltips,
12860
+ tooltipOffsetX,
12861
+ tooltipOffsetY
12862
+ });
12368
12863
  if (!isValid) {
12369
12864
  const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
12370
12865
  const errorHeight = errorWidth / 2;
@@ -12383,15 +12878,6 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12383
12878
  })
12384
12879
  });
12385
12880
  }
12386
- const dataWithIndex = visibleData.map((d) => {
12387
- const originalIndex = data.findIndex((item) => item.label === d.label);
12388
- return {
12389
- ...d,
12390
- index: originalIndex >= 0 ? originalIndex : 0
12391
- };
12392
- });
12393
- const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
12394
- const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
12395
12881
  const legendElement = showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Legend, {
12396
12882
  orientation: legend.orientation ?? "horizontal",
12397
12883
  position: legendPosition,
@@ -12424,52 +12910,60 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12424
12910
  const height = width / 2;
12425
12911
  const radius = height;
12426
12912
  const innerRadius = radius * (1 - thickness);
12913
+ const path = (0, _visx_shape.arc)({
12914
+ innerRadius,
12915
+ outerRadius: radius,
12916
+ cornerRadius: 3
12917
+ });
12427
12918
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
12428
- ref: containerRef,
12919
+ ref: chartRef,
12429
12920
  className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
12921
+ ...chartProps,
12922
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Semi-circle chart", "jetpack-charts"),
12430
12923
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
12924
+ ref: svgRef,
12431
12925
  width,
12432
12926
  height,
12433
12927
  viewBox: `0 0 ${width} ${height}`,
12434
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12435
- id: `radial-wipe-${chartId}`,
12436
- radius,
12437
- innerRadius,
12438
- startAngle: "-180deg",
12439
- wipePercentage: 50
12440
- }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12441
- top: height,
12442
- left: width / 2,
12443
- mask: animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null,
12444
- children: allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12445
- x: 0,
12446
- y: -radius / 2,
12447
- width,
12448
- height,
12449
- children: getAllHiddenMessage(legendInteractive, "segments")
12450
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
12451
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_shape.Pie, {
12452
- data: dataWithIndex,
12453
- pieValue: accessors.value,
12454
- outerRadius: radius,
12455
- innerRadius,
12456
- cornerRadius: 3,
12457
- padAngle: PAD_ANGLE,
12458
- startAngle,
12459
- endAngle,
12460
- pieSort: accessors.sort,
12461
- children: (pie) => {
12462
- return pie.arcs.map((arc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
12463
- onMouseMove: withTooltips ? handleArcMouseMove(arc) : void 0,
12464
- onMouseLeave: withTooltips ? handleMouseLeave : void 0,
12465
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12466
- d: pie.path(arc) || "",
12467
- fill: accessors.fill(arc.data)
12468
- })
12469
- }, arc.data.label));
12470
- }
12471
- }),
12472
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_text.Text, {
12928
+ children: [
12929
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12930
+ id: `radial-wipe-${chartId}`,
12931
+ radius,
12932
+ innerRadius,
12933
+ startAngle: "-180deg",
12934
+ wipePercentage: 50
12935
+ }) }),
12936
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12937
+ top: height,
12938
+ left: width / 2,
12939
+ mask: wipeMask,
12940
+ children: allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12941
+ x: 0,
12942
+ y: -radius / 2,
12943
+ width,
12944
+ height,
12945
+ children: getAllHiddenMessage(legendInteractive, "segments")
12946
+ }) : arcs.map((arcDatum) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
12947
+ ...getSegmentHandlers(arcDatum.data, navigationArcs.indexOf(arcDatum)),
12948
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12949
+ d: path(arcDatum) || "",
12950
+ fill: accessors.fill(arcDatum.data)
12951
+ })
12952
+ }, arcDatum.data.label))
12953
+ }),
12954
+ selectedArc && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12955
+ top: height,
12956
+ left: width / 2,
12957
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedSegmentRing, {
12958
+ chartId,
12959
+ d: path(selectedArc) || ""
12960
+ })
12961
+ }),
12962
+ !allSegmentsHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, {
12963
+ top: height,
12964
+ left: width / 2,
12965
+ mask: wipeMask,
12966
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_text.Text, {
12473
12967
  textAnchor: "middle",
12474
12968
  verticalAnchor: "start",
12475
12969
  y: -40,
@@ -12481,18 +12975,13 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12481
12975
  y: -20,
12482
12976
  className: pie_semi_circle_chart_module_default.note,
12483
12977
  children: note
12484
- })] }),
12485
- !allSegmentsHidden && svgChildren
12486
- ] })
12487
- })]
12488
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12489
- top: tooltipTop || 0,
12490
- left: tooltipLeft || 0,
12491
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12492
- className: standaloneScopeClass,
12493
- role: "tooltip",
12494
- children: renderTooltip({ tooltipData })
12495
- })
12978
+ })] }), svgChildren]
12979
+ })
12980
+ ]
12981
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieSelectionOutput, {
12982
+ ...selectionOutputProps,
12983
+ keyboardTooltipAnchor: selectedArc && getKeyboardTooltipAnchor(width / 2, height, path.centroid(selectedArc)),
12984
+ renderTooltip
12496
12985
  })]
12497
12986
  });
12498
12987
  }