@automattic/charts 4.8.0 → 4.9.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 (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.cjs +624 -228
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +58 -8
  5. package/dist/index.d.cts +42 -8
  6. package/dist/index.d.ts +42 -8
  7. package/dist/index.js +628 -232
  8. package/dist/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/charts/area-chart/area-chart.tsx +5 -2
  11. package/src/charts/area-chart/test/area-chart.test.tsx +23 -14
  12. package/src/charts/area-chart/test/tooltip-resolution.test.tsx +2 -2
  13. package/src/charts/area-chart/types.ts +2 -0
  14. package/src/charts/bar-chart/bar-chart.tsx +5 -2
  15. package/src/charts/bar-chart/test/bar-chart.test.tsx +82 -63
  16. package/src/charts/bar-chart/types.ts +2 -0
  17. package/src/charts/bar-list-chart/test/bar-list-chart.test.tsx +3 -1
  18. package/src/charts/heatmap-chart/heatmap-chart.module.scss +10 -4
  19. package/src/charts/heatmap-chart/heatmap-chart.tsx +144 -37
  20. package/src/charts/heatmap-chart/private/column-groups.ts +79 -8
  21. package/src/charts/heatmap-chart/private/fit-compact-cells.ts +72 -0
  22. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +14 -2
  23. package/src/charts/heatmap-chart/private/index.ts +4 -2
  24. package/src/charts/heatmap-chart/test/column-groups.test.ts +41 -4
  25. package/src/charts/heatmap-chart/test/fit-compact-cells.test.ts +59 -0
  26. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +132 -1
  27. package/src/charts/heatmap-chart/types.ts +2 -0
  28. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +46 -4
  29. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +37 -1
  30. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +53 -0
  31. package/src/charts/leaderboard-chart/types.ts +6 -1
  32. package/src/charts/line-chart/line-chart.tsx +5 -2
  33. package/src/charts/line-chart/private/line-chart-annotation-label-popover.tsx +19 -9
  34. package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +1 -1
  35. package/src/charts/line-chart/test/line-chart.test.tsx +45 -35
  36. package/src/charts/line-chart/test/tooltip-resolution.test.tsx +2 -2
  37. package/src/charts/line-chart/types.ts +5 -3
  38. package/src/charts/pie-chart/pie-chart.tsx +149 -139
  39. package/src/charts/pie-chart/test/pie-chart.test.tsx +171 -0
  40. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +122 -120
  41. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +54 -0
  42. package/src/charts/private/chart-composition/use-chart-children.ts +11 -6
  43. package/src/charts/private/pie-keyboard-navigation/index.ts +4 -0
  44. package/src/charts/private/pie-keyboard-navigation/order-arcs-for-navigation.ts +10 -0
  45. package/src/charts/private/pie-keyboard-navigation/pie-keyboard-navigation.module.scss +16 -0
  46. package/src/charts/private/pie-keyboard-navigation/pie-selection-announcement.tsx +14 -0
  47. package/src/charts/private/pie-keyboard-navigation/pie-selection-output.tsx +82 -0
  48. package/src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx +44 -0
  49. package/src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx +47 -0
  50. package/src/charts/private/pie-keyboard-navigation/test/order-arcs-for-navigation.test.ts +34 -0
  51. package/src/charts/private/pie-keyboard-navigation/test/selected-segment-ring.test.ts +14 -0
  52. package/src/charts/private/pie-keyboard-navigation/use-pie-keyboard-navigation.ts +139 -0
  53. package/src/charts/private/x-zoom/test/x-zoom.test.tsx +3 -2
  54. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +20 -19
  55. package/src/components/tooltip/xy-chart-tooltip.tsx +3 -3
  56. package/src/types.ts +18 -2
package/dist/index.cjs CHANGED
@@ -3082,7 +3082,7 @@ const XYChartTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocu
3082
3082
  /** @deprecated Use `XYChartTooltip`. */
3083
3083
  const AccessibleTooltip = XYChartTooltip;
3084
3084
  const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
3085
- const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
3085
+ const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"application\"]") ?? chartRef.current, [chartRef]);
3086
3086
  const focusWithoutScrollIfNeeded = (0, react.useCallback)((element) => {
3087
3087
  if (preventTooltipScroll) element?.focus({ preventScroll: true });
3088
3088
  else element?.focus();
@@ -3297,11 +3297,13 @@ function useChartChildren(children, chartType) {
3297
3297
  }
3298
3298
  const displayName = child.type?.displayName;
3299
3299
  if (displayName === `${chartType}.SVG` || displayName === "Chart.SVG") {
3300
- if (child.props?.children) react.Children.forEach(child.props.children, (svgChild) => {
3300
+ const { children: svgChildren } = child.props ?? {};
3301
+ if (svgChildren) react.Children.forEach(svgChildren, (svgChild) => {
3301
3302
  svg.push(svgChild);
3302
3303
  });
3303
3304
  } else if (displayName === `${chartType}.HTML` || displayName === "Chart.HTML") {
3304
- if (child.props?.children) react.Children.forEach(child.props.children, (htmlChild) => {
3305
+ const { children: htmlChildren } = child.props ?? {};
3306
+ if (htmlChildren) react.Children.forEach(htmlChildren, (htmlChild) => {
3305
3307
  html.push(htmlChild);
3306
3308
  });
3307
3309
  } else if (child.type === _visx_group.Group) svg.push(child);
@@ -4123,6 +4125,7 @@ var line_chart_module_default = {
4123
4125
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
4124
4126
  "rise": "a8ccharts-inuQka-rise"
4125
4127
  };
4128
+ const isReact18 = react.version.startsWith("18.");
4126
4129
  const CloseIcon = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4127
4130
  width: "16",
4128
4131
  height: "16",
@@ -4165,7 +4168,7 @@ const LineChartAnnotationLabelWithPopover = ({ title, subtitle, renderLabel, ren
4165
4168
  className: line_chart_module_default["line-chart__annotation-label"],
4166
4169
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4167
4170
  ref: buttonRef,
4168
- popovertarget: popoverId,
4171
+ ...isReact18 ? { popovertarget: popoverId } : { popoverTarget: popoverId },
4169
4172
  className: line_chart_module_default["line-chart__annotation-label-trigger-button"],
4170
4173
  style: {
4171
4174
  width: `44px`,
@@ -4193,8 +4196,13 @@ const LineChartAnnotationLabelWithPopover = ({ title, subtitle, renderLabel, ren
4193
4196
  subtitle
4194
4197
  })
4195
4198
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4196
- popovertarget: popoverId,
4197
- popovertargetaction: "hide",
4199
+ ...isReact18 ? {
4200
+ popovertarget: popoverId,
4201
+ popovertargetaction: "hide"
4202
+ } : {
4203
+ popoverTarget: popoverId,
4204
+ popoverTargetAction: "hide"
4205
+ },
4198
4206
  className: line_chart_module_default["line-chart__annotation-label-popover-close-button"],
4199
4207
  "aria-label": (0, _wordpress_i18n.__)("Close", "jetpack-charts"),
4200
4208
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon, {})
@@ -4716,7 +4724,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
4716
4724
  ]);
4717
4725
  return null;
4718
4726
  };
4719
- const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, tooltipPlacement, tooltipStyle, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
4727
+ const LineChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, tooltipPlacement, tooltipStyle, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
4720
4728
  const legendInteractive = legend.interactive ?? false;
4721
4729
  const legendCollapseGroups = legend.collapseGroups ?? false;
4722
4730
  const legendComparisonItem = legend.comparisonItem ?? false;
@@ -4984,8 +4992,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4984
4992
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4985
4993
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4986
4994
  ref: chartRef,
4987
- role: "grid",
4988
- "aria-label": (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
4995
+ role: "application",
4996
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
4989
4997
  tabIndex: 0,
4990
4998
  onKeyDown: onChartKeyDown,
4991
4999
  onFocus: onChartFocus,
@@ -4993,6 +5001,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4993
5001
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4994
5002
  className: xy_plot_module_default["xy-plot"],
4995
5003
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5004
+ accessibilityLabel: "",
4996
5005
  theme,
4997
5006
  width,
4998
5007
  height: chartHeight,
@@ -5233,7 +5242,7 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
5233
5242
  };
5234
5243
  //#endregion
5235
5244
  //#region src/charts/area-chart/area-chart.tsx
5236
- const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
5245
+ const AreaChartInternal = (0, react.forwardRef)(({ data, ariaLabel, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
5237
5246
  const legendInteractive = legend.interactive ?? false;
5238
5247
  const legendShape = legend.shape ?? "rect";
5239
5248
  const legendPosition = legend.position ?? "bottom";
@@ -5504,8 +5513,8 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
5504
5513
  const chartHeight = contentHeight > 0 ? contentHeight : height;
5505
5514
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5506
5515
  ref: chartRef,
5507
- role: "grid",
5508
- "aria-label": (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
5516
+ role: "application",
5517
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
5509
5518
  tabIndex: 0,
5510
5519
  onKeyDown: onChartKeyDown,
5511
5520
  onFocus: onChartFocus,
@@ -5513,6 +5522,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
5513
5522
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5514
5523
  className: xy_plot_module_default["xy-plot"],
5515
5524
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5525
+ accessibilityLabel: "",
5516
5526
  theme,
5517
5527
  width,
5518
5528
  height: chartHeight,
@@ -6365,7 +6375,7 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ (0, react_jsx_runtime
6365
6375
  className: bar_chart_module_default["bar-chart__tooltip-row"],
6366
6376
  children: (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("%1$s: %2$s", "jetpack-charts"), label, value)
6367
6377
  });
6368
- const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, tooltipStyle, barClassName, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
6378
+ const BarChartInternal = ({ data, ariaLabel, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, tooltipStyle, barClassName, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
6369
6379
  if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
6370
6380
  const legendInteractive = legend.interactive ?? false;
6371
6381
  const legendCollapseGroups = legend.collapseGroups ?? false;
@@ -6673,9 +6683,9 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6673
6683
  children: ({ contentHeight }) => {
6674
6684
  const chartHeight = contentHeight > 0 ? contentHeight : height;
6675
6685
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6676
- role: "grid",
6686
+ role: "application",
6677
6687
  ref: chartRef,
6678
- "aria-label": (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
6688
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
6679
6689
  tabIndex: 0,
6680
6690
  onKeyDown: onChartKeyDown,
6681
6691
  onFocus: onChartFocus,
@@ -6683,6 +6693,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6683
6693
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6684
6694
  className: xy_plot_module_default["xy-plot"],
6685
6695
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
6696
+ accessibilityLabel: "",
6686
6697
  theme,
6687
6698
  width,
6688
6699
  height: chartHeight,
@@ -7688,6 +7699,7 @@ var heatmap_chart_module_default = {
7688
7699
  "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
7689
7700
  "heatmap-chart__legend-scale--bar": "a8ccharts-O3YMOW-heatmap-chart__legend-scale--bar",
7690
7701
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
7702
+ "heatmap-chart__legend-swatch--empty": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch--empty",
7691
7703
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
7692
7704
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
7693
7705
  "heatmap-chart--height-capped": "a8ccharts-O3YMOW-heatmap-chart--height-capped"
@@ -7785,8 +7797,9 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7785
7797
  const context = (0, react.useContext)(HeatmapContext);
7786
7798
  const { legend } = useGlobalChartsTheme();
7787
7799
  if (!context) return null;
7788
- const { fillVars } = context;
7800
+ const { extent, fillVars } = context;
7789
7801
  const labelStyle = legend.labelStyles;
7802
+ const showsEmptyCell = isEmptyValue(0, extent);
7790
7803
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7791
7804
  direction: "row",
7792
7805
  gap: "xs",
@@ -7798,12 +7811,12 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7798
7811
  style: labelStyle,
7799
7812
  children: lessLabel ?? (0, _wordpress_i18n.__)("Less", "jetpack-charts")
7800
7813
  }),
7801
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
7814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7802
7815
  direction: "row",
7803
7816
  gap: variant === "bar" ? void 0 : "xs",
7804
7817
  "aria-hidden": "true",
7805
7818
  className: variant === "bar" ? heatmap_chart_module_default["heatmap-chart__legend-scale--bar"] : void 0,
7806
- children: Array.from({ length: steps }, (_, index) => {
7819
+ children: [showsEmptyCell && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__legend-swatch"], heatmap_chart_module_default["heatmap-chart__legend-swatch--empty"]) }), Array.from({ length: steps }, (_, index) => {
7807
7820
  const intensity = steps <= 1 ? 1 : index / (steps - 1);
7808
7821
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7809
7822
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
@@ -7812,7 +7825,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7812
7825
  "--a8c-charts-heatmap-cell-intensity": intensity
7813
7826
  }
7814
7827
  }, index);
7815
- })
7828
+ })]
7816
7829
  }),
7817
7830
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
7818
7831
  variant: "body-sm",
@@ -7838,9 +7851,11 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7838
7851
  const ungrouped = () => ({
7839
7852
  columns: Array.from({ length: columnCount }, (_, index) => ({
7840
7853
  line: firstLine + index,
7854
+ band: 0,
7841
7855
  gapBefore: false
7842
7856
  })),
7843
- groups: []
7857
+ groups: [],
7858
+ bands: 1
7844
7859
  });
7845
7860
  if (!groups || groups.length === 0 || columnCount === 0) return ungrouped();
7846
7861
  const spans = groups.map((group) => group.span);
@@ -7856,11 +7871,13 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7856
7871
  if (groupIndex > 0) line += 1;
7857
7872
  laidOut.push({
7858
7873
  ...group,
7859
- line
7874
+ line,
7875
+ band: 0
7860
7876
  });
7861
7877
  for (let offset = 0; offset < group.span; offset++) {
7862
7878
  columns.push({
7863
7879
  line,
7880
+ band: 0,
7864
7881
  gapBefore: groupIndex > 0 && offset === 0,
7865
7882
  group: groupIndex
7866
7883
  });
@@ -7870,13 +7887,99 @@ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7870
7887
  while (columns.length < columnCount) {
7871
7888
  columns.push({
7872
7889
  line,
7890
+ band: 0,
7873
7891
  gapBefore: false
7874
7892
  });
7875
7893
  line += 1;
7876
7894
  }
7877
7895
  return {
7878
7896
  columns,
7879
- groups: laidOut
7897
+ groups: laidOut,
7898
+ bands: 1
7899
+ };
7900
+ };
7901
+ /**
7902
+ * Wraps the groups onto bands of equal group count, each band starting back at
7903
+ * the first data line so the groups line up in slots down the bands.
7904
+ *
7905
+ * @param layout - The unwrapped layout from `resolveColumnGroups`.
7906
+ * @param bands - Bands to aim for; the groups split as evenly as whole bands allow.
7907
+ * @return The wrapped layout, or null when there is nothing to wrap: one band, one group, or a column outside every group.
7908
+ */
7909
+ const wrapColumnGroups = (layout, bands) => {
7910
+ const { columns, groups } = layout;
7911
+ if (bands < 2 || groups.length < 2 || columns.some((column) => column.group === void 0)) return null;
7912
+ const perBand = Math.ceil(groups.length / bands);
7913
+ const slotSpans = Array(perBand).fill(0);
7914
+ groups.forEach((group, index) => {
7915
+ const slot = index % perBand;
7916
+ slotSpans[slot] = Math.max(slotSpans[slot], group.span);
7917
+ });
7918
+ const slotLines = [];
7919
+ let line = columns[0].line;
7920
+ slotSpans.forEach((span, slot) => {
7921
+ if (slot > 0) line += 1;
7922
+ slotLines.push(line);
7923
+ line += span;
7924
+ });
7925
+ const wrappedGroups = groups.map((group, index) => ({
7926
+ ...group,
7927
+ line: slotLines[index % perBand],
7928
+ band: Math.floor(index / perBand)
7929
+ }));
7930
+ return {
7931
+ columns: columns.map((column) => {
7932
+ const group = column.group;
7933
+ const offset = column.line - groups[group].line;
7934
+ return {
7935
+ ...column,
7936
+ line: wrappedGroups[group].line + offset,
7937
+ band: wrappedGroups[group].band,
7938
+ gapBefore: offset === 0 && group % perBand > 0
7939
+ };
7940
+ }),
7941
+ groups: wrappedGroups,
7942
+ bands: Math.ceil(groups.length / perBand),
7943
+ slotSpans
7944
+ };
7945
+ };
7946
+ //#endregion
7947
+ //#region src/charts/heatmap-chart/private/fit-compact-cells.ts
7948
+ /**
7949
+ * Largest whole-pixel square cell, and the band count giving it, for a compact grid in a box.
7950
+ *
7951
+ * @param input - The box, the grid's layout, and its fixed gaps and label sizes.
7952
+ * @return The fit; one band at `minCellSize` when no band count reaches it.
7953
+ */
7954
+ const fitCompactCells = (input) => {
7955
+ const { width, height, layout, rows, cellGap, groupGap, minCellSize } = input;
7956
+ const columns = layout.columns.length;
7957
+ if (columns === 0 || rows === 0) return {
7958
+ bands: 1,
7959
+ cellSize: minCellSize
7960
+ };
7961
+ const maxBands = Math.max(1, layout.groups.length);
7962
+ const labelHeights = (input.columnLabelHeight ?? 0) + (input.groupLabelHeight ?? 0);
7963
+ const bandTracks = (input.columnLabelHeight === null ? 0 : 1) + rows + (input.groupLabelHeight === null ? 0 : 1);
7964
+ let best = null;
7965
+ for (let target = 1; target <= maxBands; target++) {
7966
+ const wrapped = target > 1 ? wrapColumnGroups(layout, target) : null;
7967
+ const { bands } = wrapped ?? layout;
7968
+ const cellColumns = wrapped ? wrapped.slotSpans.reduce((sum, span) => sum + span, 0) : columns;
7969
+ const gapTracks = wrapped ? wrapped.slotSpans.length - 1 : layout.columns.filter((column) => column.gapBefore).length;
7970
+ const columnTracks = 1 + cellColumns + gapTracks;
7971
+ const byWidth = (width - input.rowLabelWidth - gapTracks * groupGap - (columnTracks - 1) * cellGap) / cellColumns;
7972
+ const rowTracks = bands * bandTracks + (bands - 1);
7973
+ const byHeight = (height - (bands * labelHeights + (bands - 1) * groupGap + (rowTracks - 1) * cellGap)) / (bands * rows);
7974
+ const cellSize = Math.floor(Math.min(byWidth, byHeight));
7975
+ if (cellSize >= minCellSize && (!best || cellSize > best.cellSize)) best = {
7976
+ bands,
7977
+ cellSize
7978
+ };
7979
+ }
7980
+ return best ?? {
7981
+ bands: 1,
7982
+ cellSize: minCellSize
7880
7983
  };
7881
7984
  };
7882
7985
  //#endregion
@@ -8020,12 +8123,26 @@ const CELL_VALUE_MODIFIER = {
8020
8123
  };
8021
8124
  const NO_ROW_LABELS = [];
8022
8125
  const TOOLTIP_BOX_STYLE = { zIndex: 3 };
8126
+ const largest = (root, selector, size) => Math.max(0, ...Array.from(root.querySelectorAll(selector), (el) => el[size]));
8127
+ const gridBox = (chart, grid) => {
8128
+ let height = chart.clientHeight;
8129
+ const rowGap = parseFloat(getComputedStyle(chart).rowGap) || 0;
8130
+ Array.from(chart.children).forEach((child) => {
8131
+ const { display, position } = getComputedStyle(child);
8132
+ if (child === grid || display === "none" || position === "absolute" || position === "fixed") return;
8133
+ height -= child.offsetHeight + rowGap;
8134
+ });
8135
+ return {
8136
+ width: chart.clientWidth,
8137
+ height
8138
+ };
8139
+ };
8023
8140
  const cellName = (info) => info.cellLabel || [
8024
8141
  info.groupLabel,
8025
8142
  info.columnLabel,
8026
8143
  info.rowLabel
8027
8144
  ].filter(Boolean).join(" ");
8028
- const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipStyle, children }) => {
8145
+ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, fitCells = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipStyle, children }) => {
8029
8146
  const chartId = useChartId(providedChartId);
8030
8147
  const tooltipBoxStyle = tooltipStyle ? {
8031
8148
  ...TOOLTIP_BOX_STYLE,
@@ -8039,8 +8156,9 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8039
8156
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
8040
8157
  const standaloneScopeClass = useStandaloneScopeClass();
8041
8158
  const containerRef = (0, react.useRef)(null);
8159
+ const chartRef = (0, react.useRef)(null);
8042
8160
  const [labelRoles, setLabelRoles] = (0, react.useState)(null);
8043
- const getTooltipOrigin = (0, react.useCallback)(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
8161
+ const getTooltipOrigin = (0, react.useCallback)(() => chartRef.current?.getBoundingClientRect() ?? null, []);
8044
8162
  const { color: primaryColorHex } = getElementStyles({
8045
8163
  index: 0,
8046
8164
  overrideColor: primaryColor
@@ -8082,6 +8200,49 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8082
8200
  const groupLayout = (0, react.useMemo)(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
8083
8201
  const { compactCellGap, compactCellSize, groupGap } = heatmapChartSettings;
8084
8202
  const drawValues = showValues ?? !compact;
8203
+ const hasColumnLabels = data.some((column) => Boolean(column.label));
8204
+ const hasGroups = groupLayout.groups.length > 0;
8205
+ const [fit, setFit] = (0, react.useState)(null);
8206
+ const hasSummary = data.some((column) => column.summary);
8207
+ const fitting = compact && fitCells && !hasSummary;
8208
+ if (fitCells && !fitting) warnOnce("heatmap:fitCells", "fitCells applies only in compact mode without summary columns, so the cells keep their size.");
8209
+ useIsomorphicLayoutEffect(() => {
8210
+ const chart = chartRef.current;
8211
+ const grid = containerRef.current;
8212
+ if (!fitting || !chart || !grid) {
8213
+ setFit(null);
8214
+ return;
8215
+ }
8216
+ const measure = () => {
8217
+ const next = fitCompactCells({
8218
+ ...gridBox(chart, grid),
8219
+ layout: groupLayout,
8220
+ rows,
8221
+ cellGap: compactCellGap,
8222
+ groupGap,
8223
+ rowLabelWidth: largest(grid, `.${heatmap_chart_module_default["heatmap-chart__row-label"]}`, "offsetWidth"),
8224
+ columnLabelHeight: hasColumnLabels ? largest(grid, `.${heatmap_chart_module_default["heatmap-chart__col-label"]}`, "offsetHeight") : null,
8225
+ groupLabelHeight: hasGroups ? largest(grid, `.${heatmap_chart_module_default["heatmap-chart__group-label"]}`, "offsetHeight") : null,
8226
+ minCellSize: compactCellSize
8227
+ });
8228
+ setFit((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
8229
+ };
8230
+ measure();
8231
+ if (typeof ResizeObserver === "undefined") return;
8232
+ const observer = new ResizeObserver(() => (0, react_dom.flushSync)(measure));
8233
+ observer.observe(chart);
8234
+ return () => observer.disconnect();
8235
+ }, [
8236
+ fitting,
8237
+ groupLayout,
8238
+ rows,
8239
+ hasColumnLabels,
8240
+ hasGroups,
8241
+ compactCellGap,
8242
+ compactCellSize,
8243
+ groupGap
8244
+ ]);
8245
+ const wrapped = (0, react.useMemo)(() => fit ? wrapColumnGroups(groupLayout, fit.bands) : null, [fit, groupLayout]);
8085
8246
  const buildTooltipData = (0, react.useCallback)((columnIndex, rowIndex) => {
8086
8247
  const cell = data[columnIndex]?.data[rowIndex];
8087
8248
  const group = groupLayout.columns[columnIndex]?.group;
@@ -8221,21 +8382,25 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8221
8382
  });
8222
8383
  const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
8223
8384
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
8224
- const hasColumnLabels = data.some((column) => Boolean(column.label));
8225
- const hasGroups = groupLayout.groups.length > 0;
8226
- const columnLine = (columnIndex) => groupLayout.columns[columnIndex].line;
8227
- const firstDataRow = hasColumnLabels ? 2 : 1;
8385
+ const placed = wrapped ?? groupLayout;
8386
+ const columnLine = (columnIndex) => placed.columns[columnIndex].line;
8387
+ const bandOf = (columnIndex) => placed.columns[columnIndex].band;
8388
+ const { bands } = placed;
8389
+ const bandRowCount = (hasColumnLabels ? 1 : 0) + rows + (hasGroups ? 1 : 0) + 1;
8390
+ const labelRowOf = (band) => 1 + band * bandRowCount;
8391
+ const dataRowOf = (band, rowIndex) => labelRowOf(band) + (hasColumnLabels ? 1 : 0) + rowIndex;
8228
8392
  const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
8229
8393
  const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
8230
- const columnTracks = data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ");
8394
+ 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(" ");
8395
+ const bandTracks = `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`;
8231
8396
  const gridStyle = {
8232
8397
  ...fillVars,
8233
8398
  gridTemplateColumns: `auto ${columnTracks}`,
8234
- gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
8399
+ gridTemplateRows: Array(bands).fill(bandTracks).join(` ${groupGap}px `)
8235
8400
  };
8236
8401
  if (compact) {
8237
8402
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
8238
- gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
8403
+ gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${fit?.cellSize ?? compactCellSize}px`;
8239
8404
  }
8240
8405
  const summaryGaps = (columnIndex) => {
8241
8406
  if (!data[columnIndex]?.summary) return {};
@@ -8255,6 +8420,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8255
8420
  legendChildren: [],
8256
8421
  trailingContent: nonLegendChildren,
8257
8422
  gap,
8423
+ rootRef: chartRef,
8258
8424
  className: (0, clsx.default)("heatmap-chart", heatmap_chart_module_default["heatmap-chart"], className, { [heatmap_chart_module_default["heatmap-chart--height-capped"]]: heightCapped }),
8259
8425
  style: {
8260
8426
  width: width || void 0,
@@ -8284,11 +8450,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8284
8450
  className: heatmap_chart_module_default["heatmap-chart__row"],
8285
8451
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
8286
8452
  gridColumn: 1,
8287
- gridRow: 1
8453
+ gridRow: labelRowOf(0)
8288
8454
  } }), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8289
8455
  style: {
8290
8456
  gridColumn: columnLine(columnIndex),
8291
- gridRow: 1
8457
+ gridRow: labelRowOf(bandOf(columnIndex))
8292
8458
  },
8293
8459
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
8294
8460
  [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
@@ -8299,24 +8465,23 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8299
8465
  }),
8300
8466
  Array.from({ length: rows }).map((_row, rowIndex) => {
8301
8467
  const labelVisible = !compact || rowIndex % 2 === 0;
8302
- const gridRow = firstDataRow + rowIndex;
8303
8468
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8304
8469
  role: "row",
8305
8470
  "aria-rowindex": rowIndex + 1,
8306
8471
  className: heatmap_chart_module_default["heatmap-chart__row"],
8307
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8472
+ children: [Array.from({ length: bands }).map((_band, band) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8308
8473
  "aria-hidden": "true",
8309
8474
  className: heatmap_chart_module_default["heatmap-chart__row-label"],
8310
8475
  style: {
8311
8476
  gridColumn: 1,
8312
- gridRow
8477
+ gridRow: dataRowOf(band, rowIndex)
8313
8478
  },
8314
8479
  children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
8315
- }), data.map((column, columnIndex) => {
8480
+ }, `row-label-${band}`)), data.map((column, columnIndex) => {
8316
8481
  const cell = column.data[rowIndex];
8317
8482
  const placement = {
8318
8483
  gridColumn: columnLine(columnIndex),
8319
- gridRow
8484
+ gridRow: dataRowOf(bandOf(columnIndex), rowIndex)
8320
8485
  };
8321
8486
  if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8322
8487
  "aria-hidden": "true",
@@ -8366,11 +8531,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8366
8531
  role: "row",
8367
8532
  "aria-hidden": "true",
8368
8533
  className: heatmap_chart_module_default["heatmap-chart__row"],
8369
- children: groupLayout.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8534
+ children: placed.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8370
8535
  className: heatmap_chart_module_default["heatmap-chart__group-label"],
8371
8536
  style: {
8372
8537
  gridColumn: `${group.line} / span ${group.span}`,
8373
- gridRow: firstDataRow + rows
8538
+ gridRow: dataRowOf(group.band, rows)
8374
8539
  },
8375
8540
  children: group.label
8376
8541
  }, `group-${groupIndex}`))
@@ -11605,6 +11770,7 @@ var leaderboard_chart_module_default = {
11605
11770
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
11606
11771
  "is-overlay": "a8ccharts-GovfoW-is-overlay",
11607
11772
  "label": "a8ccharts-GovfoW-label",
11773
+ "layeredRow": "a8ccharts-GovfoW-layeredRow",
11608
11774
  "leaderboardChart": "a8ccharts-GovfoW-leaderboardChart",
11609
11775
  "leaderboardChart__content": "a8ccharts-GovfoW-leaderboardChart__content",
11610
11776
  "leaderboardChart__content--fit": "a8ccharts-GovfoW-leaderboardChart__content--fit",
@@ -11613,6 +11779,7 @@ var leaderboard_chart_module_default = {
11613
11779
  "overlap": "a8ccharts-GovfoW-overlap",
11614
11780
  "overlayLabel": "a8ccharts-GovfoW-overlayLabel",
11615
11781
  "row": "a8ccharts-GovfoW-row",
11782
+ "rowButton": "a8ccharts-GovfoW-rowButton",
11616
11783
  "stretch": "a8ccharts-GovfoW-stretch",
11617
11784
  "valueContainer": "a8ccharts-GovfoW-valueContainer"
11618
11785
  };
@@ -11663,6 +11830,16 @@ const defaultDeltaFormatter = (value) => {
11663
11830
  const getBarWidth = (share) => `calc(${share}% - var(--a8c-charts-dimension-leaderboard-bar-hover-inset, 0px) * ${share} / 100)`;
11664
11831
  const hasPreviousValue = (entry) => entry.previousValue != null && entry.previousShare != null;
11665
11832
  const hasDeltaValue = (entry) => hasPreviousValue(entry) && entry.delta != null;
11833
+ /**
11834
+ * A layered row's cell that takes pointer events of its own (a value tooltip)
11835
+ * sits above the row button; pass its clicks on so they still drill down.
11836
+ *
11837
+ * @param event - The click on the row.
11838
+ */
11839
+ function forwardCellClickToRowButton(event) {
11840
+ if (event.target.closest("a, button")) return;
11841
+ event.currentTarget.querySelector(":scope > button")?.click();
11842
+ }
11666
11843
  const BarLabel = ({ label }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: typeof label === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
11667
11844
  className: leaderboard_chart_module_default.label,
11668
11845
  children: label
@@ -11895,6 +12072,26 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
11895
12072
  })
11896
12073
  ]
11897
12074
  })] });
12075
+ if (entry.onClick && entry.hasInteractiveLabel) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12076
+ "data-row-index": rowIndex,
12077
+ style: rowStyle,
12078
+ className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow, leaderboard_chart_module_default.layeredRow),
12079
+ onClick: forwardCellClickToRowButton,
12080
+ children: [
12081
+ rowCells,
12082
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
12083
+ type: "button",
12084
+ className: leaderboard_chart_module_default.rowButton,
12085
+ onClick: entry.onClick,
12086
+ "aria-label": entry.ariaLabel
12087
+ }),
12088
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
12089
+ className: leaderboard_chart_module_default.chevron,
12090
+ icon: _wordpress_icons.chevronRight,
12091
+ size: 24
12092
+ })
12093
+ ]
12094
+ }, entry.id);
11898
12095
  if (entry.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11899
12096
  type: "button",
11900
12097
  "data-row-index": rowIndex,
@@ -11928,6 +12125,210 @@ LeaderboardChartWithProvider.displayName = "LeaderboardChart";
11928
12125
  const LeaderboardChart = attachSubComponents(LeaderboardChartWithProvider, { Legend });
11929
12126
  const LeaderboardChartResponsive = attachSubComponents(withResponsive(LeaderboardChartWithProvider), { Legend });
11930
12127
  //#endregion
12128
+ //#region src/charts/private/pie-keyboard-navigation/order-arcs-for-navigation.ts
12129
+ /**
12130
+ * Orders arcs the way they appear: clockwise from 12 o'clock, which for a semi-circle is left to right.
12131
+ *
12132
+ * @param arcs - Arcs from visx's `pie` factory, in data order.
12133
+ * @return A copy of `arcs` sorted by angular midpoint.
12134
+ */
12135
+ const orderArcsForNavigation = (arcs) => [...arcs].sort((a, b) => a.startAngle + a.endAngle - (b.startAngle + b.endAngle));
12136
+ //#endregion
12137
+ //#region src/charts/private/pie-keyboard-navigation/pie-selection-announcement.tsx
12138
+ const PieSelectionAnnouncement = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.VisuallyHidden, {
12139
+ role: "status",
12140
+ "aria-atomic": "true",
12141
+ children: data && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelValueContent, { data })
12142
+ });
12143
+ //#endregion
12144
+ //#region src/charts/private/pie-keyboard-navigation/pie-tooltip.tsx
12145
+ const PieTooltip = ({ top, left, selectedIndex, tooltipRef, children }) => {
12146
+ const standaloneScopeClass = useStandaloneScopeClass();
12147
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12148
+ top,
12149
+ left,
12150
+ children: selectedIndex === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12151
+ className: standaloneScopeClass,
12152
+ role: "tooltip",
12153
+ children
12154
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12155
+ ref: tooltipRef,
12156
+ tabIndex: -1,
12157
+ role: "tooltip",
12158
+ "aria-atomic": "true",
12159
+ className: standaloneScopeClass,
12160
+ children
12161
+ }, `chart-tooltip-${selectedIndex}`)
12162
+ });
12163
+ };
12164
+ //#endregion
12165
+ //#region src/charts/private/pie-keyboard-navigation/pie-selection-output.tsx
12166
+ /**
12167
+ * Reports the selected segment: a focused tooltip for a keyboard selection, a pointer tooltip on hover, or a screen reader announcement without tooltips.
12168
+ *
12169
+ * @param {PieSelectionOutputProps} props - Component props
12170
+ * @return {JSX.Element} The announcement region and the tooltip, if any
12171
+ */
12172
+ const PieSelectionOutput = ({ withTooltips, selectedIndex, selectedData, keyboardTooltipAnchor, pointerTooltip, chartRef, svgRef, tooltipRef, renderTooltip }) => {
12173
+ const [svgOffset, setSvgOffset] = (0, react.useState)({
12174
+ top: 0,
12175
+ left: 0
12176
+ });
12177
+ const showKeyboardTooltip = withTooltips && selectedData && keyboardTooltipAnchor;
12178
+ (0, react.useLayoutEffect)(() => {
12179
+ const chartBounds = chartRef.current?.getBoundingClientRect();
12180
+ const svgBounds = svgRef.current?.getBoundingClientRect();
12181
+ if (!showKeyboardTooltip || !chartBounds || !svgBounds) return;
12182
+ const top = svgBounds.top - chartBounds.top;
12183
+ const left = svgBounds.left - chartBounds.left;
12184
+ setSvgOffset((current) => current.top === top && current.left === left ? current : {
12185
+ top,
12186
+ left
12187
+ });
12188
+ });
12189
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
12190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieSelectionAnnouncement, { data: withTooltips ? void 0 : selectedData }),
12191
+ showKeyboardTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieTooltip, {
12192
+ top: svgOffset.top + keyboardTooltipAnchor.top,
12193
+ left: svgOffset.left + keyboardTooltipAnchor.left,
12194
+ selectedIndex,
12195
+ tooltipRef,
12196
+ children: renderTooltip({ tooltipData: selectedData })
12197
+ }),
12198
+ withTooltips && !selectedData && pointerTooltip?.data && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieTooltip, {
12199
+ top: pointerTooltip.top,
12200
+ left: pointerTooltip.left,
12201
+ children: renderTooltip({ tooltipData: pointerTooltip.data })
12202
+ })
12203
+ ] });
12204
+ };
12205
+ //#endregion
12206
+ //#region src/charts/private/pie-keyboard-navigation/pie-keyboard-navigation.module.scss
12207
+ var pie_keyboard_navigation_module_default = {
12208
+ "selected-ring__contrast": "a8ccharts-V2HDKq-selected-ring__contrast",
12209
+ "selected-ring__focus": "a8ccharts-V2HDKq-selected-ring__focus"
12210
+ };
12211
+ //#endregion
12212
+ //#region src/charts/private/pie-keyboard-navigation/selected-segment-ring.tsx
12213
+ /**
12214
+ * Encodes `value` for an SVG id, losslessly so distinct chart ids never share a clip path.
12215
+ *
12216
+ * @param value - Any string.
12217
+ * @return `value` with each character outside `[A-Za-z0-9-]` written as `_<hex code point>_`.
12218
+ */
12219
+ const toSvgId = (value) => Array.from(value, (character) => /[A-Za-z0-9-]/.test(character) ? character : `_${character.codePointAt(0)?.toString(16)}_`).join("");
12220
+ /**
12221
+ * Marks the selected segment with a two-tone ring drawn inside its edge, so the SVG edge cannot cut it off.
12222
+ *
12223
+ * @param {SelectedSegmentRingProps} props - Component props
12224
+ * @return {JSX.Element} The ring
12225
+ */
12226
+ const SelectedSegmentRing = ({ chartId, d }) => {
12227
+ const clipId = `pie-selected-ring-clip-${toSvgId(chartId)}`;
12228
+ const clipPath = `url(#${clipId})`;
12229
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
12230
+ pointerEvents: "none",
12231
+ "aria-hidden": "true",
12232
+ children: [
12233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("clipPath", {
12234
+ id: clipId,
12235
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d })
12236
+ }),
12237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12238
+ d,
12239
+ clipPath,
12240
+ className: pie_keyboard_navigation_module_default["selected-ring__contrast"]
12241
+ }),
12242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12243
+ d,
12244
+ clipPath,
12245
+ className: pie_keyboard_navigation_module_default["selected-ring__focus"]
12246
+ })
12247
+ ]
12248
+ });
12249
+ };
12250
+ //#endregion
12251
+ //#region src/charts/private/pie-keyboard-navigation/use-pie-keyboard-navigation.ts
12252
+ const usePieKeyboardNavigation = ({ navigationArcs, withTooltips, tooltipOffsetX, tooltipOffsetY }) => {
12253
+ const chartRef = (0, react.useRef)(null);
12254
+ const svgRef = (0, react.useRef)(null);
12255
+ const [selectedIndex, setSelectedIndex] = (0, react.useState)(void 0);
12256
+ const [isNavigating, setIsNavigating] = (0, react.useState)(false);
12257
+ const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
12258
+ const { tooltipRef: focusTooltip, onChartFocus, onChartBlur, onChartKeyDown, onChartPointerMove } = useKeyboardNavigation({
12259
+ selectedIndex,
12260
+ setSelectedIndex,
12261
+ isNavigating,
12262
+ setIsNavigating,
12263
+ chartRef,
12264
+ totalPoints: navigationArcs.length,
12265
+ visibleSeriesKey: JSON.stringify(navigationArcs.map((arcDatum) => arcDatum.data.label))
12266
+ });
12267
+ const selectedArc = selectedIndex === void 0 ? void 0 : navigationArcs[selectedIndex];
12268
+ const keyboardTooltip = (0, react.useRef)(null);
12269
+ const tooltipLostFocus = (0, react.useRef)(false);
12270
+ const tooltipRef = (0, react.useCallback)((element) => {
12271
+ if (element) {
12272
+ keyboardTooltip.current = element;
12273
+ focusTooltip(element);
12274
+ } else if (keyboardTooltip.current?.ownerDocument.activeElement === keyboardTooltip.current) tooltipLostFocus.current = true;
12275
+ }, [focusTooltip]);
12276
+ (0, react.useLayoutEffect)(() => {
12277
+ if (!tooltipLostFocus.current) return;
12278
+ tooltipLostFocus.current = false;
12279
+ const { activeElement, body } = chartRef.current?.ownerDocument ?? document;
12280
+ if (activeElement === null || activeElement === body) chartRef.current?.focus();
12281
+ });
12282
+ const getSegmentHandlers = (data, navigationIndex) => ({
12283
+ onMouseMove: (event) => {
12284
+ onChartPointerMove(navigationIndex);
12285
+ if (!withTooltips) return;
12286
+ const bounds = chartRef.current?.getBoundingClientRect();
12287
+ if (!bounds) return;
12288
+ showTooltip({
12289
+ tooltipData: data,
12290
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12291
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12292
+ });
12293
+ },
12294
+ onMouseLeave: withTooltips ? hideTooltip : void 0
12295
+ });
12296
+ return {
12297
+ chartRef,
12298
+ svgRef,
12299
+ selectedArc,
12300
+ getSegmentHandlers,
12301
+ getKeyboardTooltipAnchor: (0, react.useCallback)((originX, originY, [x, y]) => withTooltips ? {
12302
+ left: originX + x + tooltipOffsetX,
12303
+ top: originY + y + tooltipOffsetY
12304
+ } : void 0, [
12305
+ withTooltips,
12306
+ tooltipOffsetX,
12307
+ tooltipOffsetY
12308
+ ]),
12309
+ selectionOutputProps: {
12310
+ withTooltips,
12311
+ selectedIndex,
12312
+ selectedData: selectedArc?.data,
12313
+ chartRef,
12314
+ svgRef,
12315
+ tooltipRef,
12316
+ pointerTooltip: tooltipOpen ? {
12317
+ data: tooltipData,
12318
+ left: tooltipLeft || 0,
12319
+ top: tooltipTop || 0
12320
+ } : void 0
12321
+ },
12322
+ chartProps: {
12323
+ role: "application",
12324
+ tabIndex: 0,
12325
+ onKeyDown: onChartKeyDown,
12326
+ onFocus: onChartFocus,
12327
+ onBlur: onChartBlur
12328
+ }
12329
+ };
12330
+ };
12331
+ //#endregion
11931
12332
  //#region src/charts/private/radial-wipe-animation/radial-wipe-animation.tsx
11932
12333
  /**
11933
12334
  * Renders a SVG mask that creates a radial wipe animation effect.
@@ -12023,20 +12424,13 @@ const LABEL_TEXT_MODIFIER = {
12023
12424
  * @param {PieChartProps} props - Component props
12024
12425
  * @return {JSX.Element} The rendered chart component
12025
12426
  */
12026
- const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false, className, showLegend = false, legend = {}, width: propWidth, height: propHeight, size, animation, thickness = 1, padding = 0, gapScale = 0, cornerScale = 0, showLabels = true, legendValueDisplay = "percentage", children = null, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieTooltip, gap = "md" }) => {
12427
+ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false, className, showLegend = false, legend = {}, width: propWidth, height: propHeight, size, animation, thickness = 1, padding = 0, gapScale = 0, cornerScale = 0, showLabels = true, legendValueDisplay = "percentage", children = null, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieTooltip, ariaLabel, gap = "md" }) => {
12027
12428
  const legendInteractive = legend.interactive ?? false;
12028
12429
  const legendPosition = legend.position ?? "bottom";
12029
12430
  const providerTheme = useGlobalChartsTheme();
12030
12431
  const chartId = useChartId(providedChartId);
12031
- const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
12032
- const standaloneScopeClass = useStandaloneScopeClass();
12033
- const containerRef = (0, react.useRef)(null);
12034
12432
  const rootRef = (0, react.useRef)(null);
12035
12433
  const [labelRoles, setLabelRoles] = (0, react.useState)(null);
12036
- const onMouseLeave = (0, react.useCallback)(() => {
12037
- if (!withTooltips) return;
12038
- hideTooltip();
12039
- }, [withTooltips, hideTooltip]);
12040
12434
  const { getElementStyles, isSeriesVisible, isColorPaletteResolved } = useGlobalChartsContext();
12041
12435
  const { isValid, message } = validateData$1(data);
12042
12436
  (0, react.useLayoutEffect)(() => {
@@ -12080,13 +12474,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
12080
12474
  metadata: chartMetadata
12081
12475
  });
12082
12476
  const prefersReducedMotion = usePrefersReducedMotion();
12083
- if (!isValid) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12084
- className: (0, clsx.default)("pie-chart", pie_chart_module_default["pie-chart"], className),
12085
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12086
- className: pie_chart_module_default["error-message"],
12087
- children: message
12088
- })
12089
- });
12477
+ const wipeMask = animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null;
12090
12478
  const padAngle = gapScale * (2 * Math.PI / data.length);
12091
12479
  const dataWithIndex = visibleData.map((d) => {
12092
12480
  const originalIndex = data.findIndex((item) => item.label === d.label);
@@ -12104,6 +12492,24 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
12104
12492
  }).color;
12105
12493
  }
12106
12494
  };
12495
+ const arcs = (0, _visx_shape.pie)({
12496
+ value: accessors.value,
12497
+ padAngle
12498
+ })(dataWithIndex);
12499
+ const navigationArcs = orderArcsForNavigation(arcs);
12500
+ const { chartRef, svgRef, selectedArc, getSegmentHandlers, getKeyboardTooltipAnchor, selectionOutputProps, chartProps } = usePieKeyboardNavigation({
12501
+ navigationArcs,
12502
+ withTooltips,
12503
+ tooltipOffsetX,
12504
+ tooltipOffsetY
12505
+ });
12506
+ if (!isValid) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12507
+ className: (0, clsx.default)("pie-chart", pie_chart_module_default["pie-chart"], className),
12508
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12509
+ className: pie_chart_module_default["error-message"],
12510
+ children: message
12511
+ })
12512
+ });
12107
12513
  const legendElement = showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Legend, {
12108
12514
  orientation: legend.orientation ?? "horizontal",
12109
12515
  position: legendPosition,
@@ -12142,102 +12548,96 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
12142
12548
  const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
12143
12549
  const maxCornerRadius = (outerRadius - innerRadius) / 2;
12144
12550
  const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
12551
+ const path = (0, _visx_shape.arc)({
12552
+ innerRadius,
12553
+ outerRadius,
12554
+ cornerRadius
12555
+ });
12556
+ const svgLabelSmall = providerTheme.svgLabelSmall;
12557
+ const fontSize = resolveFontSize(svgLabelSmall?.fontSize) ?? 12;
12558
+ const renderLabel = (arcDatum, index) => {
12559
+ if (arcDatum.endAngle - arcDatum.startAngle < .25) return null;
12560
+ const [centroidX, centroidY] = path.centroid(arcDatum);
12561
+ const backgroundWidth = (0, _visx_text.getStringWidth)(arcDatum.data.label, {
12562
+ fontSize,
12563
+ fontFamily: svgLabelSmall?.fontFamily,
12564
+ fontWeight: svgLabelSmall?.fontWeight
12565
+ }) + 12;
12566
+ const backgroundHeight = fontSize + 12;
12567
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
12568
+ className: pie_chart_module_default["pie-chart__label-plate"],
12569
+ x: centroidX - backgroundWidth / 2,
12570
+ y: centroidY - backgroundHeight / 2,
12571
+ width: backgroundWidth,
12572
+ height: backgroundHeight,
12573
+ rx: 4,
12574
+ ry: 4,
12575
+ pointerEvents: "none"
12576
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
12577
+ className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(accessors.fill(arcDatum.data), labelRoles, "label-inverse")]),
12578
+ x: centroidX,
12579
+ y: centroidY,
12580
+ dy: ".33em",
12581
+ fontSize,
12582
+ textAnchor: "middle",
12583
+ pointerEvents: "none",
12584
+ children: arcDatum.data.label
12585
+ })] }, `label-${index}`);
12586
+ };
12145
12587
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
12146
- ref: containerRef,
12588
+ ref: chartRef,
12147
12589
  className: pie_chart_module_default["pie-chart__plot"],
12590
+ ...chartProps,
12591
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Pie chart", "jetpack-charts"),
12148
12592
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
12593
+ ref: svgRef,
12149
12594
  viewBox: `0 0 ${width} ${height}`,
12150
12595
  preserveAspectRatio: "xMidYMid meet",
12151
12596
  width,
12152
12597
  height,
12153
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12154
- id: `radial-wipe-${chartId}`,
12155
- radius: outerRadius,
12156
- innerRadius
12157
- }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, {
12158
- top: centerY,
12159
- left: centerX,
12160
- mask: animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null,
12161
- children: [allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12162
- x: 0,
12163
- y: 0,
12164
- width,
12165
- height,
12166
- children: getAllHiddenMessage(legendInteractive, "segments")
12167
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_shape.Pie, {
12168
- data: dataWithIndex,
12169
- pieValue: accessors.value,
12170
- outerRadius,
12171
- innerRadius,
12172
- padAngle,
12173
- cornerRadius,
12174
- children: (pie) => {
12175
- return pie.arcs.map((arc, index) => {
12176
- const [centroidX, centroidY] = pie.path.centroid(arc);
12177
- const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
12178
- const handleMouseMove = (event) => {
12179
- if (!withTooltips) return;
12180
- const bounds = containerRef.current?.getBoundingClientRect();
12181
- if (!bounds) return;
12182
- showTooltip({
12183
- tooltipData: arc.data,
12184
- tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12185
- tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12186
- });
12187
- };
12188
- const fill = accessors.fill(arc.data);
12189
- const pathProps = {
12190
- d: pie.path(arc) || "",
12191
- fill,
12192
- "data-testid": "pie-segment"
12193
- };
12194
- const groupProps = {};
12195
- if (withTooltips) {
12196
- groupProps.onMouseMove = handleMouseMove;
12197
- groupProps.onMouseLeave = onMouseLeave;
12198
- }
12199
- const svgLabelSmall = providerTheme.svgLabelSmall;
12200
- const fontSize = resolveFontSize(svgLabelSmall?.fontSize) ?? 12;
12201
- const backgroundWidth = (0, _visx_text.getStringWidth)(arc.data.label, {
12202
- fontSize,
12203
- fontFamily: svgLabelSmall?.fontFamily,
12204
- fontWeight: svgLabelSmall?.fontWeight
12205
- }) + 12;
12206
- const backgroundHeight = fontSize + 12;
12207
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
12208
- ...groupProps,
12209
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
12210
- className: pie_chart_module_default["pie-chart__label-plate"],
12211
- x: centroidX - backgroundWidth / 2,
12212
- y: centroidY - backgroundHeight / 2,
12213
- width: backgroundWidth,
12214
- height: backgroundHeight,
12215
- rx: 4,
12216
- ry: 4,
12217
- pointerEvents: "none"
12218
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
12219
- className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
12220
- x: centroidX,
12221
- y: centroidY,
12222
- dy: ".33em",
12223
- fontSize,
12224
- textAnchor: "middle",
12225
- pointerEvents: "none",
12226
- children: arc.data.label
12227
- })] })]
12228
- }, `arc-${index}`);
12229
- });
12230
- }
12231
- }), !allSegmentsHidden && svgChildren]
12232
- })]
12233
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12234
- top: tooltipTop || 0,
12235
- left: tooltipLeft || 0,
12236
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12237
- className: standaloneScopeClass,
12238
- role: "tooltip",
12239
- children: renderTooltip({ tooltipData })
12240
- })
12598
+ children: [
12599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12600
+ id: `radial-wipe-${chartId}`,
12601
+ radius: outerRadius,
12602
+ innerRadius
12603
+ }) }),
12604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12605
+ top: centerY,
12606
+ left: centerX,
12607
+ mask: wipeMask,
12608
+ children: allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12609
+ x: 0,
12610
+ y: 0,
12611
+ width,
12612
+ height,
12613
+ children: getAllHiddenMessage(legendInteractive, "segments")
12614
+ }) : arcs.map((arcDatum, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
12615
+ ...getSegmentHandlers(arcDatum.data, navigationArcs.indexOf(arcDatum)),
12616
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12617
+ d: path(arcDatum) || "",
12618
+ fill: accessors.fill(arcDatum.data)
12619
+ })
12620
+ }, `arc-${index}`))
12621
+ }),
12622
+ selectedArc && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12623
+ top: centerY,
12624
+ left: centerX,
12625
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedSegmentRing, {
12626
+ chartId,
12627
+ d: path(selectedArc) || ""
12628
+ })
12629
+ }),
12630
+ !allSegmentsHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, {
12631
+ top: centerY,
12632
+ left: centerX,
12633
+ mask: wipeMask,
12634
+ children: [showLabels && arcs.map(renderLabel), svgChildren]
12635
+ })
12636
+ ]
12637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieSelectionOutput, {
12638
+ ...selectionOutputProps,
12639
+ keyboardTooltipAnchor: selectedArc && getKeyboardTooltipAnchor(centerX, centerY, path.centroid(selectedArc)),
12640
+ renderTooltip
12241
12641
  })]
12242
12642
  });
12243
12643
  }
@@ -12305,32 +12705,10 @@ const validateData = (data) => {
12305
12705
  message: ""
12306
12706
  };
12307
12707
  };
12308
- const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, thickness = .4, clockwise = true, withTooltips = false, showLegend = false, legend = {}, legendValueDisplay = "percentage", label, animation, note, className, children, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieSemiCircleTooltip, gap = "md" }) => {
12708
+ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, thickness = .4, clockwise = true, withTooltips = false, showLegend = false, legend = {}, legendValueDisplay = "percentage", label, animation, note, className, children, tooltipOffsetX = 0, tooltipOffsetY = -15, renderTooltip = renderDefaultPieSemiCircleTooltip, ariaLabel, gap = "md" }) => {
12309
12709
  const legendInteractive = legend.interactive ?? false;
12310
12710
  const legendPosition = legend.position ?? "bottom";
12311
12711
  const chartId = useChartId(providedChartId);
12312
- const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
12313
- const standaloneScopeClass = useStandaloneScopeClass();
12314
- const containerRef = (0, react.useRef)(null);
12315
- const handleMouseMove = (0, react.useCallback)((event, arc) => {
12316
- const bounds = containerRef.current?.getBoundingClientRect();
12317
- if (!bounds) return;
12318
- showTooltip({
12319
- tooltipData: arc.data,
12320
- tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
12321
- tooltipTop: event.clientY - bounds.top + tooltipOffsetY
12322
- });
12323
- }, [
12324
- showTooltip,
12325
- tooltipOffsetX,
12326
- tooltipOffsetY
12327
- ]);
12328
- const handleMouseLeave = (0, react.useCallback)(() => {
12329
- hideTooltip();
12330
- }, [hideTooltip]);
12331
- const handleArcMouseMove = (0, react.useCallback)((arc) => (event) => {
12332
- handleMouseMove(event, arc);
12333
- }, [handleMouseMove]);
12334
12712
  const { isValid, message } = validateData(data);
12335
12713
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
12336
12714
  const dataWithPercentages = useDataWithPercentages(data);
@@ -12364,7 +12742,31 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12364
12742
  metadata: chartMetadata
12365
12743
  });
12366
12744
  const prefersReducedMotion = usePrefersReducedMotion();
12745
+ const wipeMask = animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null;
12367
12746
  const effectiveWidth = propWidth || DEFAULT_WIDTH$1;
12747
+ const dataWithIndex = visibleData.map((d) => {
12748
+ const originalIndex = data.findIndex((item) => item.label === d.label);
12749
+ return {
12750
+ ...d,
12751
+ index: originalIndex >= 0 ? originalIndex : 0
12752
+ };
12753
+ });
12754
+ const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
12755
+ const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
12756
+ const arcs = (0, _visx_shape.pie)({
12757
+ value: accessors.value,
12758
+ sort: accessors.sort,
12759
+ padAngle: PAD_ANGLE,
12760
+ startAngle,
12761
+ endAngle
12762
+ })(dataWithIndex);
12763
+ const navigationArcs = orderArcsForNavigation(arcs);
12764
+ const { chartRef, svgRef, selectedArc, getSegmentHandlers, getKeyboardTooltipAnchor, selectionOutputProps, chartProps } = usePieKeyboardNavigation({
12765
+ navigationArcs,
12766
+ withTooltips,
12767
+ tooltipOffsetX,
12768
+ tooltipOffsetY
12769
+ });
12368
12770
  if (!isValid) {
12369
12771
  const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
12370
12772
  const errorHeight = errorWidth / 2;
@@ -12383,15 +12785,6 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12383
12785
  })
12384
12786
  });
12385
12787
  }
12386
- const dataWithIndex = visibleData.map((d) => {
12387
- const originalIndex = data.findIndex((item) => item.label === d.label);
12388
- return {
12389
- ...d,
12390
- index: originalIndex >= 0 ? originalIndex : 0
12391
- };
12392
- });
12393
- const startAngle = clockwise ? -Math.PI / 2 : Math.PI / 2;
12394
- const endAngle = clockwise ? Math.PI / 2 : -Math.PI / 2;
12395
12788
  const legendElement = showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Legend, {
12396
12789
  orientation: legend.orientation ?? "horizontal",
12397
12790
  position: legendPosition,
@@ -12424,52 +12817,60 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12424
12817
  const height = width / 2;
12425
12818
  const radius = height;
12426
12819
  const innerRadius = radius * (1 - thickness);
12820
+ const path = (0, _visx_shape.arc)({
12821
+ innerRadius,
12822
+ outerRadius: radius,
12823
+ cornerRadius: 3
12824
+ });
12427
12825
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
12428
- ref: containerRef,
12826
+ ref: chartRef,
12429
12827
  className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
12828
+ ...chartProps,
12829
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Semi-circle chart", "jetpack-charts"),
12430
12830
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
12831
+ ref: svgRef,
12431
12832
  width,
12432
12833
  height,
12433
12834
  viewBox: `0 0 ${width} ${height}`,
12434
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12435
- id: `radial-wipe-${chartId}`,
12436
- radius,
12437
- innerRadius,
12438
- startAngle: "-180deg",
12439
- wipePercentage: 50
12440
- }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12441
- top: height,
12442
- left: width / 2,
12443
- mask: animation && !prefersReducedMotion ? `url(#radial-wipe-${chartId})` : null,
12444
- children: allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12445
- x: 0,
12446
- y: -radius / 2,
12447
- width,
12448
- height,
12449
- children: getAllHiddenMessage(legendInteractive, "segments")
12450
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
12451
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_shape.Pie, {
12452
- data: dataWithIndex,
12453
- pieValue: accessors.value,
12454
- outerRadius: radius,
12455
- innerRadius,
12456
- cornerRadius: 3,
12457
- padAngle: PAD_ANGLE,
12458
- startAngle,
12459
- endAngle,
12460
- pieSort: accessors.sort,
12461
- children: (pie) => {
12462
- return pie.arcs.map((arc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
12463
- onMouseMove: withTooltips ? handleArcMouseMove(arc) : void 0,
12464
- onMouseLeave: withTooltips ? handleMouseLeave : void 0,
12465
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12466
- d: pie.path(arc) || "",
12467
- fill: accessors.fill(arc.data)
12468
- })
12469
- }, arc.data.label));
12470
- }
12471
- }),
12472
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_text.Text, {
12835
+ children: [
12836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RadialWipeAnimation, {
12837
+ id: `radial-wipe-${chartId}`,
12838
+ radius,
12839
+ innerRadius,
12840
+ startAngle: "-180deg",
12841
+ wipePercentage: 50
12842
+ }) }),
12843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12844
+ top: height,
12845
+ left: width / 2,
12846
+ mask: wipeMask,
12847
+ children: allSegmentsHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
12848
+ x: 0,
12849
+ y: -radius / 2,
12850
+ width,
12851
+ height,
12852
+ children: getAllHiddenMessage(legendInteractive, "segments")
12853
+ }) : arcs.map((arcDatum) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
12854
+ ...getSegmentHandlers(arcDatum.data, navigationArcs.indexOf(arcDatum)),
12855
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
12856
+ d: path(arcDatum) || "",
12857
+ fill: accessors.fill(arcDatum.data)
12858
+ })
12859
+ }, arcDatum.data.label))
12860
+ }),
12861
+ selectedArc && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
12862
+ top: height,
12863
+ left: width / 2,
12864
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectedSegmentRing, {
12865
+ chartId,
12866
+ d: path(selectedArc) || ""
12867
+ })
12868
+ }),
12869
+ !allSegmentsHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, {
12870
+ top: height,
12871
+ left: width / 2,
12872
+ mask: wipeMask,
12873
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_group.Group, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_text.Text, {
12473
12874
  textAnchor: "middle",
12474
12875
  verticalAnchor: "start",
12475
12876
  y: -40,
@@ -12481,18 +12882,13 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
12481
12882
  y: -20,
12482
12883
  className: pie_semi_circle_chart_module_default.note,
12483
12884
  children: note
12484
- })] }),
12485
- !allSegmentsHidden && svgChildren
12486
- ] })
12487
- })]
12488
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
12489
- top: tooltipTop || 0,
12490
- left: tooltipLeft || 0,
12491
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12492
- className: standaloneScopeClass,
12493
- role: "tooltip",
12494
- children: renderTooltip({ tooltipData })
12495
- })
12885
+ })] }), svgChildren]
12886
+ })
12887
+ ]
12888
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PieSelectionOutput, {
12889
+ ...selectionOutputProps,
12890
+ keyboardTooltipAnchor: selectedArc && getKeyboardTooltipAnchor(width / 2, height, path.centroid(selectedArc)),
12891
+ renderTooltip
12496
12892
  })]
12497
12893
  });
12498
12894
  }