@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.js CHANGED
@@ -4,7 +4,7 @@ import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarS
4
4
  import { __, _x, sprintf } from "@wordpress/i18n";
5
5
  import clsx from "clsx";
6
6
  import * as React from "react";
7
- import { Children, Fragment, createContext, createContext as createContext$1, createElement, forwardRef, forwardRef as forwardRef$1, isValidElement, memo, useCallback, useCallback as useCallback$1, useContext, useContext as useContext$1, useEffect, useEffect as useEffect$1, useId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useRef, useRef as useRef$1, useState, useState as useState$1 } from "react";
7
+ import { Children, Fragment, createContext, createContext as createContext$1, createElement, forwardRef, forwardRef as forwardRef$1, isValidElement, memo, useCallback, useCallback as useCallback$1, useContext, useContext as useContext$1, useEffect, useEffect as useEffect$1, useId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useRef, useRef as useRef$1, useState, useState as useState$1, version } from "react";
8
8
  import { differenceInHours, differenceInYears, isValid, parse, parseISO } from "date-fns";
9
9
  import { tzOffset } from "@date-fns/tz";
10
10
  import { Text, getStringWidth } from "@visx/text";
@@ -15,9 +15,9 @@ import { useTooltip } from "@visx/tooltip";
15
15
  import { createScale, getTicks, scaleBand, scaleCanBeZeroed, scaleOrdinal, scaleTime } from "@visx/scale";
16
16
  import { Group } from "@visx/group";
17
17
  import { LegendItem, LegendLabel, LegendOrdinal, LegendShape } from "@visx/legend";
18
- import { IconButton, Stack, Text as Text$1 } from "@wordpress/ui";
18
+ import { IconButton, Stack, Text as Text$1, VisuallyHidden } from "@wordpress/ui";
19
19
  import { ThemeProvider } from "@wordpress/theme";
20
- import { createPortal } from "react-dom";
20
+ import { createPortal, flushSync } from "react-dom";
21
21
  import { LinearGradient } from "@visx/gradient";
22
22
  import { curveCatmullRom, curveLinear, curveMonotoneX } from "@visx/curve";
23
23
  import { useParentSize } from "@visx/responsive";
@@ -30,7 +30,7 @@ import { __assign } from "tslib";
30
30
  import "@babel/runtime/helpers/esm/extends";
31
31
  import "@babel/runtime/helpers/extends";
32
32
  import { Icon, chevronRight } from "@wordpress/icons";
33
- import { Pie } from "@visx/shape";
33
+ import { arc, pie } from "@visx/shape";
34
34
  //#region \0rolldown/runtime.js
35
35
  var __create = Object.create;
36
36
  var __defProp = Object.defineProperty;
@@ -669,16 +669,17 @@ function readCustomProperty(varName, styles) {
669
669
  }
670
670
  //#endregion
671
671
  //#region src/utils/resolve-font-size.ts
672
+ const toFontSize = (size) => Number.isFinite(size) && size >= 0 ? size : void 0;
672
673
  /**
673
674
  * Resolve a theme `fontSize` value into a plain number suitable for
674
675
  * canvas-based measurement (e.g. `getStringWidth`).
675
676
  *
676
677
  * Accepts:
677
- * - A number — returned as-is
678
+ * - A finite, non-negative number — returned as-is
678
679
  * - A pixel string like `"12px"` — parsed and returned as a number
679
680
  *
680
- * Returns `undefined` for any other input (missing value, NaN, or
681
- * relative units like `rem`/`em`/`%`/`vh`) so callers can fall back to
681
+ * Returns `undefined` for any other input (missing value, a non-finite or
682
+ * negative size, or relative units like `rem`/`em`/`%`/`vh`) so callers can fall back to
682
683
  * their own default. Relative units are intentionally rejected because
683
684
  * we cannot resolve them to absolute pixels here without a parent
684
685
  * computed style, and silently returning the unitless prefix
@@ -688,12 +689,10 @@ function readCustomProperty(varName, styles) {
688
689
  * @return Parsed numeric font size in pixels, or `undefined` when unresolvable
689
690
  */
690
691
  const resolveFontSize = (val) => {
691
- if (typeof val === "number") return isNaN(val) ? void 0 : val;
692
+ if (typeof val === "number") return toFontSize(val);
692
693
  if (typeof val === "string") {
693
- const match = val.trim().match(/^(-?\d+\.?\d*|-?\.\d+)(px)?$/);
694
- if (!match) return;
695
- const parsed = parseFloat(match[1]);
696
- return isNaN(parsed) ? void 0 : parsed;
694
+ const match = val.trim().match(/^\+?((?:\d*\.)?\d+)(px)?$/);
695
+ return match ? toFontSize(parseFloat(match[1])) : void 0;
697
696
  }
698
697
  };
699
698
  //#endregion
@@ -1572,6 +1571,20 @@ const useChartScopeElement = () => useContext(ChartScopeContext);
1572
1571
  const useStandaloneScopeClass = () => useContext(GlobalChartsContext) ? void 0 : CHART_SCOPE_CLASS;
1573
1572
  //#endregion
1574
1573
  //#region src/hooks/use-xychart-theme.ts
1574
+ const STARTS_LIKE_A_NUMBER = /^[+-]?[\d.]/;
1575
+ const NON_NEGATIVE_CSS_NUMBER = /^\+?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:[a-z]+|%)?$/i;
1576
+ const isRenderableFontSize = (fontSize) => {
1577
+ if (typeof fontSize === "number") return resolveFontSize(fontSize) !== void 0;
1578
+ const value = fontSize.trim();
1579
+ if (!STARTS_LIKE_A_NUMBER.test(value)) return true;
1580
+ return NON_NEGATIVE_CSS_NUMBER.test(value) && Number.isFinite(parseFloat(value));
1581
+ };
1582
+ const withRenderableFontSize = (style) => {
1583
+ if (style?.fontSize === void 0 || isRenderableFontSize(style.fontSize)) return style;
1584
+ const rest = { ...style };
1585
+ delete rest.fontSize;
1586
+ return rest;
1587
+ };
1575
1588
  const useXYChartTheme = (data) => {
1576
1589
  const theme = useGlobalChartsTheme();
1577
1590
  const scopeElement = useChartScopeElement();
@@ -1602,9 +1615,10 @@ const useXYChartTheme = (data) => {
1602
1615
  yAxisLineStyles: { stroke: CATALOG_POINTERS.axisY },
1603
1616
  yTickLineStyles: { stroke: CATALOG_POINTERS.tickY },
1604
1617
  svgLabelSmall: {
1605
- ...theme.svgLabelSmall,
1618
+ ...withRenderableFontSize(theme.svgLabelSmall),
1606
1619
  fill: CATALOG_POINTERS.labelAxis
1607
- }
1620
+ },
1621
+ svgLabelBig: withRenderableFontSize(theme.svgLabelBig)
1608
1622
  });
1609
1623
  }, [
1610
1624
  theme,
@@ -2117,7 +2131,7 @@ const getLegendItemAriaLabel = (text, value, visible, interactive) => {
2117
2131
  if (visible) return;
2118
2132
  return sprintf(_x("%s: hidden", "hidden non-interactive legend item", "jetpack-charts"), accessibleText);
2119
2133
  };
2120
- const BaseLegend = 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) => {
2134
+ const BaseLegend = 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) => {
2121
2135
  const { margin: itemMargin = "0", flexDirection: itemDirection = "row" } = itemStyles ?? {};
2122
2136
  const { justifyContent: labelJustifyContent = "flex-start", flex: labelFlex = "0 0 auto", margin: labelMargin = "0 4px", maxWidth, textOverflow = "wrap" } = labelStyles ?? {};
2123
2137
  const { width: shapeWidth = 16, height: shapeHeight = 16, margin: shapeMargin = "2px 4px 2px 0" } = shapeStyles ?? {};
@@ -2125,6 +2139,16 @@ const BaseLegend = forwardRef(({ items, className, orientation = "horizontal", a
2125
2139
  const context = useContext(GlobalChartsContext);
2126
2140
  const chartInstanceContext = useContext(ChartInstanceContext);
2127
2141
  const standaloneScopeClass = useStandaloneScopeClass();
2142
+ const isSeriesVisible = useCallback((seriesLabel) => {
2143
+ if (chartInstanceContext?.isSeriesVisible) return chartInstanceContext.isSeriesVisible(seriesLabel);
2144
+ if (!chartId || !context) return true;
2145
+ return context.isSeriesVisible(chartId, seriesLabel);
2146
+ }, [
2147
+ chartId,
2148
+ chartInstanceContext,
2149
+ context
2150
+ ]);
2151
+ const items = useMemo(() => allItems.filter((item) => item.interactive !== false || !item.describesSeries || isSeriesVisible(item.describesSeries)), [allItems, isSeriesVisible]);
2128
2152
  const domain = items.map((item) => item.label);
2129
2153
  const legendScale = scaleOrdinal({
2130
2154
  domain,
@@ -2147,15 +2171,6 @@ const BaseLegend = forwardRef(({ items, className, orientation = "horizontal", a
2147
2171
  chartId,
2148
2172
  context
2149
2173
  ]);
2150
- const isSeriesVisible = useCallback((seriesLabel) => {
2151
- if (chartInstanceContext?.isSeriesVisible) return chartInstanceContext.isSeriesVisible(seriesLabel);
2152
- if (!chartId || !context) return true;
2153
- return context.isSeriesVisible(chartId, seriesLabel);
2154
- }, [
2155
- chartId,
2156
- chartInstanceContext,
2157
- context
2158
- ]);
2159
2174
  const createClickHandler = useCallback((seriesLabels, itemInteractive) => {
2160
2175
  if (!itemInteractive) return;
2161
2176
  return () => handleLegendClick(seriesLabels);
@@ -2430,6 +2445,34 @@ function buildComparisonLegendItem(seriesData, items, label, getElementStyles, l
2430
2445
  };
2431
2446
  }
2432
2447
  /**
2448
+ * Builds one static item per comparison series folded into another series' item, named after
2449
+ * that item and styled like the comparison series itself.
2450
+ * @param seriesData - The series data to search for comparison series
2451
+ * @param items - The legend items already built from the series
2452
+ * @param formatLabel - Names an item after the label of the item it was folded into
2453
+ * @param getElementStyles - Function to get element styles
2454
+ * @param legendShape - The shape type for legend items (string literal or React component)
2455
+ * @return The legend items, in series order
2456
+ */
2457
+ function buildPerSeriesComparisonItems(seriesData, items, formatLabel, getElementStyles, legendShape) {
2458
+ return seriesData.flatMap((series, index) => {
2459
+ const owner = items.find((item) => item.seriesLabels?.includes(series.label));
2460
+ if (series.options?.type !== "comparison" || !owner || owner.seriesLabels?.[0] === series.label) return [];
2461
+ const { color, shapeStyles } = getElementStyles({
2462
+ data: series,
2463
+ index,
2464
+ legendShape
2465
+ });
2466
+ return [{
2467
+ label: formatLabel(owner.label),
2468
+ color,
2469
+ shapeStyle: shapeStyles,
2470
+ interactive: false,
2471
+ describesSeries: series.label
2472
+ }];
2473
+ });
2474
+ }
2475
+ /**
2433
2476
  * Processes point data into legend items
2434
2477
  * @param pointData - The point data to process
2435
2478
  * @param getElementStyles - Function to get element styles
@@ -2465,13 +2508,14 @@ function processPointData(pointData, getElementStyles, showValues, legendValueDi
2465
2508
  * @return Array of legend items ready for display
2466
2509
  */
2467
2510
  function useChartLegendItems(data, options = {}, legendShape) {
2468
- const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, comparisonItem = false, renderGlyph } = options;
2511
+ const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, comparisonItem = false, comparisonSeriesItems, renderGlyph } = options;
2469
2512
  const { getElementStyles } = useGlobalChartsContext();
2470
2513
  return useMemo(() => {
2471
2514
  if (!data || !Array.isArray(data) || data.length === 0) return [];
2472
2515
  if ("data" in data[0]) {
2473
2516
  const seriesData = data;
2474
2517
  const items = processSeriesData(seriesData, getElementStyles, showValues, withGlyph, glyphSize, collapseGroups, renderGlyph, legendShape);
2518
+ if (comparisonSeriesItems) return [...items, ...buildPerSeriesComparisonItems(seriesData, items, comparisonSeriesItems, getElementStyles, legendShape)];
2475
2519
  const comparison = comparisonItem ? buildComparisonLegendItem(seriesData, items, typeof comparisonItem === "string" ? comparisonItem : __("Comparison period", "jetpack-charts"), getElementStyles, legendShape) : null;
2476
2520
  return comparison ? [...items, comparison] : items;
2477
2521
  }
@@ -2485,6 +2529,7 @@ function useChartLegendItems(data, options = {}, legendShape) {
2485
2529
  glyphSize,
2486
2530
  collapseGroups,
2487
2531
  comparisonItem,
2532
+ comparisonSeriesItems,
2488
2533
  renderGlyph,
2489
2534
  legendShape
2490
2535
  ]);
@@ -3080,7 +3125,7 @@ const XYChartTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocu
3080
3125
  /** @deprecated Use `XYChartTooltip`. */
3081
3126
  const AccessibleTooltip = XYChartTooltip;
3082
3127
  const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
3083
- const getChartRoot = useCallback(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
3128
+ const getChartRoot = useCallback(() => chartRef.current?.closest("[role=\"application\"]") ?? chartRef.current, [chartRef]);
3084
3129
  const focusWithoutScrollIfNeeded = useCallback((element) => {
3085
3130
  if (preventTooltipScroll) element?.focus({ preventScroll: true });
3086
3131
  else element?.focus();
@@ -3295,11 +3340,13 @@ function useChartChildren(children, chartType) {
3295
3340
  }
3296
3341
  const displayName = child.type?.displayName;
3297
3342
  if (displayName === `${chartType}.SVG` || displayName === "Chart.SVG") {
3298
- if (child.props?.children) Children.forEach(child.props.children, (svgChild) => {
3343
+ const { children: svgChildren } = child.props ?? {};
3344
+ if (svgChildren) Children.forEach(svgChildren, (svgChild) => {
3299
3345
  svg.push(svgChild);
3300
3346
  });
3301
3347
  } else if (displayName === `${chartType}.HTML` || displayName === "Chart.HTML") {
3302
- if (child.props?.children) Children.forEach(child.props.children, (htmlChild) => {
3348
+ const { children: htmlChildren } = child.props ?? {};
3349
+ if (htmlChildren) Children.forEach(htmlChildren, (htmlChild) => {
3303
3350
  html.push(htmlChild);
3304
3351
  });
3305
3352
  } else if (child.type === Group) svg.push(child);
@@ -4121,6 +4168,7 @@ var line_chart_module_default = {
4121
4168
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
4122
4169
  "rise": "a8ccharts-inuQka-rise"
4123
4170
  };
4171
+ const isReact18 = version.startsWith("18.");
4124
4172
  const CloseIcon = () => /* @__PURE__ */ jsx("svg", {
4125
4173
  width: "16",
4126
4174
  height: "16",
@@ -4163,7 +4211,7 @@ const LineChartAnnotationLabelWithPopover = ({ title, subtitle, renderLabel, ren
4163
4211
  className: line_chart_module_default["line-chart__annotation-label"],
4164
4212
  children: [/* @__PURE__ */ jsx("button", {
4165
4213
  ref: buttonRef,
4166
- popovertarget: popoverId,
4214
+ ...isReact18 ? { popovertarget: popoverId } : { popoverTarget: popoverId },
4167
4215
  className: line_chart_module_default["line-chart__annotation-label-trigger-button"],
4168
4216
  style: {
4169
4217
  width: `44px`,
@@ -4191,8 +4239,13 @@ const LineChartAnnotationLabelWithPopover = ({ title, subtitle, renderLabel, ren
4191
4239
  subtitle
4192
4240
  })
4193
4241
  }), /* @__PURE__ */ jsx("button", {
4194
- popovertarget: popoverId,
4195
- popovertargetaction: "hide",
4242
+ ...isReact18 ? {
4243
+ popovertarget: popoverId,
4244
+ popovertargetaction: "hide"
4245
+ } : {
4246
+ popoverTarget: popoverId,
4247
+ popoverTargetAction: "hide"
4248
+ },
4196
4249
  className: line_chart_module_default["line-chart__annotation-label-popover-close-button"],
4197
4250
  "aria-label": __("Close", "jetpack-charts"),
4198
4251
  children: /* @__PURE__ */ jsx(CloseIcon, {})
@@ -4714,10 +4767,11 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
4714
4767
  ]);
4715
4768
  return null;
4716
4769
  };
4717
- const LineChartInternal = 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) => {
4770
+ const LineChartInternal = 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) => {
4718
4771
  const legendInteractive = legend.interactive ?? false;
4719
4772
  const legendCollapseGroups = legend.collapseGroups ?? false;
4720
4773
  const legendComparisonItem = legend.comparisonItem ?? false;
4774
+ const legendComparisonSeriesItems = legend.comparisonSeriesItems;
4721
4775
  const legendShape = legend.shape ?? "line";
4722
4776
  const legendPosition = legend.position ?? "bottom";
4723
4777
  const formatting = useChartFormatting();
@@ -4902,12 +4956,14 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4902
4956
  glyphSize: Math.max(0, toNumber(glyphStyle?.radius) ?? 4),
4903
4957
  collapseGroups: legendCollapseGroups,
4904
4958
  comparisonItem: legendComparisonItem,
4959
+ comparisonSeriesItems: legendComparisonSeriesItems,
4905
4960
  renderGlyph
4906
4961
  }), [
4907
4962
  withLegendGlyph,
4908
4963
  glyphStyle?.radius,
4909
4964
  legendCollapseGroups,
4910
4965
  legendComparisonItem,
4966
+ legendComparisonSeriesItems,
4911
4967
  renderGlyph
4912
4968
  ]), legendShape);
4913
4969
  const chartMetadata = useMemo(() => ({
@@ -4982,8 +5038,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4982
5038
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4983
5039
  return /* @__PURE__ */ jsx("div", {
4984
5040
  ref: chartRef,
4985
- role: "grid",
4986
- "aria-label": __("Line chart", "jetpack-charts"),
5041
+ role: "application",
5042
+ "aria-label": ariaLabel ?? __("Line chart", "jetpack-charts"),
4987
5043
  tabIndex: 0,
4988
5044
  onKeyDown: onChartKeyDown,
4989
5045
  onFocus: onChartFocus,
@@ -4991,6 +5047,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4991
5047
  children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
4992
5048
  className: xy_plot_module_default["xy-plot"],
4993
5049
  children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
5050
+ accessibilityLabel: "",
4994
5051
  theme,
4995
5052
  width,
4996
5053
  height: chartHeight,
@@ -5231,7 +5288,7 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
5231
5288
  };
5232
5289
  //#endregion
5233
5290
  //#region src/charts/area-chart/area-chart.tsx
5234
- const AreaChartInternal = 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) => {
5291
+ const AreaChartInternal = 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) => {
5235
5292
  const legendInteractive = legend.interactive ?? false;
5236
5293
  const legendShape = legend.shape ?? "rect";
5237
5294
  const legendPosition = legend.position ?? "bottom";
@@ -5375,8 +5432,13 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
5375
5432
  withGlyph: false,
5376
5433
  glyphSize: 0,
5377
5434
  collapseGroups: legend.collapseGroups ?? false,
5378
- comparisonItem: legend.comparisonItem ?? false
5379
- }), [legend.collapseGroups, legend.comparisonItem]), legendShape);
5435
+ comparisonItem: legend.comparisonItem ?? false,
5436
+ comparisonSeriesItems: legend.comparisonSeriesItems
5437
+ }), [
5438
+ legend.collapseGroups,
5439
+ legend.comparisonItem,
5440
+ legend.comparisonSeriesItems
5441
+ ]), legendShape);
5380
5442
  const chartMetadata = useMemo(() => ({
5381
5443
  stacked,
5382
5444
  stackOffset,
@@ -5502,8 +5564,8 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
5502
5564
  const chartHeight = contentHeight > 0 ? contentHeight : height;
5503
5565
  return /* @__PURE__ */ jsx("div", {
5504
5566
  ref: chartRef,
5505
- role: "grid",
5506
- "aria-label": __("Area chart", "jetpack-charts"),
5567
+ role: "application",
5568
+ "aria-label": ariaLabel ?? __("Area chart", "jetpack-charts"),
5507
5569
  tabIndex: 0,
5508
5570
  onKeyDown: onChartKeyDown,
5509
5571
  onFocus: onChartFocus,
@@ -5511,6 +5573,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
5511
5573
  children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
5512
5574
  className: xy_plot_module_default["xy-plot"],
5513
5575
  children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
5576
+ accessibilityLabel: "",
5514
5577
  theme,
5515
5578
  width,
5516
5579
  height: chartHeight,
@@ -6363,11 +6426,12 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ jsx("div", {
6363
6426
  className: bar_chart_module_default["bar-chart__tooltip-row"],
6364
6427
  children: sprintf(__("%1$s: %2$s", "jetpack-charts"), label, value)
6365
6428
  });
6366
- 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 }) => {
6429
+ 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 }) => {
6367
6430
  if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
6368
6431
  const legendInteractive = legend.interactive ?? false;
6369
6432
  const legendCollapseGroups = legend.collapseGroups ?? false;
6370
6433
  const legendComparisonItem = legend.comparisonItem ?? false;
6434
+ const legendComparisonSeriesItems = legend.comparisonSeriesItems;
6371
6435
  const horizontal = orientation === "horizontal";
6372
6436
  const chartId = useChartId(providedChartId);
6373
6437
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -6380,8 +6444,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6380
6444
  });
6381
6445
  const legendItems = useChartLegendItems(dataSorted, useMemo(() => ({
6382
6446
  collapseGroups: legendCollapseGroups,
6383
- comparisonItem: legendComparisonItem
6384
- }), [legendCollapseGroups, legendComparisonItem]));
6447
+ comparisonItem: legendComparisonItem,
6448
+ comparisonSeriesItems: legendComparisonSeriesItems
6449
+ }), [
6450
+ legendCollapseGroups,
6451
+ legendComparisonItem,
6452
+ legendComparisonSeriesItems
6453
+ ]));
6385
6454
  const { getElementStyles } = useGlobalChartsContext();
6386
6455
  const isSeriesRendered = useCallback((series) => isSeriesVisible(series.label), [isSeriesVisible]);
6387
6456
  const hasWholeNumberValues = useMemo(() => hasOnlyWholeNumbers(dataSorted.filter(isSeriesRendered)), [dataSorted, isSeriesRendered]);
@@ -6671,9 +6740,9 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6671
6740
  children: ({ contentHeight }) => {
6672
6741
  const chartHeight = contentHeight > 0 ? contentHeight : height;
6673
6742
  return /* @__PURE__ */ jsx("div", {
6674
- role: "grid",
6743
+ role: "application",
6675
6744
  ref: chartRef,
6676
- "aria-label": __("Bar chart", "jetpack-charts"),
6745
+ "aria-label": ariaLabel ?? __("Bar chart", "jetpack-charts"),
6677
6746
  tabIndex: 0,
6678
6747
  onKeyDown: onChartKeyDown,
6679
6748
  onFocus: onChartFocus,
@@ -6681,6 +6750,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6681
6750
  children: chartHeight > 0 && /* @__PURE__ */ jsx("div", {
6682
6751
  className: xy_plot_module_default["xy-plot"],
6683
6752
  children: /* @__PURE__ */ jsxs(XYChart, {
6753
+ accessibilityLabel: "",
6684
6754
  theme,
6685
6755
  width,
6686
6756
  height: chartHeight,
@@ -6891,7 +6961,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
6891
6961
  right: 20,
6892
6962
  bottom: 0,
6893
6963
  top: 0
6894
- }, className, ...rest }) => {
6964
+ }, className, ariaLabel, ...rest }) => {
6895
6965
  const { getElementStyles } = useGlobalChartsContext();
6896
6966
  const tintedData = useMemo(() => {
6897
6967
  if (data.length > 1) return data;
@@ -6966,6 +7036,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
6966
7036
  xScale: chartOptions.xScale,
6967
7037
  yScale: chartOptions.yScale
6968
7038
  },
7039
+ ariaLabel: ariaLabel ?? __("Bar list chart", "jetpack-charts"),
6969
7040
  ...rest
6970
7041
  });
6971
7042
  };
@@ -7041,6 +7112,8 @@ const useFunnelSelection = (hideTooltip) => {
7041
7112
  };
7042
7113
  //#endregion
7043
7114
  //#region src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx
7115
+ /** Chart height (px) below which the funnel tightens its gaps so the bars keep their room. */
7116
+ const SHORT_CHART_HEIGHT = 180;
7044
7117
  /**
7045
7118
  * Internal ConversionFunnelChart component with chart registration
7046
7119
  *
@@ -7052,6 +7125,7 @@ const useFunnelSelection = (hideTooltip) => {
7052
7125
  * @param props.loading - Whether the chart is in loading state
7053
7126
  * @param props.animation - Whether to show chart animation on initial render or not
7054
7127
  * @param props.className - Additional CSS class name
7128
+ * @param props.ariaLabel - Accessible name for the chart group
7055
7129
  * @param props.height - Height of the chart container. Falls back to style.height if set, otherwise defaults to "100%".
7056
7130
  * @param props.style - Custom styling
7057
7131
  * @param props.renderStepLabel - Custom render function for step labels
@@ -7061,7 +7135,7 @@ const useFunnelSelection = (hideTooltip) => {
7061
7135
  * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
7062
7136
  * @return JSX element representing the conversion funnel chart
7063
7137
  */
7064
- const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
7138
+ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, ariaLabel, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
7065
7139
  const chartId = useChartId(providedChartId);
7066
7140
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
7067
7141
  const chartRef = useRef(null);
@@ -7070,10 +7144,13 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7070
7144
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = useTooltip();
7071
7145
  const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
7072
7146
  const standaloneScopeClass = useStandaloneScopeClass();
7147
+ const [measureChartRef, , chartHeight] = useElementSize();
7148
+ const gap = chartHeight > 0 && chartHeight < SHORT_CHART_HEIGHT ? "sm" : "xl";
7073
7149
  const setChartRef = useCallback((node) => {
7074
7150
  chartRef.current = node;
7075
7151
  setScopeNode(node);
7076
- }, []);
7152
+ measureChartRef(node);
7153
+ }, [measureChartRef]);
7077
7154
  const clearSelectionAndRef = useCallback(() => {
7078
7155
  clearSelection();
7079
7156
  selectedBarRef.current = null;
@@ -7221,7 +7298,9 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7221
7298
  const maxRate = Math.max(...steps.map((step) => step.rate));
7222
7299
  return /* @__PURE__ */ jsxs(Stack, {
7223
7300
  direction: "column",
7224
- gap: "xl",
7301
+ gap,
7302
+ role: "group",
7303
+ "aria-label": ariaLabel ?? __("Conversion funnel chart", "jetpack-charts"),
7225
7304
  ref: setChartRef,
7226
7305
  className: clsx("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
7227
7306
  style: {
@@ -7252,7 +7331,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7252
7331
  return /* @__PURE__ */ jsxs(Stack, {
7253
7332
  direction: "column",
7254
7333
  className: clsx(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"]),
7255
- gap: "xl",
7334
+ gap,
7256
7335
  children: [/* @__PURE__ */ jsxs(Stack, {
7257
7336
  direction: "column",
7258
7337
  gap: "xs",
@@ -7446,6 +7525,7 @@ function normalizeGeoChartError(eventArgs) {
7446
7525
  *
7447
7526
  * @param props - The props for the GeoChart component
7448
7527
  * @param props.data - Data in Google Charts format (array of arrays with headers)
7528
+ * @param props.ariaLabel - Accessible name of the chart
7449
7529
  * @param props.width - Width of the chart in pixels
7450
7530
  * @param props.height - Height of the chart in pixels
7451
7531
  * @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
@@ -7457,7 +7537,7 @@ function normalizeGeoChartError(eventArgs) {
7457
7537
  * @param props.renderPlaceholder - Optional render function for the loading placeholder
7458
7538
  * @return A React component displaying an interactive map with data visualization
7459
7539
  */
7460
- const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", displayMode = "regions", domain, onError, renderPlaceholder }) => {
7540
+ const GeoChartInternal = ({ className, data, ariaLabel, width, height, region = "world", resolution = "countries", displayMode = "regions", domain, onError, renderPlaceholder }) => {
7461
7541
  const { getElementStyles } = useGlobalChartsContext();
7462
7542
  const scopeElement = useChartScopeElement();
7463
7543
  const containerRef = useRef(null);
@@ -7545,8 +7625,14 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7545
7625
  sanitizedData.hasHtmlTooltips
7546
7626
  ]);
7547
7627
  const chartEvents = useMemo(() => {
7548
- if (!onError) return;
7549
- return [{
7628
+ const readyEvent = {
7629
+ eventName: "ready",
7630
+ callback: () => {
7631
+ containerRef.current?.querySelectorAll("svg").forEach((svg) => svg.setAttribute("aria-hidden", "true"));
7632
+ }
7633
+ };
7634
+ if (!onError) return [readyEvent];
7635
+ return [readyEvent, {
7550
7636
  eventName: "error",
7551
7637
  callback: ({ eventArgs }) => {
7552
7638
  onError(normalizeGeoChartError(eventArgs));
@@ -7555,6 +7641,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7555
7641
  }, [onError]);
7556
7642
  return /* @__PURE__ */ jsx(Center, {
7557
7643
  ref: containerRef,
7644
+ role: "group",
7645
+ "aria-label": ariaLabel ?? __("Geo chart", "jetpack-charts"),
7558
7646
  className: clsx("geo-chart", geo_chart_module_default.container, className),
7559
7647
  style: {
7560
7648
  width,
@@ -7686,6 +7774,7 @@ var heatmap_chart_module_default = {
7686
7774
  "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
7687
7775
  "heatmap-chart__legend-scale--bar": "a8ccharts-O3YMOW-heatmap-chart__legend-scale--bar",
7688
7776
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
7777
+ "heatmap-chart__legend-swatch--empty": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch--empty",
7689
7778
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
7690
7779
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
7691
7780
  "heatmap-chart--height-capped": "a8ccharts-O3YMOW-heatmap-chart--height-capped"
@@ -7783,8 +7872,9 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7783
7872
  const context = useContext(HeatmapContext);
7784
7873
  const { legend } = useGlobalChartsTheme();
7785
7874
  if (!context) return null;
7786
- const { fillVars } = context;
7875
+ const { extent, fillVars } = context;
7787
7876
  const labelStyle = legend.labelStyles;
7877
+ const showsEmptyCell = isEmptyValue(0, extent);
7788
7878
  return /* @__PURE__ */ jsxs(Stack, {
7789
7879
  direction: "row",
7790
7880
  gap: "xs",
@@ -7796,12 +7886,12 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7796
7886
  style: labelStyle,
7797
7887
  children: lessLabel ?? __("Less", "jetpack-charts")
7798
7888
  }),
7799
- /* @__PURE__ */ jsx(Stack, {
7889
+ /* @__PURE__ */ jsxs(Stack, {
7800
7890
  direction: "row",
7801
7891
  gap: variant === "bar" ? void 0 : "xs",
7802
7892
  "aria-hidden": "true",
7803
7893
  className: variant === "bar" ? heatmap_chart_module_default["heatmap-chart__legend-scale--bar"] : void 0,
7804
- children: Array.from({ length: steps }, (_, index) => {
7894
+ children: [showsEmptyCell && /* @__PURE__ */ jsx("span", { className: clsx(heatmap_chart_module_default["heatmap-chart__legend-swatch"], heatmap_chart_module_default["heatmap-chart__legend-swatch--empty"]) }), Array.from({ length: steps }, (_, index) => {
7805
7895
  const intensity = steps <= 1 ? 1 : index / (steps - 1);
7806
7896
  return /* @__PURE__ */ jsx("span", {
7807
7897
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
@@ -7810,7 +7900,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7810
7900
  "--a8c-charts-heatmap-cell-intensity": intensity
7811
7901
  }
7812
7902
  }, index);
7813
- })
7903
+ })]
7814
7904
  }),
7815
7905
  /* @__PURE__ */ jsx(Text$1, {
7816
7906
  variant: "body-sm",
@@ -7836,9 +7926,11 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7836
7926
  const ungrouped = () => ({
7837
7927
  columns: Array.from({ length: columnCount }, (_, index) => ({
7838
7928
  line: firstLine + index,
7929
+ band: 0,
7839
7930
  gapBefore: false
7840
7931
  })),
7841
- groups: []
7932
+ groups: [],
7933
+ bands: 1
7842
7934
  });
7843
7935
  if (!groups || groups.length === 0 || columnCount === 0) return ungrouped();
7844
7936
  const spans = groups.map((group) => group.span);
@@ -7854,11 +7946,13 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7854
7946
  if (groupIndex > 0) line += 1;
7855
7947
  laidOut.push({
7856
7948
  ...group,
7857
- line
7949
+ line,
7950
+ band: 0
7858
7951
  });
7859
7952
  for (let offset = 0; offset < group.span; offset++) {
7860
7953
  columns.push({
7861
7954
  line,
7955
+ band: 0,
7862
7956
  gapBefore: groupIndex > 0 && offset === 0,
7863
7957
  group: groupIndex
7864
7958
  });
@@ -7868,13 +7962,99 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7868
7962
  while (columns.length < columnCount) {
7869
7963
  columns.push({
7870
7964
  line,
7965
+ band: 0,
7871
7966
  gapBefore: false
7872
7967
  });
7873
7968
  line += 1;
7874
7969
  }
7875
7970
  return {
7876
7971
  columns,
7877
- groups: laidOut
7972
+ groups: laidOut,
7973
+ bands: 1
7974
+ };
7975
+ };
7976
+ /**
7977
+ * Wraps the groups onto bands of equal group count, each band starting back at
7978
+ * the first data line so the groups line up in slots down the bands.
7979
+ *
7980
+ * @param layout - The unwrapped layout from `resolveColumnGroups`.
7981
+ * @param bands - Bands to aim for; the groups split as evenly as whole bands allow.
7982
+ * @return The wrapped layout, or null when there is nothing to wrap: one band, one group, or a column outside every group.
7983
+ */
7984
+ const wrapColumnGroups = (layout, bands) => {
7985
+ const { columns, groups } = layout;
7986
+ if (bands < 2 || groups.length < 2 || columns.some((column) => column.group === void 0)) return null;
7987
+ const perBand = Math.ceil(groups.length / bands);
7988
+ const slotSpans = Array(perBand).fill(0);
7989
+ groups.forEach((group, index) => {
7990
+ const slot = index % perBand;
7991
+ slotSpans[slot] = Math.max(slotSpans[slot], group.span);
7992
+ });
7993
+ const slotLines = [];
7994
+ let line = columns[0].line;
7995
+ slotSpans.forEach((span, slot) => {
7996
+ if (slot > 0) line += 1;
7997
+ slotLines.push(line);
7998
+ line += span;
7999
+ });
8000
+ const wrappedGroups = groups.map((group, index) => ({
8001
+ ...group,
8002
+ line: slotLines[index % perBand],
8003
+ band: Math.floor(index / perBand)
8004
+ }));
8005
+ return {
8006
+ columns: columns.map((column) => {
8007
+ const group = column.group;
8008
+ const offset = column.line - groups[group].line;
8009
+ return {
8010
+ ...column,
8011
+ line: wrappedGroups[group].line + offset,
8012
+ band: wrappedGroups[group].band,
8013
+ gapBefore: offset === 0 && group % perBand > 0
8014
+ };
8015
+ }),
8016
+ groups: wrappedGroups,
8017
+ bands: Math.ceil(groups.length / perBand),
8018
+ slotSpans
8019
+ };
8020
+ };
8021
+ //#endregion
8022
+ //#region src/charts/heatmap-chart/private/fit-compact-cells.ts
8023
+ /**
8024
+ * Largest whole-pixel square cell, and the band count giving it, for a compact grid in a box.
8025
+ *
8026
+ * @param input - The box, the grid's layout, and its fixed gaps and label sizes.
8027
+ * @return The fit; one band at `minCellSize` when no band count reaches it.
8028
+ */
8029
+ const fitCompactCells = (input) => {
8030
+ const { width, height, layout, rows, cellGap, groupGap, minCellSize } = input;
8031
+ const columns = layout.columns.length;
8032
+ if (columns === 0 || rows === 0) return {
8033
+ bands: 1,
8034
+ cellSize: minCellSize
8035
+ };
8036
+ const maxBands = Math.max(1, layout.groups.length);
8037
+ const labelHeights = (input.columnLabelHeight ?? 0) + (input.groupLabelHeight ?? 0);
8038
+ const bandTracks = (input.columnLabelHeight === null ? 0 : 1) + rows + (input.groupLabelHeight === null ? 0 : 1);
8039
+ let best = null;
8040
+ for (let target = 1; target <= maxBands; target++) {
8041
+ const wrapped = target > 1 ? wrapColumnGroups(layout, target) : null;
8042
+ const { bands } = wrapped ?? layout;
8043
+ const cellColumns = wrapped ? wrapped.slotSpans.reduce((sum, span) => sum + span, 0) : columns;
8044
+ const gapTracks = wrapped ? wrapped.slotSpans.length - 1 : layout.columns.filter((column) => column.gapBefore).length;
8045
+ const columnTracks = 1 + cellColumns + gapTracks;
8046
+ const byWidth = (width - input.rowLabelWidth - gapTracks * groupGap - (columnTracks - 1) * cellGap) / cellColumns;
8047
+ const rowTracks = bands * bandTracks + (bands - 1);
8048
+ const byHeight = (height - (bands * labelHeights + (bands - 1) * groupGap + (rowTracks - 1) * cellGap)) / (bands * rows);
8049
+ const cellSize = Math.floor(Math.min(byWidth, byHeight));
8050
+ if (cellSize >= minCellSize && (!best || cellSize > best.cellSize)) best = {
8051
+ bands,
8052
+ cellSize
8053
+ };
8054
+ }
8055
+ return best ?? {
8056
+ bands: 1,
8057
+ cellSize: minCellSize
7878
8058
  };
7879
8059
  };
7880
8060
  //#endregion
@@ -8017,18 +8197,32 @@ const CELL_VALUE_MODIFIER = {
8017
8197
  white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
8018
8198
  };
8019
8199
  const NO_ROW_LABELS = [];
8020
- const TOOLTIP_BOX_STYLE = { zIndex: 3 };
8200
+ const TOOLTIP_BOX_STYLE$1 = { zIndex: 3 };
8201
+ const largest = (root, selector, size) => Math.max(0, ...Array.from(root.querySelectorAll(selector), (el) => el[size]));
8202
+ const gridBox = (chart, grid) => {
8203
+ let height = chart.clientHeight;
8204
+ const rowGap = parseFloat(getComputedStyle(chart).rowGap) || 0;
8205
+ Array.from(chart.children).forEach((child) => {
8206
+ const { display, position } = getComputedStyle(child);
8207
+ if (child === grid || display === "none" || position === "absolute" || position === "fixed") return;
8208
+ height -= child.offsetHeight + rowGap;
8209
+ });
8210
+ return {
8211
+ width: chart.clientWidth,
8212
+ height
8213
+ };
8214
+ };
8021
8215
  const cellName = (info) => info.cellLabel || [
8022
8216
  info.groupLabel,
8023
8217
  info.columnLabel,
8024
8218
  info.rowLabel
8025
8219
  ].filter(Boolean).join(" ");
8026
- 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 }) => {
8220
+ 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 }) => {
8027
8221
  const chartId = useChartId(providedChartId);
8028
8222
  const tooltipBoxStyle = tooltipStyle ? {
8029
- ...TOOLTIP_BOX_STYLE,
8223
+ ...TOOLTIP_BOX_STYLE$1,
8030
8224
  ...tooltipStyle
8031
- } : TOOLTIP_BOX_STYLE;
8225
+ } : TOOLTIP_BOX_STYLE$1;
8032
8226
  const { getElementStyles, theme } = useGlobalChartsContext();
8033
8227
  const scopeElement = useChartScopeElement();
8034
8228
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -8037,8 +8231,9 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8037
8231
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = useTooltip();
8038
8232
  const standaloneScopeClass = useStandaloneScopeClass();
8039
8233
  const containerRef = useRef(null);
8234
+ const chartRef = useRef(null);
8040
8235
  const [labelRoles, setLabelRoles] = useState(null);
8041
- const getTooltipOrigin = useCallback(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
8236
+ const getTooltipOrigin = useCallback(() => chartRef.current?.getBoundingClientRect() ?? null, []);
8042
8237
  const { color: primaryColorHex } = getElementStyles({
8043
8238
  index: 0,
8044
8239
  overrideColor: primaryColor
@@ -8080,6 +8275,49 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8080
8275
  const groupLayout = useMemo(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
8081
8276
  const { compactCellGap, compactCellSize, groupGap } = heatmapChartSettings;
8082
8277
  const drawValues = showValues ?? !compact;
8278
+ const hasColumnLabels = data.some((column) => Boolean(column.label));
8279
+ const hasGroups = groupLayout.groups.length > 0;
8280
+ const [fit, setFit] = useState(null);
8281
+ const hasSummary = data.some((column) => column.summary);
8282
+ const fitting = compact && fitCells && !hasSummary;
8283
+ if (fitCells && !fitting) warnOnce("heatmap:fitCells", "fitCells applies only in compact mode without summary columns, so the cells keep their size.");
8284
+ useIsomorphicLayoutEffect(() => {
8285
+ const chart = chartRef.current;
8286
+ const grid = containerRef.current;
8287
+ if (!fitting || !chart || !grid) {
8288
+ setFit(null);
8289
+ return;
8290
+ }
8291
+ const measure = () => {
8292
+ const next = fitCompactCells({
8293
+ ...gridBox(chart, grid),
8294
+ layout: groupLayout,
8295
+ rows,
8296
+ cellGap: compactCellGap,
8297
+ groupGap,
8298
+ rowLabelWidth: largest(grid, `.${heatmap_chart_module_default["heatmap-chart__row-label"]}`, "offsetWidth"),
8299
+ columnLabelHeight: hasColumnLabels ? largest(grid, `.${heatmap_chart_module_default["heatmap-chart__col-label"]}`, "offsetHeight") : null,
8300
+ groupLabelHeight: hasGroups ? largest(grid, `.${heatmap_chart_module_default["heatmap-chart__group-label"]}`, "offsetHeight") : null,
8301
+ minCellSize: compactCellSize
8302
+ });
8303
+ setFit((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
8304
+ };
8305
+ measure();
8306
+ if (typeof ResizeObserver === "undefined") return;
8307
+ const observer = new ResizeObserver(() => flushSync(measure));
8308
+ observer.observe(chart);
8309
+ return () => observer.disconnect();
8310
+ }, [
8311
+ fitting,
8312
+ groupLayout,
8313
+ rows,
8314
+ hasColumnLabels,
8315
+ hasGroups,
8316
+ compactCellGap,
8317
+ compactCellSize,
8318
+ groupGap
8319
+ ]);
8320
+ const wrapped = useMemo(() => fit ? wrapColumnGroups(groupLayout, fit.bands) : null, [fit, groupLayout]);
8083
8321
  const buildTooltipData = useCallback((columnIndex, rowIndex) => {
8084
8322
  const cell = data[columnIndex]?.data[rowIndex];
8085
8323
  const group = groupLayout.columns[columnIndex]?.group;
@@ -8219,21 +8457,25 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8219
8457
  });
8220
8458
  const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
8221
8459
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
8222
- const hasColumnLabels = data.some((column) => Boolean(column.label));
8223
- const hasGroups = groupLayout.groups.length > 0;
8224
- const columnLine = (columnIndex) => groupLayout.columns[columnIndex].line;
8225
- const firstDataRow = hasColumnLabels ? 2 : 1;
8460
+ const placed = wrapped ?? groupLayout;
8461
+ const columnLine = (columnIndex) => placed.columns[columnIndex].line;
8462
+ const bandOf = (columnIndex) => placed.columns[columnIndex].band;
8463
+ const { bands } = placed;
8464
+ const bandRowCount = (hasColumnLabels ? 1 : 0) + rows + (hasGroups ? 1 : 0) + 1;
8465
+ const labelRowOf = (band) => 1 + band * bandRowCount;
8466
+ const dataRowOf = (band, rowIndex) => labelRowOf(band) + (hasColumnLabels ? 1 : 0) + rowIndex;
8226
8467
  const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
8227
8468
  const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
8228
- const columnTracks = data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ");
8469
+ 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(" ");
8470
+ const bandTracks = `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`;
8229
8471
  const gridStyle = {
8230
8472
  ...fillVars,
8231
8473
  gridTemplateColumns: `auto ${columnTracks}`,
8232
- gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
8474
+ gridTemplateRows: Array(bands).fill(bandTracks).join(` ${groupGap}px `)
8233
8475
  };
8234
8476
  if (compact) {
8235
8477
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
8236
- gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
8478
+ gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${fit?.cellSize ?? compactCellSize}px`;
8237
8479
  }
8238
8480
  const summaryGaps = (columnIndex) => {
8239
8481
  if (!data[columnIndex]?.summary) return {};
@@ -8253,6 +8495,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8253
8495
  legendChildren: [],
8254
8496
  trailingContent: nonLegendChildren,
8255
8497
  gap,
8498
+ rootRef: chartRef,
8256
8499
  className: clsx("heatmap-chart", heatmap_chart_module_default["heatmap-chart"], className, { [heatmap_chart_module_default["heatmap-chart--height-capped"]]: heightCapped }),
8257
8500
  style: {
8258
8501
  width: width || void 0,
@@ -8282,11 +8525,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8282
8525
  className: heatmap_chart_module_default["heatmap-chart__row"],
8283
8526
  children: [/* @__PURE__ */ jsx("span", { style: {
8284
8527
  gridColumn: 1,
8285
- gridRow: 1
8528
+ gridRow: labelRowOf(0)
8286
8529
  } }), data.map((column, columnIndex) => /* @__PURE__ */ jsx("span", {
8287
8530
  style: {
8288
8531
  gridColumn: columnLine(columnIndex),
8289
- gridRow: 1
8532
+ gridRow: labelRowOf(bandOf(columnIndex))
8290
8533
  },
8291
8534
  className: clsx(heatmap_chart_module_default["heatmap-chart__col-label"], {
8292
8535
  [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
@@ -8297,24 +8540,23 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8297
8540
  }),
8298
8541
  Array.from({ length: rows }).map((_row, rowIndex) => {
8299
8542
  const labelVisible = !compact || rowIndex % 2 === 0;
8300
- const gridRow = firstDataRow + rowIndex;
8301
8543
  return /* @__PURE__ */ jsxs("div", {
8302
8544
  role: "row",
8303
8545
  "aria-rowindex": rowIndex + 1,
8304
8546
  className: heatmap_chart_module_default["heatmap-chart__row"],
8305
- children: [/* @__PURE__ */ jsx("span", {
8547
+ children: [Array.from({ length: bands }).map((_band, band) => /* @__PURE__ */ jsx("span", {
8306
8548
  "aria-hidden": "true",
8307
8549
  className: heatmap_chart_module_default["heatmap-chart__row-label"],
8308
8550
  style: {
8309
8551
  gridColumn: 1,
8310
- gridRow
8552
+ gridRow: dataRowOf(band, rowIndex)
8311
8553
  },
8312
8554
  children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
8313
- }), data.map((column, columnIndex) => {
8555
+ }, `row-label-${band}`)), data.map((column, columnIndex) => {
8314
8556
  const cell = column.data[rowIndex];
8315
8557
  const placement = {
8316
8558
  gridColumn: columnLine(columnIndex),
8317
- gridRow
8559
+ gridRow: dataRowOf(bandOf(columnIndex), rowIndex)
8318
8560
  };
8319
8561
  if (cell?.hidden) return /* @__PURE__ */ jsx("div", {
8320
8562
  "aria-hidden": "true",
@@ -8364,11 +8606,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8364
8606
  role: "row",
8365
8607
  "aria-hidden": "true",
8366
8608
  className: heatmap_chart_module_default["heatmap-chart__row"],
8367
- children: groupLayout.groups.map((group, groupIndex) => /* @__PURE__ */ jsx("span", {
8609
+ children: placed.groups.map((group, groupIndex) => /* @__PURE__ */ jsx("span", {
8368
8610
  className: heatmap_chart_module_default["heatmap-chart__group-label"],
8369
8611
  style: {
8370
8612
  gridColumn: `${group.line} / span ${group.span}`,
8371
- gridRow: firstDataRow + rows
8613
+ gridRow: dataRowOf(group.band, rows)
8372
8614
  },
8373
8615
  children: group.label
8374
8616
  }, `group-${groupIndex}`))
@@ -11105,8 +11347,8 @@ const SUBPIXEL_TOLERANCE = .5;
11105
11347
  * depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
11106
11348
  * whatever a caller renders as a label, so any restated number would drift.
11107
11349
  *
11108
- * Every row is a direct grid child marked with `data-row-index`; interactive rows
11109
- * use a button wrapper and non-interactive rows use a div wrapper.
11350
+ * Every row is a `listitem` div, a direct grid child marked with `data-row-index`;
11351
+ * interactive rows hold a button inside it.
11110
11352
  *
11111
11353
  * @param enabled - Whether to measure at all. When false every row fits.
11112
11354
  * @param rowCount - Total number of rows rendered.
@@ -11238,6 +11480,7 @@ var leaderboard_chart_module_default = {
11238
11480
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
11239
11481
  "is-overlay": "a8ccharts-GovfoW-is-overlay",
11240
11482
  "label": "a8ccharts-GovfoW-label",
11483
+ "layeredRow": "a8ccharts-GovfoW-layeredRow",
11241
11484
  "leaderboardChart": "a8ccharts-GovfoW-leaderboardChart",
11242
11485
  "leaderboardChart__content": "a8ccharts-GovfoW-leaderboardChart__content",
11243
11486
  "leaderboardChart__content--fit": "a8ccharts-GovfoW-leaderboardChart__content--fit",
@@ -11246,6 +11489,8 @@ var leaderboard_chart_module_default = {
11246
11489
  "overlap": "a8ccharts-GovfoW-overlap",
11247
11490
  "overlayLabel": "a8ccharts-GovfoW-overlayLabel",
11248
11491
  "row": "a8ccharts-GovfoW-row",
11492
+ "rowButton": "a8ccharts-GovfoW-rowButton",
11493
+ "rowItem": "a8ccharts-GovfoW-rowItem",
11249
11494
  "stretch": "a8ccharts-GovfoW-stretch",
11250
11495
  "valueContainer": "a8ccharts-GovfoW-valueContainer"
11251
11496
  };
@@ -11296,6 +11541,16 @@ const defaultDeltaFormatter = (value) => {
11296
11541
  const getBarWidth = (share) => `calc(${share}% - var(--a8c-charts-dimension-leaderboard-bar-hover-inset, 0px) * ${share} / 100)`;
11297
11542
  const hasPreviousValue = (entry) => entry.previousValue != null && entry.previousShare != null;
11298
11543
  const hasDeltaValue = (entry) => hasPreviousValue(entry) && entry.delta != null;
11544
+ /**
11545
+ * A layered row's cell that takes pointer events of its own (a value tooltip)
11546
+ * sits above the row button; pass its clicks on so they still drill down.
11547
+ *
11548
+ * @param event - The click on the row.
11549
+ */
11550
+ function forwardCellClickToRowButton(event) {
11551
+ if (event.target.closest("a, button")) return;
11552
+ event.currentTarget.querySelector(":scope > button")?.click();
11553
+ }
11299
11554
  const BarLabel = ({ label }) => /* @__PURE__ */ jsx(Fragment$1, { children: typeof label === "string" ? /* @__PURE__ */ jsx(Text$1, {
11300
11555
  className: leaderboard_chart_module_default.label,
11301
11556
  children: label
@@ -11348,9 +11603,10 @@ const BarWithLabel = ({ entry, withComparison, withOverlayLabel, primaryColor, s
11348
11603
  * @param props.children - Child components for composition API
11349
11604
  * @param props.className - Additional CSS class name
11350
11605
  * @param props.style - Custom styling for the chart container
11606
+ * @param props.ariaLabel - Accessible name for the list, defaulting to "Leaderboard chart"
11351
11607
  * @return JSX element representing the leaderboard chart
11352
11608
  */
11353
- 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 }) => {
11609
+ 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 }) => {
11354
11610
  const legendInteractive = legend.interactive ?? false;
11355
11611
  const legendPosition = legend.position ?? "bottom";
11356
11612
  const chartId = useChartId(providedChartId);
@@ -11481,6 +11737,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
11481
11737
  className: leaderboard_chart_module_default.emptyState,
11482
11738
  children: getAllHiddenMessage(legendInteractive, "series")
11483
11739
  }) : /* @__PURE__ */ jsx(component_default, {
11740
+ role: "list",
11741
+ "aria-label": ariaLabel ?? __("Leaderboard chart", "jetpack-charts"),
11484
11742
  templateColumns: "minmax(0, 1fr) auto",
11485
11743
  rowGap: ROW_GAP,
11486
11744
  columnGap: COLUMN_GAP,
@@ -11528,20 +11786,49 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
11528
11786
  })
11529
11787
  ]
11530
11788
  })] });
11531
- if (entry.onClick) return /* @__PURE__ */ jsxs("button", {
11532
- type: "button",
11789
+ if (entry.onClick && entry.hasInteractiveLabel) return /* @__PURE__ */ jsx("div", {
11790
+ role: "listitem",
11533
11791
  "data-row-index": rowIndex,
11534
11792
  style: rowStyle,
11535
- className: clsx(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow),
11536
- onClick: entry.onClick,
11537
- "aria-label": entry.ariaLabel,
11538
- children: [rowCells, /* @__PURE__ */ jsx(Icon, {
11539
- className: leaderboard_chart_module_default.chevron,
11540
- icon: chevronRight,
11541
- size: 24
11542
- })]
11793
+ className: leaderboard_chart_module_default.rowItem,
11794
+ children: /* @__PURE__ */ jsxs("div", {
11795
+ className: clsx(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow, leaderboard_chart_module_default.layeredRow),
11796
+ onClick: forwardCellClickToRowButton,
11797
+ children: [
11798
+ rowCells,
11799
+ /* @__PURE__ */ jsx("button", {
11800
+ type: "button",
11801
+ className: leaderboard_chart_module_default.rowButton,
11802
+ onClick: entry.onClick,
11803
+ "aria-label": entry.ariaLabel
11804
+ }),
11805
+ /* @__PURE__ */ jsx(Icon, {
11806
+ className: leaderboard_chart_module_default.chevron,
11807
+ icon: chevronRight,
11808
+ size: 24
11809
+ })
11810
+ ]
11811
+ })
11812
+ }, entry.id);
11813
+ if (entry.onClick) return /* @__PURE__ */ jsx("div", {
11814
+ role: "listitem",
11815
+ "data-row-index": rowIndex,
11816
+ style: rowStyle,
11817
+ className: leaderboard_chart_module_default.rowItem,
11818
+ children: /* @__PURE__ */ jsxs("button", {
11819
+ type: "button",
11820
+ className: clsx(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow),
11821
+ onClick: entry.onClick,
11822
+ "aria-label": entry.ariaLabel,
11823
+ children: [rowCells, /* @__PURE__ */ jsx(Icon, {
11824
+ className: leaderboard_chart_module_default.chevron,
11825
+ icon: chevronRight,
11826
+ size: 24
11827
+ })]
11828
+ })
11543
11829
  }, entry.id);
11544
11830
  return /* @__PURE__ */ jsx("div", {
11831
+ role: "listitem",
11545
11832
  "data-row-index": rowIndex,
11546
11833
  style: rowStyle,
11547
11834
  className: leaderboard_chart_module_default.row,
@@ -11561,6 +11848,212 @@ LeaderboardChartWithProvider.displayName = "LeaderboardChart";
11561
11848
  const LeaderboardChart = attachSubComponents(LeaderboardChartWithProvider, { Legend });
11562
11849
  const LeaderboardChartResponsive = attachSubComponents(withResponsive(LeaderboardChartWithProvider), { Legend });
11563
11850
  //#endregion
11851
+ //#region src/charts/private/pie-keyboard-navigation/order-arcs-for-navigation.ts
11852
+ /**
11853
+ * Orders arcs the way they appear: clockwise from 12 o'clock, which for a semi-circle is left to right.
11854
+ *
11855
+ * @param arcs - Arcs from visx's `pie` factory, in data order.
11856
+ * @return A copy of `arcs` sorted by angular midpoint.
11857
+ */
11858
+ const orderArcsForNavigation = (arcs) => [...arcs].sort((a, b) => a.startAngle + a.endAngle - (b.startAngle + b.endAngle));
11859
+ //#endregion
11860
+ //#region src/charts/private/pie-keyboard-navigation/pie-selection-announcement.tsx
11861
+ const PieSelectionAnnouncement = ({ data }) => /* @__PURE__ */ jsx(VisuallyHidden, {
11862
+ role: "status",
11863
+ "aria-atomic": "true",
11864
+ children: data && /* @__PURE__ */ jsx(LabelValueContent, { data })
11865
+ });
11866
+ //#endregion
11867
+ //#region src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx
11868
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
11869
+ const PieTooltip = ({ top, left, selectedIndex, tooltipRef, children }) => {
11870
+ const standaloneScopeClass = useStandaloneScopeClass();
11871
+ return /* @__PURE__ */ jsx(BoundedTooltip, {
11872
+ top,
11873
+ left,
11874
+ style: TOOLTIP_BOX_STYLE,
11875
+ children: selectedIndex === void 0 ? /* @__PURE__ */ jsx("div", {
11876
+ className: standaloneScopeClass,
11877
+ role: "tooltip",
11878
+ children
11879
+ }) : /* @__PURE__ */ jsx("div", {
11880
+ ref: tooltipRef,
11881
+ tabIndex: -1,
11882
+ role: "tooltip",
11883
+ "aria-atomic": "true",
11884
+ className: standaloneScopeClass,
11885
+ children
11886
+ }, `chart-tooltip-${selectedIndex}`)
11887
+ });
11888
+ };
11889
+ //#endregion
11890
+ //#region src/charts/private/pie-keyboard-navigation/pie-selection-output.tsx
11891
+ /**
11892
+ * Reports the selected segment: a focused tooltip for a keyboard selection, a pointer tooltip on hover, or a screen reader announcement without tooltips.
11893
+ *
11894
+ * @param {PieSelectionOutputProps} props - Component props
11895
+ * @return {JSX.Element} The announcement region and the tooltip, if any
11896
+ */
11897
+ const PieSelectionOutput = ({ withTooltips, selectedIndex, selectedData, keyboardTooltipAnchor, pointerTooltip, chartRef, svgRef, tooltipRef, renderTooltip }) => {
11898
+ const [svgOffset, setSvgOffset] = useState({
11899
+ top: 0,
11900
+ left: 0
11901
+ });
11902
+ const showKeyboardTooltip = withTooltips && selectedData && keyboardTooltipAnchor;
11903
+ useLayoutEffect(() => {
11904
+ const chartBounds = chartRef.current?.getBoundingClientRect();
11905
+ const svgBounds = svgRef.current?.getBoundingClientRect();
11906
+ if (!showKeyboardTooltip || !chartBounds || !svgBounds) return;
11907
+ const top = svgBounds.top - chartBounds.top;
11908
+ const left = svgBounds.left - chartBounds.left;
11909
+ setSvgOffset((current) => current.top === top && current.left === left ? current : {
11910
+ top,
11911
+ left
11912
+ });
11913
+ });
11914
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
11915
+ /* @__PURE__ */ jsx(PieSelectionAnnouncement, { data: withTooltips ? void 0 : selectedData }),
11916
+ showKeyboardTooltip && /* @__PURE__ */ jsx(PieTooltip, {
11917
+ top: svgOffset.top + keyboardTooltipAnchor.top,
11918
+ left: svgOffset.left + keyboardTooltipAnchor.left,
11919
+ selectedIndex,
11920
+ tooltipRef,
11921
+ children: renderTooltip({ tooltipData: selectedData })
11922
+ }),
11923
+ withTooltips && !selectedData && pointerTooltip?.data && /* @__PURE__ */ jsx(PieTooltip, {
11924
+ top: pointerTooltip.top,
11925
+ left: pointerTooltip.left,
11926
+ children: renderTooltip({ tooltipData: pointerTooltip.data })
11927
+ })
11928
+ ] });
11929
+ };
11930
+ //#endregion
11931
+ //#region src/charts/private/pie-keyboard-navigation/pie-keyboard-navigation.module.scss
11932
+ var pie_keyboard_navigation_module_default = {
11933
+ "selected-ring__contrast": "a8ccharts-V2HDKq-selected-ring__contrast",
11934
+ "selected-ring__focus": "a8ccharts-V2HDKq-selected-ring__focus"
11935
+ };
11936
+ //#endregion
11937
+ //#region src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx
11938
+ /**
11939
+ * Encodes `value` for an SVG id, losslessly so distinct chart ids never share a clip path.
11940
+ *
11941
+ * @param value - Any string.
11942
+ * @return `value` with each character outside `[A-Za-z0-9-]` written as `_<hex code point>_`.
11943
+ */
11944
+ const toSvgId = (value) => Array.from(value, (character) => /[A-Za-z0-9-]/.test(character) ? character : `_${character.codePointAt(0)?.toString(16)}_`).join("");
11945
+ /**
11946
+ * Marks the selected segment with a two-tone ring drawn inside its edge, so the SVG edge cannot cut it off.
11947
+ *
11948
+ * @param {SelectedSegmentRingProps} props - Component props
11949
+ * @return {JSX.Element} The ring
11950
+ */
11951
+ const SelectedSegmentRing = ({ chartId, d }) => {
11952
+ const clipId = `pie-selected-ring-clip-${toSvgId(chartId)}`;
11953
+ const clipPath = `url(#${clipId})`;
11954
+ return /* @__PURE__ */ jsxs("g", {
11955
+ pointerEvents: "none",
11956
+ "aria-hidden": "true",
11957
+ children: [
11958
+ /* @__PURE__ */ jsx("clipPath", {
11959
+ id: clipId,
11960
+ children: /* @__PURE__ */ jsx("path", { d })
11961
+ }),
11962
+ /* @__PURE__ */ jsx("path", {
11963
+ d,
11964
+ clipPath,
11965
+ className: pie_keyboard_navigation_module_default["selected-ring__contrast"]
11966
+ }),
11967
+ /* @__PURE__ */ jsx("path", {
11968
+ d,
11969
+ clipPath,
11970
+ className: pie_keyboard_navigation_module_default["selected-ring__focus"]
11971
+ })
11972
+ ]
11973
+ });
11974
+ };
11975
+ //#endregion
11976
+ //#region src/charts/private/pie-keyboard-navigation/use-pie-keyboard-navigation.ts
11977
+ const usePieKeyboardNavigation = ({ navigationArcs, withTooltips, tooltipOffsetX, tooltipOffsetY }) => {
11978
+ const chartRef = useRef(null);
11979
+ const svgRef = useRef(null);
11980
+ const [selectedIndex, setSelectedIndex] = useState(void 0);
11981
+ const [isNavigating, setIsNavigating] = useState(false);
11982
+ const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
11983
+ const { tooltipRef: focusTooltip, onChartFocus, onChartBlur, onChartKeyDown, onChartPointerMove } = useKeyboardNavigation({
11984
+ selectedIndex,
11985
+ setSelectedIndex,
11986
+ isNavigating,
11987
+ setIsNavigating,
11988
+ chartRef,
11989
+ totalPoints: navigationArcs.length,
11990
+ visibleSeriesKey: JSON.stringify(navigationArcs.map((arcDatum) => arcDatum.data.label))
11991
+ });
11992
+ const selectedArc = selectedIndex === void 0 ? void 0 : navigationArcs[selectedIndex];
11993
+ const keyboardTooltip = useRef(null);
11994
+ const tooltipLostFocus = useRef(false);
11995
+ const tooltipRef = useCallback((element) => {
11996
+ if (element) {
11997
+ keyboardTooltip.current = element;
11998
+ focusTooltip(element);
11999
+ } else if (keyboardTooltip.current?.ownerDocument.activeElement === keyboardTooltip.current) tooltipLostFocus.current = true;
12000
+ }, [focusTooltip]);
12001
+ useLayoutEffect(() => {
12002
+ if (!tooltipLostFocus.current) return;
12003
+ tooltipLostFocus.current = false;
12004
+ const { activeElement, body } = chartRef.current?.ownerDocument ?? document;
12005
+ if (activeElement === null || activeElement === body) chartRef.current?.focus();
12006
+ });
12007
+ const getSegmentHandlers = (data, navigationIndex) => ({
12008
+ onMouseMove: (event) => {
12009
+ onChartPointerMove(navigationIndex);
12010
+ if (!withTooltips) return;
12011
+ const bounds = chartRef.current?.getBoundingClientRect();
12012
+ if (!bounds) return;
12013
+ showTooltip({
12014
+ tooltipData: data,
12015
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12016
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12017
+ });
12018
+ },
12019
+ onMouseLeave: withTooltips ? hideTooltip : void 0
12020
+ });
12021
+ return {
12022
+ chartRef,
12023
+ svgRef,
12024
+ selectedArc,
12025
+ getSegmentHandlers,
12026
+ getKeyboardTooltipAnchor: useCallback((originX, originY, [x, y]) => withTooltips ? {
12027
+ left: originX + x + tooltipOffsetX,
12028
+ top: originY + y + tooltipOffsetY
12029
+ } : void 0, [
12030
+ withTooltips,
12031
+ tooltipOffsetX,
12032
+ tooltipOffsetY
12033
+ ]),
12034
+ selectionOutputProps: {
12035
+ withTooltips,
12036
+ selectedIndex,
12037
+ selectedData: selectedArc?.data,
12038
+ chartRef,
12039
+ svgRef,
12040
+ tooltipRef,
12041
+ pointerTooltip: tooltipOpen ? {
12042
+ data: tooltipData,
12043
+ left: tooltipLeft || 0,
12044
+ top: tooltipTop || 0
12045
+ } : void 0
12046
+ },
12047
+ chartProps: {
12048
+ role: "application",
12049
+ tabIndex: 0,
12050
+ onKeyDown: onChartKeyDown,
12051
+ onFocus: onChartFocus,
12052
+ onBlur: onChartBlur
12053
+ }
12054
+ };
12055
+ };
12056
+ //#endregion
11564
12057
  //#region src/charts/private/radial-wipe-animation/radial-wipe-animation.tsx
11565
12058
  /**
11566
12059
  * Renders a SVG mask that creates a radial wipe animation effect.
@@ -11601,6 +12094,7 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
11601
12094
  //#endregion
11602
12095
  //#region src/charts/pie-chart/pie-chart.module.scss
11603
12096
  var pie_chart_module_default = {
12097
+ "pie-chart": "a8ccharts-gnszbG-pie-chart",
11604
12098
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
11605
12099
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
11606
12100
  "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",
@@ -11656,20 +12150,13 @@ const LABEL_TEXT_MODIFIER = {
11656
12150
  * @param {PieChartProps} props - Component props
11657
12151
  * @return {JSX.Element} The rendered chart component
11658
12152
  */
11659
- 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" }) => {
12153
+ 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" }) => {
11660
12154
  const legendInteractive = legend.interactive ?? false;
11661
12155
  const legendPosition = legend.position ?? "bottom";
11662
12156
  const providerTheme = useGlobalChartsTheme();
11663
12157
  const chartId = useChartId(providedChartId);
11664
- const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
11665
- const standaloneScopeClass = useStandaloneScopeClass();
11666
- const containerRef = useRef(null);
11667
12158
  const rootRef = useRef(null);
11668
12159
  const [labelRoles, setLabelRoles] = useState(null);
11669
- const onMouseLeave = useCallback(() => {
11670
- if (!withTooltips) return;
11671
- hideTooltip();
11672
- }, [withTooltips, hideTooltip]);
11673
12160
  const { getElementStyles, isSeriesVisible, isColorPaletteResolved } = useGlobalChartsContext();
11674
12161
  const { isValid, message } = validateData$1(data);
11675
12162
  useLayoutEffect(() => {
@@ -11713,13 +12200,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11713
12200
  metadata: chartMetadata
11714
12201
  });
11715
12202
  const prefersReducedMotion = usePrefersReducedMotion();
11716
- if (!isValid) return /* @__PURE__ */ jsx("div", {
11717
- className: clsx("pie-chart", pie_chart_module_default["pie-chart"], className),
11718
- children: /* @__PURE__ */ jsx("div", {
11719
- className: pie_chart_module_default["error-message"],
11720
- children: message
11721
- })
11722
- });
12203
+ const wipeMask = animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null;
11723
12204
  const padAngle = gapScale * (2 * Math.PI / data.length);
11724
12205
  const dataWithIndex = visibleData.map((d) => {
11725
12206
  const originalIndex = data.findIndex((item) => item.label === d.label);
@@ -11737,6 +12218,24 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11737
12218
  }).color;
11738
12219
  }
11739
12220
  };
12221
+ const arcs = pie({
12222
+ value: accessors.value,
12223
+ padAngle
12224
+ })(dataWithIndex);
12225
+ const navigationArcs = orderArcsForNavigation(arcs);
12226
+ const { chartRef, svgRef, selectedArc, getSegmentHandlers, getKeyboardTooltipAnchor, selectionOutputProps, chartProps } = usePieKeyboardNavigation({
12227
+ navigationArcs,
12228
+ withTooltips,
12229
+ tooltipOffsetX,
12230
+ tooltipOffsetY
12231
+ });
12232
+ if (!isValid) return /* @__PURE__ */ jsx("div", {
12233
+ className: clsx("pie-chart", pie_chart_module_default["pie-chart"], className),
12234
+ children: /* @__PURE__ */ jsx("div", {
12235
+ className: pie_chart_module_default["error-message"],
12236
+ children: message
12237
+ })
12238
+ });
11740
12239
  const legendElement = showLegend && /* @__PURE__ */ jsx(Legend, {
11741
12240
  orientation: legend.orientation ?? "horizontal",
11742
12241
  position: legendPosition,
@@ -11775,102 +12274,96 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11775
12274
  const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
11776
12275
  const maxCornerRadius = (outerRadius - innerRadius) / 2;
11777
12276
  const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
12277
+ const path = arc({
12278
+ innerRadius,
12279
+ outerRadius,
12280
+ cornerRadius
12281
+ });
12282
+ const svgLabelSmall = providerTheme.svgLabelSmall;
12283
+ const fontSize = resolveFontSize(svgLabelSmall?.fontSize) ?? 12;
12284
+ const renderLabel = (arcDatum, index) => {
12285
+ if (arcDatum.endAngle - arcDatum.startAngle < .25) return null;
12286
+ const [centroidX, centroidY] = path.centroid(arcDatum);
12287
+ const backgroundWidth = getStringWidth$1(arcDatum.data.label, {
12288
+ fontSize,
12289
+ fontFamily: svgLabelSmall?.fontFamily,
12290
+ fontWeight: svgLabelSmall?.fontWeight
12291
+ }) + 12;
12292
+ const backgroundHeight = fontSize + 12;
12293
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
12294
+ className: pie_chart_module_default["pie-chart__label-plate"],
12295
+ x: centroidX - backgroundWidth / 2,
12296
+ y: centroidY - backgroundHeight / 2,
12297
+ width: backgroundWidth,
12298
+ height: backgroundHeight,
12299
+ rx: 4,
12300
+ ry: 4,
12301
+ pointerEvents: "none"
12302
+ }), /* @__PURE__ */ jsx("text", {
12303
+ className: clsx(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(accessors.fill(arcDatum.data), labelRoles, "label-inverse")]),
12304
+ x: centroidX,
12305
+ y: centroidY,
12306
+ dy: ".33em",
12307
+ fontSize,
12308
+ textAnchor: "middle",
12309
+ pointerEvents: "none",
12310
+ children: arcDatum.data.label
12311
+ })] }, `label-${index}`);
12312
+ };
11778
12313
  return /* @__PURE__ */ jsxs(Center, {
11779
- ref: containerRef,
12314
+ ref: chartRef,
11780
12315
  className: pie_chart_module_default["pie-chart__plot"],
12316
+ ...chartProps,
12317
+ "aria-label": ariaLabel ?? __("Pie chart", "jetpack-charts"),
11781
12318
  children: [/* @__PURE__ */ jsxs("svg", {
12319
+ ref: svgRef,
11782
12320
  viewBox: `0 0 ${width} ${height}`,
11783
12321
  preserveAspectRatio: "xMidYMid meet",
11784
12322
  width,
11785
12323
  height,
11786
- children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(RadialWipeAnimation, {
11787
- id: `radial-wipe-${chartId}`,
11788
- radius: outerRadius,
11789
- innerRadius
11790
- }) }), /* @__PURE__ */ jsxs(Group, {
11791
- top: centerY,
11792
- left: centerX,
11793
- mask: animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null,
11794
- children: [allSegmentsHidden ? /* @__PURE__ */ jsx(SvgEmptyState, {
11795
- x: 0,
11796
- y: 0,
11797
- width,
11798
- height,
11799
- children: getAllHiddenMessage(legendInteractive, "segments")
11800
- }) : /* @__PURE__ */ jsx(Pie, {
11801
- data: dataWithIndex,
11802
- pieValue: accessors.value,
11803
- outerRadius,
11804
- innerRadius,
11805
- padAngle,
11806
- cornerRadius,
11807
- children: (pie) => {
11808
- return pie.arcs.map((arc, index) => {
11809
- const [centroidX, centroidY] = pie.path.centroid(arc);
11810
- const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
11811
- const handleMouseMove = (event) => {
11812
- if (!withTooltips) return;
11813
- const bounds = containerRef.current?.getBoundingClientRect();
11814
- if (!bounds) return;
11815
- showTooltip({
11816
- tooltipData: arc.data,
11817
- tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
11818
- tooltipTop: event.clientY - bounds.top + tooltipOffsetY
11819
- });
11820
- };
11821
- const fill = accessors.fill(arc.data);
11822
- const pathProps = {
11823
- d: pie.path(arc) || "",
11824
- fill,
11825
- "data-testid": "pie-segment"
11826
- };
11827
- const groupProps = {};
11828
- if (withTooltips) {
11829
- groupProps.onMouseMove = handleMouseMove;
11830
- groupProps.onMouseLeave = onMouseLeave;
11831
- }
11832
- const svgLabelSmall = providerTheme.svgLabelSmall;
11833
- const fontSize = resolveFontSize(svgLabelSmall?.fontSize) ?? 12;
11834
- const backgroundWidth = getStringWidth$1(arc.data.label, {
11835
- fontSize,
11836
- fontFamily: svgLabelSmall?.fontFamily,
11837
- fontWeight: svgLabelSmall?.fontWeight
11838
- }) + 12;
11839
- const backgroundHeight = fontSize + 12;
11840
- return /* @__PURE__ */ jsxs("g", {
11841
- ...groupProps,
11842
- children: [/* @__PURE__ */ jsx("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
11843
- className: pie_chart_module_default["pie-chart__label-plate"],
11844
- x: centroidX - backgroundWidth / 2,
11845
- y: centroidY - backgroundHeight / 2,
11846
- width: backgroundWidth,
11847
- height: backgroundHeight,
11848
- rx: 4,
11849
- ry: 4,
11850
- pointerEvents: "none"
11851
- }), /* @__PURE__ */ jsx("text", {
11852
- className: clsx(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
11853
- x: centroidX,
11854
- y: centroidY,
11855
- dy: ".33em",
11856
- fontSize,
11857
- textAnchor: "middle",
11858
- pointerEvents: "none",
11859
- children: arc.data.label
11860
- })] })]
11861
- }, `arc-${index}`);
11862
- });
11863
- }
11864
- }), !allSegmentsHidden && svgChildren]
11865
- })]
11866
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
11867
- top: tooltipTop || 0,
11868
- left: tooltipLeft || 0,
11869
- children: /* @__PURE__ */ jsx("div", {
11870
- className: standaloneScopeClass,
11871
- role: "tooltip",
11872
- children: renderTooltip({ tooltipData })
11873
- })
12324
+ children: [
12325
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(RadialWipeAnimation, {
12326
+ id: `radial-wipe-${chartId}`,
12327
+ radius: outerRadius,
12328
+ innerRadius
12329
+ }) }),
12330
+ /* @__PURE__ */ jsx(Group, {
12331
+ top: centerY,
12332
+ left: centerX,
12333
+ mask: wipeMask,
12334
+ children: allSegmentsHidden ? /* @__PURE__ */ jsx(SvgEmptyState, {
12335
+ x: 0,
12336
+ y: 0,
12337
+ width,
12338
+ height,
12339
+ children: getAllHiddenMessage(legendInteractive, "segments")
12340
+ }) : arcs.map((arcDatum, index) => /* @__PURE__ */ jsx("g", {
12341
+ ...getSegmentHandlers(arcDatum.data, navigationArcs.indexOf(arcDatum)),
12342
+ children: /* @__PURE__ */ jsx("path", {
12343
+ d: path(arcDatum) || "",
12344
+ fill: accessors.fill(arcDatum.data)
12345
+ })
12346
+ }, `arc-${index}`))
12347
+ }),
12348
+ selectedArc && /* @__PURE__ */ jsx(Group, {
12349
+ top: centerY,
12350
+ left: centerX,
12351
+ children: /* @__PURE__ */ jsx(SelectedSegmentRing, {
12352
+ chartId,
12353
+ d: path(selectedArc) || ""
12354
+ })
12355
+ }),
12356
+ !allSegmentsHidden && /* @__PURE__ */ jsxs(Group, {
12357
+ top: centerY,
12358
+ left: centerX,
12359
+ mask: wipeMask,
12360
+ children: [showLabels && arcs.map(renderLabel), svgChildren]
12361
+ })
12362
+ ]
12363
+ }), /* @__PURE__ */ jsx(PieSelectionOutput, {
12364
+ ...selectionOutputProps,
12365
+ keyboardTooltipAnchor: selectedArc && getKeyboardTooltipAnchor(centerX, centerY, path.centroid(selectedArc)),
12366
+ renderTooltip
11874
12367
  })]
11875
12368
  });
11876
12369
  }
@@ -11938,32 +12431,10 @@ const validateData = (data) => {
11938
12431
  message: ""
11939
12432
  };
11940
12433
  };
11941
- 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" }) => {
12434
+ 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" }) => {
11942
12435
  const legendInteractive = legend.interactive ?? false;
11943
12436
  const legendPosition = legend.position ?? "bottom";
11944
12437
  const chartId = useChartId(providedChartId);
11945
- const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
11946
- const standaloneScopeClass = useStandaloneScopeClass();
11947
- const containerRef = useRef(null);
11948
- const handleMouseMove = useCallback((event, arc) => {
11949
- const bounds = containerRef.current?.getBoundingClientRect();
11950
- if (!bounds) return;
11951
- showTooltip({
11952
- tooltipData: arc.data,
11953
- tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
11954
- tooltipTop: event.clientY - bounds.top + tooltipOffsetY
11955
- });
11956
- }, [
11957
- showTooltip,
11958
- tooltipOffsetX,
11959
- tooltipOffsetY
11960
- ]);
11961
- const handleMouseLeave = useCallback(() => {
11962
- hideTooltip();
11963
- }, [hideTooltip]);
11964
- const handleArcMouseMove = useCallback((arc) => (event) => {
11965
- handleMouseMove(event, arc);
11966
- }, [handleMouseMove]);
11967
12438
  const { isValid, message } = validateData(data);
11968
12439
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
11969
12440
  const dataWithPercentages = useDataWithPercentages(data);
@@ -11997,7 +12468,31 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
11997
12468
  metadata: chartMetadata
11998
12469
  });
11999
12470
  const prefersReducedMotion = usePrefersReducedMotion();
12471
+ const wipeMask = animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null;
12000
12472
  const effectiveWidth = propWidth || DEFAULT_WIDTH$1;
12473
+ const dataWithIndex = visibleData.map((d) => {
12474
+ const originalIndex = data.findIndex((item) => item.label === d.label);
12475
+ return {
12476
+ ...d,
12477
+ index: originalIndex >= 0 ? originalIndex : 0
12478
+ };
12479
+ });
12480
+ const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
12481
+ const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
12482
+ const arcs = pie({
12483
+ value: accessors.value,
12484
+ sort: accessors.sort,
12485
+ padAngle: PAD_ANGLE,
12486
+ startAngle,
12487
+ endAngle
12488
+ })(dataWithIndex);
12489
+ const navigationArcs = orderArcsForNavigation(arcs);
12490
+ const { chartRef, svgRef, selectedArc, getSegmentHandlers, getKeyboardTooltipAnchor, selectionOutputProps, chartProps } = usePieKeyboardNavigation({
12491
+ navigationArcs,
12492
+ withTooltips,
12493
+ tooltipOffsetX,
12494
+ tooltipOffsetY
12495
+ });
12001
12496
  if (!isValid) {
12002
12497
  const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
12003
12498
  const errorHeight = errorWidth / 2;
@@ -12016,15 +12511,6 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12016
12511
  })
12017
12512
  });
12018
12513
  }
12019
- const dataWithIndex = visibleData.map((d) => {
12020
- const originalIndex = data.findIndex((item) => item.label === d.label);
12021
- return {
12022
- ...d,
12023
- index: originalIndex >= 0 ? originalIndex : 0
12024
- };
12025
- });
12026
- const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
12027
- const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
12028
12514
  const legendElement = showLegend && /* @__PURE__ */ jsx(Legend, {
12029
12515
  orientation: legend.orientation ?? "horizontal",
12030
12516
  position: legendPosition,
@@ -12057,52 +12543,60 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12057
12543
  const height = width / 2;
12058
12544
  const radius = height;
12059
12545
  const innerRadius = radius * (1 - thickness);
12546
+ const path = arc({
12547
+ innerRadius,
12548
+ outerRadius: radius,
12549
+ cornerRadius: 3
12550
+ });
12060
12551
  return /* @__PURE__ */ jsxs(Center, {
12061
- ref: containerRef,
12552
+ ref: chartRef,
12062
12553
  className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
12554
+ ...chartProps,
12555
+ "aria-label": ariaLabel ?? __("Semi-circle chart", "jetpack-charts"),
12063
12556
  children: [/* @__PURE__ */ jsxs("svg", {
12557
+ ref: svgRef,
12064
12558
  width,
12065
12559
  height,
12066
12560
  viewBox: `0 0 ${width} ${height}`,
12067
- children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(RadialWipeAnimation, {
12068
- id: `radial-wipe-${chartId}`,
12069
- radius,
12070
- innerRadius,
12071
- startAngle: "-180deg",
12072
- wipePercentage: 50
12073
- }) }), /* @__PURE__ */ jsx(Group, {
12074
- top: height,
12075
- left: width / 2,
12076
- mask: animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null,
12077
- children: allSegmentsHidden ? /* @__PURE__ */ jsx(SvgEmptyState, {
12078
- x: 0,
12079
- y: -radius / 2,
12080
- width,
12081
- height,
12082
- children: getAllHiddenMessage(legendInteractive, "segments")
12083
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
12084
- /* @__PURE__ */ jsx(Pie, {
12085
- data: dataWithIndex,
12086
- pieValue: accessors.value,
12087
- outerRadius: radius,
12088
- innerRadius,
12089
- cornerRadius: 3,
12090
- padAngle: PAD_ANGLE,
12091
- startAngle,
12092
- endAngle,
12093
- pieSort: accessors.sort,
12094
- children: (pie) => {
12095
- return pie.arcs.map((arc) => /* @__PURE__ */ jsx("g", {
12096
- onMouseMove: withTooltips ? handleArcMouseMove(arc) : void 0,
12097
- onMouseLeave: withTooltips ? handleMouseLeave : void 0,
12098
- children: /* @__PURE__ */ jsx("path", {
12099
- d: pie.path(arc) || "",
12100
- fill: accessors.fill(arc.data)
12101
- })
12102
- }, arc.data.label));
12103
- }
12104
- }),
12105
- /* @__PURE__ */ jsxs(Group, { children: [/* @__PURE__ */ jsx(Text, {
12561
+ children: [
12562
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(RadialWipeAnimation, {
12563
+ id: `radial-wipe-${chartId}`,
12564
+ radius,
12565
+ innerRadius,
12566
+ startAngle: "-180deg",
12567
+ wipePercentage: 50
12568
+ }) }),
12569
+ /* @__PURE__ */ jsx(Group, {
12570
+ top: height,
12571
+ left: width / 2,
12572
+ mask: wipeMask,
12573
+ children: allSegmentsHidden ? /* @__PURE__ */ jsx(SvgEmptyState, {
12574
+ x: 0,
12575
+ y: -radius / 2,
12576
+ width,
12577
+ height,
12578
+ children: getAllHiddenMessage(legendInteractive, "segments")
12579
+ }) : arcs.map((arcDatum) => /* @__PURE__ */ jsx("g", {
12580
+ ...getSegmentHandlers(arcDatum.data, navigationArcs.indexOf(arcDatum)),
12581
+ children: /* @__PURE__ */ jsx("path", {
12582
+ d: path(arcDatum) || "",
12583
+ fill: accessors.fill(arcDatum.data)
12584
+ })
12585
+ }, arcDatum.data.label))
12586
+ }),
12587
+ selectedArc && /* @__PURE__ */ jsx(Group, {
12588
+ top: height,
12589
+ left: width / 2,
12590
+ children: /* @__PURE__ */ jsx(SelectedSegmentRing, {
12591
+ chartId,
12592
+ d: path(selectedArc) || ""
12593
+ })
12594
+ }),
12595
+ !allSegmentsHidden && /* @__PURE__ */ jsxs(Group, {
12596
+ top: height,
12597
+ left: width / 2,
12598
+ mask: wipeMask,
12599
+ children: [/* @__PURE__ */ jsxs(Group, { children: [/* @__PURE__ */ jsx(Text, {
12106
12600
  textAnchor: "middle",
12107
12601
  verticalAnchor: "start",
12108
12602
  y: -40,
@@ -12114,18 +12608,13 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12114
12608
  y: -20,
12115
12609
  className: pie_semi_circle_chart_module_default.note,
12116
12610
  children: note
12117
- })] }),
12118
- !allSegmentsHidden && svgChildren
12119
- ] })
12120
- })]
12121
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
12122
- top: tooltipTop || 0,
12123
- left: tooltipLeft || 0,
12124
- children: /* @__PURE__ */ jsx("div", {
12125
- className: standaloneScopeClass,
12126
- role: "tooltip",
12127
- children: renderTooltip({ tooltipData })
12128
- })
12611
+ })] }), svgChildren]
12612
+ })
12613
+ ]
12614
+ }), /* @__PURE__ */ jsx(PieSelectionOutput, {
12615
+ ...selectionOutputProps,
12616
+ keyboardTooltipAnchor: selectedArc && getKeyboardTooltipAnchor(width / 2, height, path.centroid(selectedArc)),
12617
+ renderTooltip
12129
12618
  })]
12130
12619
  });
12131
12620
  }