@automattic/charts 4.1.1 → 4.3.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 (97) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/TOKENS.md +1 -1
  3. package/dist/index.cjs +1335 -348
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +30 -1
  6. package/dist/index.d.cts +129 -8
  7. package/dist/index.d.ts +129 -8
  8. package/dist/index.js +1336 -351
  9. package/dist/index.js.map +1 -1
  10. package/package.json +8 -8
  11. package/src/charts/area-chart/area-chart.tsx +42 -10
  12. package/src/charts/area-chart/private/overlays.tsx +1 -2
  13. package/src/charts/area-chart/private/validate-data.ts +2 -3
  14. package/src/charts/area-chart/test/area-chart.test.tsx +72 -1
  15. package/src/charts/bar-chart/bar-chart.tsx +137 -70
  16. package/src/charts/bar-chart/index.ts +1 -1
  17. package/src/charts/bar-chart/private/band-highlight.tsx +87 -0
  18. package/src/charts/bar-chart/private/band-scale.ts +13 -0
  19. package/src/charts/bar-chart/private/band-tooltip.tsx +138 -0
  20. package/src/charts/bar-chart/private/comparison-bars.tsx +5 -10
  21. package/src/charts/bar-chart/private/index.ts +3 -0
  22. package/src/charts/bar-chart/private/test/band-highlight.test.tsx +135 -0
  23. package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
  24. package/src/charts/bar-chart/private/test/value-domain.test.ts +100 -0
  25. package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
  26. package/src/charts/bar-chart/private/use-bar-chart-options.ts +13 -40
  27. package/src/charts/bar-chart/private/value-domain.ts +90 -0
  28. package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
  29. package/src/charts/bar-chart/test/bar-chart.test.tsx +1058 -19
  30. package/src/charts/bar-chart/types.ts +39 -0
  31. package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -3
  32. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
  33. package/src/charts/conversion-funnel-chart/types.ts +4 -2
  34. package/src/charts/geo-chart/types.ts +4 -2
  35. package/src/charts/heatmap-chart/build-calendar-data.ts +5 -11
  36. package/src/charts/heatmap-chart/build-month-calendar-data.ts +124 -0
  37. package/src/charts/heatmap-chart/heatmap-chart.module.scss +32 -0
  38. package/src/charts/heatmap-chart/heatmap-chart.tsx +191 -114
  39. package/src/charts/heatmap-chart/index.ts +6 -0
  40. package/src/charts/heatmap-chart/private/civil-day.ts +26 -0
  41. package/src/charts/heatmap-chart/private/column-groups.ts +71 -0
  42. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +19 -3
  43. package/src/charts/heatmap-chart/private/index.ts +2 -0
  44. package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
  45. package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +36 -0
  46. package/src/charts/heatmap-chart/test/build-month-calendar-data.test.ts +194 -0
  47. package/src/charts/heatmap-chart/test/column-groups.test.ts +83 -0
  48. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +428 -2
  49. package/src/charts/heatmap-chart/test/keyboard-navigation.test.ts +25 -0
  50. package/src/charts/heatmap-chart/test/use-month-calendar-heatmap-data.test.tsx +90 -0
  51. package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
  52. package/src/charts/heatmap-chart/types.ts +59 -3
  53. package/src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts +34 -0
  54. package/src/charts/leaderboard-chart/types.ts +13 -14
  55. package/src/charts/line-chart/line-chart.tsx +149 -36
  56. package/src/charts/line-chart/private/index.ts +1 -0
  57. package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
  58. package/src/charts/line-chart/private/line-chart-glyph.tsx +17 -3
  59. package/src/charts/line-chart/private/nearest-pointer-events.tsx +155 -0
  60. package/src/charts/line-chart/test/line-chart.test.tsx +604 -9
  61. package/src/charts/line-chart/types.ts +4 -3
  62. package/src/charts/private/readings.ts +34 -0
  63. package/src/charts/private/test/readings.test.ts +29 -0
  64. package/src/charts/private/test/whole-number-ticks.test.tsx +57 -0
  65. package/src/charts/private/time-axis-options.ts +1 -1
  66. package/src/charts/private/whole-number-ticks.tsx +79 -0
  67. package/src/charts/private/with-responsive/with-responsive.tsx +2 -2
  68. package/src/charts/private/x-zoom/test/x-zoom.test.tsx +1 -3
  69. package/src/charts/private/x-zoom/x-zoom.tsx +1 -2
  70. package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +124 -0
  71. package/src/components/legend/hooks/use-chart-legend-items.ts +60 -2
  72. package/src/components/legend/private/base-legend.tsx +37 -22
  73. package/src/components/legend/test/legend.test.tsx +84 -0
  74. package/src/components/legend/types.ts +2 -0
  75. package/src/components/legend/utils/index.ts +0 -1
  76. package/src/components/tooltip/accessible-tooltip.tsx +128 -41
  77. package/src/components/tooltip/base-tooltip.module.scss +6 -1
  78. package/src/components/tooltip/private/bounded-tooltip.tsx +12 -6
  79. package/src/components/tooltip/test/accessible-tooltip.test.tsx +128 -0
  80. package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
  81. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +7 -0
  82. package/src/components/tooltip/xy-chart-tooltip.tsx +9 -10
  83. package/src/hooks/test/use-chart-margin.test.tsx +35 -0
  84. package/src/hooks/test/use-element-size.test.tsx +13 -13
  85. package/src/hooks/use-chart-margin.tsx +7 -2
  86. package/src/hooks/use-xychart-theme.ts +1 -1
  87. package/src/index.ts +7 -1
  88. package/src/providers/chart-context/global-charts-provider.tsx +1 -1
  89. package/src/providers/chart-context/test/chart-context.test.tsx +4 -4
  90. package/src/providers/chart-context/themes.ts +1 -0
  91. package/src/styles/test/chart-scope.test.ts +1 -1
  92. package/src/types.ts +14 -13
  93. package/src/utils/format-metric-value.ts +4 -4
  94. package/src/utils/get-edge-tick-widths.ts +1 -1
  95. package/src/utils/get-styles.ts +1 -1
  96. package/src/visx/types.ts +7 -10
  97. package/src/components/legend/utils/label-transform-factory.ts +0 -24
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ let _visx_gradient = require("@visx/gradient");
46
46
  let _visx_curve = require("@visx/curve");
47
47
  let _visx_responsive = require("@visx/responsive");
48
48
  let _visx_annotation = require("@visx/annotation");
49
+ let _visx_vendor_d3_array = require("@visx/vendor/d3-array");
49
50
  let _visx_pattern = require("@visx/pattern");
50
51
  let react_google_charts = require("react-google-charts");
51
52
  let dompurify = require("dompurify");
@@ -983,7 +984,8 @@ const defaultTheme = {
983
984
  },
984
985
  heatmapChart: {
985
986
  compactCellGap: 2,
986
- compactCellSize: 11
987
+ compactCellSize: 11,
988
+ groupGap: 24
987
989
  }
988
990
  };
989
991
  //#endregion
@@ -1075,7 +1077,7 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
1075
1077
  return getChartColor(index, colorCache);
1076
1078
  }, [colorCache, groupToColorMap]);
1077
1079
  const getElementStyles = (0, react.useCallback)(({ data, index, overrideColor, legendShape }) => {
1078
- const isSeriesData = data && typeof data === "object" && "data" in data && "options" in data;
1080
+ const isSeriesData = data && typeof data === "object" && "data" in data;
1079
1081
  const isPointPercentageData = data && typeof data === "object" && "value" in data && typeof data.value === "number" && !("data" in data);
1080
1082
  return {
1081
1083
  color: resolveColor({
@@ -1456,8 +1458,9 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1456
1458
  const allDataPoints = data.flatMap((series) => series.data);
1457
1459
  if (horizontal) return allDataPoints.map((d) => d.label || d.date?.getTime());
1458
1460
  if (options.axis?.y?.tickValues?.length) return options.axis.y.tickValues;
1459
- const minY = Math.min(...allDataPoints.map((d) => d.value));
1460
- const maxY = Math.max(...allDataPoints.map((d) => d.value));
1461
+ const values = allDataPoints.map((d) => d.value).filter((value) => typeof value === "number" && Number.isFinite(value));
1462
+ const minY = values.length ? Math.min(...values) : 0;
1463
+ const maxY = values.length ? Math.max(...values) : 1;
1461
1464
  const yScale = (0, _visx_scale.createScale)({
1462
1465
  ...options.yScale,
1463
1466
  domain: options.yScale?.domain ?? [minY, maxY],
@@ -1792,23 +1795,6 @@ function valueOrIdentityString(_) {
1792
1795
  return String(valueOrIdentity(_));
1793
1796
  }
1794
1797
  //#endregion
1795
- //#region src/components/legend/utils/label-transform-factory.ts
1796
- /**
1797
- * Returns a function which takes a Datum and index as input, and returns a formatted label object.
1798
- * @param {object} root0 - The object to return the value of.
1799
- * @param {AnyD3Scale} root0.scale - The scale to use.
1800
- * @param {LabelFormatter<ScaleInput<Scale>>} root0.labelFormat - The label format to use.
1801
- * @return {ItemTransformer<ScaleInput<Scale>, ReturnType<Scale>>} The label transform factory.
1802
- */
1803
- function labelTransformFactory({ scale, labelFormat }) {
1804
- return (d, i) => ({
1805
- datum: d,
1806
- index: i,
1807
- text: `${labelFormat(d, i)}`,
1808
- value: scale(d)
1809
- });
1810
- }
1811
- //#endregion
1812
1798
  //#region src/components/legend/private/base-legend.module.scss
1813
1799
  var base_legend_module_default = {
1814
1800
  "legend": "a8ccharts-04TogW-legend",
@@ -1850,7 +1836,7 @@ const getLegendItemAriaLabel = (text, value, visible, interactive) => {
1850
1836
  if (visible) return;
1851
1837
  return (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n._x)("%s: hidden", "hidden non-interactive legend item", "jetpack-charts"), accessibleText);
1852
1838
  };
1853
- const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "horizontal", alignment = "center", shape = "rect", fill = valueOrIdentityString, size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform = labelTransformFactory, itemStyles, itemClassName, labelStyles, labelClassName, shapeStyles, render, interactive = false, chartId }, ref) => {
1839
+ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "horizontal", alignment = "center", shape = "rect", fill = valueOrIdentityString, size = valueOrIdentityString, labelFormat = valueOrIdentity, labelTransform, itemStyles, itemClassName, labelStyles, labelClassName, shapeStyles, render, interactive = false, chartId }, ref) => {
1854
1840
  const { margin: itemMargin = "0", flexDirection: itemDirection = "row" } = itemStyles ?? {};
1855
1841
  const { justifyContent: labelJustifyContent = "flex-start", flex: labelFlex = "0 0 auto", margin: labelMargin = "0 4px", maxWidth, textOverflow = "wrap" } = labelStyles ?? {};
1856
1842
  const { width: shapeWidth = 16, height: shapeHeight = 16, margin: shapeMargin = "2px 4px 2px 0" } = shapeStyles ?? {};
@@ -1858,11 +1844,17 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
1858
1844
  const context = (0, react.useContext)(GlobalChartsContext);
1859
1845
  const chartInstanceContext = (0, react.useContext)(ChartInstanceContext);
1860
1846
  const standaloneScopeClass = useStandaloneScopeClass();
1847
+ const domain = items.map((item) => item.label);
1861
1848
  const legendScale = (0, _visx_scale.scaleOrdinal)({
1862
- domain: items.map((item) => item.label),
1849
+ domain,
1863
1850
  range: items.map((item) => item.color)
1864
1851
  });
1865
- const domain = legendScale.domain();
1852
+ const defaultLabelTransform = (0, react.useCallback)(({ labelFormat: format }) => (datum, index) => ({
1853
+ datum,
1854
+ index,
1855
+ text: String(format(datum, index)),
1856
+ value: items[index].color
1857
+ }), [items]);
1866
1858
  const getShapeStyle = (0, react.useCallback)(({ index }) => items[index]?.shapeStyle, [items]);
1867
1859
  const handleLegendClick = (0, react.useCallback)((seriesLabels) => {
1868
1860
  if (interactive && chartId && context) {
@@ -1883,24 +1875,26 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
1883
1875
  chartInstanceContext,
1884
1876
  context
1885
1877
  ]);
1886
- const createClickHandler = (0, react.useCallback)((seriesLabels) => {
1887
- if (!interactive) return;
1878
+ const createClickHandler = (0, react.useCallback)((seriesLabels, itemInteractive) => {
1879
+ if (!itemInteractive) return;
1888
1880
  return () => handleLegendClick(seriesLabels);
1889
- }, [interactive, handleLegendClick]);
1890
- const createKeyDownHandler = (0, react.useCallback)((seriesLabels) => {
1891
- if (!interactive) return;
1881
+ }, [handleLegendClick]);
1882
+ const createKeyDownHandler = (0, react.useCallback)((seriesLabels, itemInteractive) => {
1883
+ if (!itemInteractive) return;
1892
1884
  return (event) => {
1893
1885
  if (event.key === "Enter" || event.key === " ") {
1894
1886
  event.preventDefault();
1895
1887
  handleLegendClick(seriesLabels);
1896
1888
  }
1897
1889
  };
1898
- }, [interactive, handleLegendClick]);
1890
+ }, [handleLegendClick]);
1899
1891
  const flexAlignment = ALIGNMENT_TO_FLEX[alignment] ?? "center";
1892
+ const staticItemRole = interactive ? void 0 : "listitem";
1900
1893
  return render ? render(items) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_legend.LegendOrdinal, {
1901
1894
  scale: legendScale,
1895
+ domain,
1902
1896
  labelFormat,
1903
- labelTransform,
1897
+ labelTransform: labelTransform ?? defaultLabelTransform,
1904
1898
  children: (labels) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
1905
1899
  ref,
1906
1900
  direction: orientation === "vertical" ? "column" : "row",
@@ -1914,19 +1908,20 @@ const BaseLegend = (0, react.forwardRef)(({ items, className, orientation = "hor
1914
1908
  children: labels.map((label, i) => {
1915
1909
  const matchedItem = items[i];
1916
1910
  const seriesLabels = matchedItem?.seriesLabels?.length ? matchedItem.seriesLabels : [label.text];
1917
- const visible = isSeriesVisible(seriesLabels[0]);
1918
- const handleClick = createClickHandler(seriesLabels);
1919
- const handleKeyDown = createKeyDownHandler(seriesLabels);
1911
+ const visible = matchedItem?.interactive === false || isSeriesVisible(seriesLabels[0]);
1912
+ const itemInteractive = interactive && matchedItem?.interactive !== false;
1913
+ const handleClick = createClickHandler(seriesLabels, itemInteractive);
1914
+ const handleKeyDown = createKeyDownHandler(seriesLabels, itemInteractive);
1920
1915
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_legend.LegendItem, {
1921
- className: (0, clsx.default)("visx-legend-item", base_legend_module_default["legend-item"], interactive && base_legend_module_default["legend-item--interactive"], !visible && base_legend_module_default["legend-item--inactive"], itemClassName),
1916
+ className: (0, clsx.default)("visx-legend-item", base_legend_module_default["legend-item"], itemInteractive && base_legend_module_default["legend-item--interactive"], !visible && base_legend_module_default["legend-item--inactive"], itemClassName),
1922
1917
  margin: itemMargin,
1923
1918
  flexDirection: orientation === "vertical" && alignment === "end" ? "row-reverse" : itemDirection,
1924
1919
  onClick: handleClick,
1925
1920
  onKeyDown: handleKeyDown,
1926
- role: interactive ? "button" : "listitem",
1927
- tabIndex: interactive ? 0 : void 0,
1928
- "aria-pressed": interactive ? visible : void 0,
1929
- "aria-label": getLegendItemAriaLabel(label.text, matchedItem?.value, visible, interactive),
1921
+ role: itemInteractive ? "button" : staticItemRole,
1922
+ tabIndex: itemInteractive ? 0 : void 0,
1923
+ "aria-pressed": itemInteractive ? visible : void 0,
1924
+ "aria-label": getLegendItemAriaLabel(label.text, matchedItem?.value, visible, itemInteractive),
1930
1925
  children: [items[i]?.renderGlyph ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1931
1926
  width: items[i]?.glyphSize * 2,
1932
1927
  height: items[i]?.glyphSize * 2,
@@ -2128,6 +2123,32 @@ function processSeriesData(seriesData, getElementStyles, showValues, withGlyph,
2128
2123
  });
2129
2124
  }
2130
2125
  /**
2126
+ * Builds the static item that names the comparison overlay, styled like its first series.
2127
+ * Skipped when a comparison series already has an item of its own, so nothing is listed twice.
2128
+ * @param seriesData - The series data to search for a comparison series
2129
+ * @param items - The legend items already built from the series
2130
+ * @param label - The item label
2131
+ * @param getElementStyles - Function to get element styles
2132
+ * @param legendShape - The shape type for legend items (string literal or React component)
2133
+ * @return The legend item, or null when it would add nothing
2134
+ */
2135
+ function buildComparisonLegendItem(seriesData, items, label, getElementStyles, legendShape) {
2136
+ const itemLabels = new Set(items.map((item) => item.label));
2137
+ const index = seriesData.findIndex((series) => series.options?.type === "comparison" && !itemLabels.has(series.label));
2138
+ if (index === -1) return null;
2139
+ const { color, shapeStyles } = getElementStyles({
2140
+ data: seriesData[index],
2141
+ index,
2142
+ legendShape
2143
+ });
2144
+ return {
2145
+ label,
2146
+ color,
2147
+ shapeStyle: shapeStyles,
2148
+ interactive: false
2149
+ };
2150
+ }
2151
+ /**
2131
2152
  * Processes point data into legend items
2132
2153
  * @param pointData - The point data to process
2133
2154
  * @param getElementStyles - Function to get element styles
@@ -2163,11 +2184,16 @@ function processPointData(pointData, getElementStyles, showValues, legendValueDi
2163
2184
  * @return Array of legend items ready for display
2164
2185
  */
2165
2186
  function useChartLegendItems(data, options = {}, legendShape) {
2166
- const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, renderGlyph } = options;
2187
+ const { showValues = false, legendValueDisplay = "percentage", withGlyph = false, glyphSize = 8, collapseGroups = false, comparisonItem = false, renderGlyph } = options;
2167
2188
  const { getElementStyles } = useGlobalChartsContext();
2168
2189
  return (0, react.useMemo)(() => {
2169
2190
  if (!data || !Array.isArray(data) || data.length === 0) return [];
2170
- if ("data" in data[0]) return processSeriesData(data, getElementStyles, showValues, withGlyph, glyphSize, collapseGroups, renderGlyph, legendShape);
2191
+ if ("data" in data[0]) {
2192
+ const seriesData = data;
2193
+ const items = processSeriesData(seriesData, getElementStyles, showValues, withGlyph, glyphSize, collapseGroups, renderGlyph, legendShape);
2194
+ const comparison = comparisonItem ? buildComparisonLegendItem(seriesData, items, typeof comparisonItem === "string" ? comparisonItem : (0, _wordpress_i18n.__)("Comparison period", "jetpack-charts"), getElementStyles, legendShape) : null;
2195
+ return comparison ? [...items, comparison] : items;
2196
+ }
2171
2197
  return processPointData(data, getElementStyles, showValues, legendValueDisplay, withGlyph, glyphSize, renderGlyph, legendShape);
2172
2198
  }, [
2173
2199
  data,
@@ -2177,13 +2203,17 @@ function useChartLegendItems(data, options = {}, legendShape) {
2177
2203
  withGlyph,
2178
2204
  glyphSize,
2179
2205
  collapseGroups,
2206
+ comparisonItem,
2180
2207
  renderGlyph,
2181
2208
  legendShape
2182
2209
  ]);
2183
2210
  }
2184
2211
  //#endregion
2185
2212
  //#region src/components/tooltip/base-tooltip.module.scss
2186
- var base_tooltip_module_default = { "tooltip": "a8ccharts--zY0xG-tooltip" };
2213
+ var base_tooltip_module_default = {
2214
+ "surface": "a8ccharts--zY0xG-surface",
2215
+ "tooltip": "a8ccharts--zY0xG-tooltip"
2216
+ };
2187
2217
  //#endregion
2188
2218
  //#region src/components/tooltip/base-tooltip.tsx
2189
2219
  const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
@@ -2233,7 +2263,8 @@ const findClippingAncestor = (wrapper) => {
2233
2263
  /**
2234
2264
  * Where the box goes, in wrapper coordinates. Automatic placement flips to
2235
2265
  * the side that clips less, then clamps inside `bounds`; below-axis placement
2236
- * is centered on the anchor and clamped horizontally.
2266
+ * is centered on the anchor and clamped horizontally; beside placement preserves
2267
+ * the anchor's y and only flips horizontally before bounds clamping.
2237
2268
  *
2238
2269
  * The flip measures against the visible part of the wrapper, its intersection
2239
2270
  * with `bounds`. Only the clamp is bound by the page: a box that fits inside the
@@ -2247,7 +2278,7 @@ const findClippingAncestor = (wrapper) => {
2247
2278
  * @param params.offsetTop - Gap between the anchor and the box, vertically.
2248
2279
  * @param params.box - Rendered size of the box.
2249
2280
  * @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
2250
- * @param params.placement - Fixed below-axis placement or automatic flipping.
2281
+ * @param params.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2251
2282
  * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2252
2283
  * @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
2253
2284
  */
@@ -2271,7 +2302,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2271
2302
  const upY = top - offsetTop - box.height;
2272
2303
  const downOverflow = downY + box.height - fit.bottom;
2273
2304
  const upOverflow = fit.top - upY;
2274
- let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2305
+ let y = top;
2306
+ if (placement !== "beside") y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2275
2307
  x = clamp(x, bounds.left, bounds.right, box.width);
2276
2308
  y = clamp(y, bounds.top, bounds.bottom, box.height);
2277
2309
  return {
@@ -2287,8 +2319,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2287
2319
  * alone would let the box spill into an `overflow: hidden` card and be cut off.
2288
2320
  *
2289
2321
  * Re-measures on every render, so a box whose content changes width between
2290
- * two hovers is placed for its current size. See `getBoundedPosition` for the
2291
- * below-axis exception.
2322
+ * two hovers is placed for its current size.
2292
2323
  *
2293
2324
  * @param props - visx `Tooltip` props.
2294
2325
  * @param props.left - Anchor x, in wrapper coordinates.
@@ -2298,7 +2329,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2298
2329
  * @param props.style - Box styles; visx's defaults unless `unstyled`.
2299
2330
  * @param props.unstyled - Skip `style` and leave the box bare.
2300
2331
  * @param props.children - Box content.
2301
- * @param props.placement - Fixed below-axis placement or automatic flipping.
2332
+ * @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2302
2333
  * @return The tooltip box.
2303
2334
  */
2304
2335
  const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
@@ -2401,7 +2432,6 @@ const crosshairPaintProps = (props = {}) => {
2401
2432
  };
2402
2433
  const DEFAULT_GLYPH_RADIUS = 4;
2403
2434
  const FALLBACK_COLOR = "#222";
2404
- const DEFAULT_TOOLTIP_Z_INDEX = 3;
2405
2435
  const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
2406
2436
  "scroll",
2407
2437
  "debounce",
@@ -2429,7 +2459,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_js
2429
2459
  seriesKey: key,
2430
2460
  ...props
2431
2461
  }, key);
2432
- const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, renderGlyph = defaultRenderGlyph$1, glyphStyle, snapTooltipToDatumX = false, snapTooltipToDatumY = false, showVerticalCrosshair = false, showHorizontalCrosshair = false, showDatumGlyph = false, showSeriesGlyphs = false, verticalCrosshairStyle, horizontalCrosshairStyle, detectBounds = true, tooltipPlacement = "auto", zIndex = DEFAULT_TOOLTIP_Z_INDEX, style, ...rest }) => {
2462
+ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, renderGlyph = defaultRenderGlyph$1, glyphStyle, snapTooltipToDatumX = false, snapTooltipToDatumY = false, showVerticalCrosshair = false, showHorizontalCrosshair = false, showDatumGlyph = false, showSeriesGlyphs = false, verticalCrosshairStyle, horizontalCrosshairStyle, detectBounds = true, tooltipPlacement = "auto", tooltipAnchorTop, zIndex = 3, style, ...rest }) => {
2433
2463
  const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2434
2464
  const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2435
2465
  const tooltipContent = renderTooltip ? renderTooltip({
@@ -2484,7 +2514,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2484
2514
  const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2485
2515
  const marginTop = margin?.top ?? 0;
2486
2516
  const marginLeft = margin?.left ?? 0;
2487
- const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : _visx_tooltip.Tooltip;
2517
+ const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : _visx_tooltip.Tooltip;
2488
2518
  const boxStyle = {
2489
2519
  ..._visx_tooltip.defaultStyles,
2490
2520
  zIndex,
@@ -2521,11 +2551,11 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2521
2551
  ]
2522
2552
  }), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2523
2553
  left: tooltipLeft,
2524
- top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
2554
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
2525
2555
  style: boxStyle,
2526
2556
  applyPositionStyle: true,
2527
2557
  ...tooltipProps,
2528
- ...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
2558
+ ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2529
2559
  children: tooltipContent
2530
2560
  }), container)] });
2531
2561
  };
@@ -2591,11 +2621,32 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2591
2621
  return flattened;
2592
2622
  }, [series, mode]);
2593
2623
  const hasKeyboardSelection = (0, react.useRef)(false);
2624
+ const closeTooltipNow = (0, react.useCallback)(() => {
2625
+ tooltipContext?.updateTooltip({
2626
+ tooltipOpen: false,
2627
+ tooltipLeft: void 0,
2628
+ tooltipTop: void 0,
2629
+ tooltipData: void 0
2630
+ });
2631
+ }, []);
2632
+ const chartId = (0, react.useContext)(ChartInstanceContext)?.chartId;
2633
+ const { getHiddenSeries } = useGlobalChartsContext();
2634
+ const hiddenSeriesKey = chartId ? JSON.stringify([...getHiddenSeries(chartId)].sort()) : void 0;
2635
+ const lastHiddenSeriesKey = (0, react.useRef)(hiddenSeriesKey);
2636
+ (0, react.useEffect)(() => {
2637
+ const changed = lastHiddenSeriesKey.current !== hiddenSeriesKey;
2638
+ lastHiddenSeriesKey.current = hiddenSeriesKey;
2639
+ if (changed && selectedIndex === void 0) closeTooltipNow();
2640
+ }, [
2641
+ hiddenSeriesKey,
2642
+ selectedIndex,
2643
+ closeTooltipNow
2644
+ ]);
2594
2645
  (0, react.useEffect)(() => {
2595
2646
  if (selectedIndex === void 0) {
2596
2647
  if (hasKeyboardSelection.current) {
2597
2648
  hasKeyboardSelection.current = false;
2598
- tooltipContext?.hideTooltip();
2649
+ closeTooltipNow();
2599
2650
  }
2600
2651
  return;
2601
2652
  }
@@ -2616,7 +2667,7 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2616
2667
  tooltipContext?.showTooltip({
2617
2668
  datum: tooltipItem.datum,
2618
2669
  key: tooltipItem.seriesLabel,
2619
- index: tooltipItem.seriesIndex
2670
+ index: tooltipItem.dataPointIndex
2620
2671
  });
2621
2672
  }
2622
2673
  }
@@ -2664,14 +2715,43 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2664
2715
  renderTooltip: focusableRenderTooltip
2665
2716
  });
2666
2717
  };
2667
- const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
2718
+ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
2719
+ const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
2720
+ const focusWithoutScrollIfNeeded = (0, react.useCallback)((element) => {
2721
+ if (preventTooltipScroll) element?.focus({ preventScroll: true });
2722
+ else element?.focus();
2723
+ }, [preventTooltipScroll]);
2724
+ const tooltipRef = (0, react.useCallback)((element) => {
2725
+ if (element && selectedIndex !== void 0) focusWithoutScrollIfNeeded(element);
2726
+ }, [focusWithoutScrollIfNeeded, selectedIndex]);
2727
+ const previousTotalPoints = (0, react.useRef)(totalPoints);
2728
+ const previousVisibleSeriesKey = (0, react.useRef)(visibleSeriesKey);
2729
+ (0, react.useEffect)(() => {
2730
+ const countChanged = previousTotalPoints.current !== totalPoints;
2731
+ const seriesChanged = previousVisibleSeriesKey.current !== visibleSeriesKey;
2732
+ previousTotalPoints.current = totalPoints;
2733
+ previousVisibleSeriesKey.current = visibleSeriesKey;
2734
+ if (!countChanged && !seriesChanged || selectedIndex === void 0) return;
2735
+ const { activeElement } = document;
2736
+ const focusIsInChart = activeElement !== null && !!getChartRoot()?.contains(activeElement);
2737
+ if (totalPoints === 0 || !focusIsInChart || !countChanged) {
2738
+ setSelectedIndex(void 0);
2739
+ setIsNavigating(false);
2740
+ if (focusIsInChart) focusWithoutScrollIfNeeded(getChartRoot());
2741
+ return;
2742
+ }
2743
+ setSelectedIndex(Math.min(selectedIndex, totalPoints - 1));
2744
+ }, [
2745
+ selectedIndex,
2746
+ totalPoints,
2747
+ visibleSeriesKey,
2748
+ setSelectedIndex,
2749
+ setIsNavigating,
2750
+ getChartRoot,
2751
+ focusWithoutScrollIfNeeded
2752
+ ]);
2668
2753
  return {
2669
- tooltipRef: (0, react.useCallback)((element) => {
2670
- if (element && selectedIndex !== void 0) {
2671
- if (preventTooltipScroll) element.focus({ preventScroll: true });
2672
- else element.focus();
2673
- }
2674
- }, [preventTooltipScroll, selectedIndex]),
2754
+ tooltipRef,
2675
2755
  onChartFocus: (0, react.useCallback)((event) => {
2676
2756
  if (event.currentTarget.contains(event.relatedTarget)) return;
2677
2757
  if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
@@ -2684,44 +2764,35 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2684
2764
  setIsNavigating(false);
2685
2765
  }, [setIsNavigating]),
2686
2766
  onChartKeyDown: (0, react.useCallback)((event) => {
2687
- if (totalPoints === 0) return;
2688
- const focusChart = () => {
2689
- if (preventTooltipScroll) chartRef.current?.focus({ preventScroll: true });
2690
- else chartRef.current?.focus();
2691
- };
2692
- if (event.key === "Tab") {
2693
- focusChart();
2767
+ if (event.key === "Tab" || event.key === "Escape") {
2768
+ if (event.key === "Escape" && selectedIndex !== void 0) event.preventDefault();
2769
+ focusWithoutScrollIfNeeded(getChartRoot());
2694
2770
  setSelectedIndex(void 0);
2695
2771
  setIsNavigating(false);
2696
2772
  return;
2697
2773
  }
2774
+ if (totalPoints === 0) return;
2698
2775
  const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
2699
- if (currentSelectedIndex + 1 >= totalPoints && ["ArrowRight"].includes(event.key)) {
2700
- focusChart();
2701
- setSelectedIndex(void 0);
2702
- setIsNavigating(false);
2703
- return;
2704
- }
2705
- event.preventDefault();
2706
- if (["ArrowRight"].includes(event.key)) {
2776
+ if (event.key === "ArrowRight") {
2777
+ event.preventDefault();
2707
2778
  setIsNavigating(true);
2708
- setSelectedIndex((currentSelectedIndex + 1) % totalPoints);
2709
- } else if (["ArrowLeft"].includes(event.key)) {
2779
+ setSelectedIndex(Math.min(currentSelectedIndex + 1, totalPoints - 1));
2780
+ } else if (event.key === "ArrowLeft") {
2781
+ event.preventDefault();
2710
2782
  setIsNavigating(true);
2711
- setSelectedIndex((currentSelectedIndex - 1 + totalPoints) % totalPoints);
2712
- } else if (event.key === "Escape") {
2713
- setSelectedIndex(void 0);
2714
- setIsNavigating(false);
2715
- focusChart();
2716
- } else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
2783
+ setSelectedIndex(Math.max(currentSelectedIndex - 1, 0));
2784
+ } else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) {
2785
+ event.preventDefault();
2786
+ onActivate?.(selectedIndex);
2787
+ }
2717
2788
  }, [
2718
2789
  totalPoints,
2719
2790
  selectedIndex,
2720
2791
  setSelectedIndex,
2721
2792
  setIsNavigating,
2722
- chartRef,
2723
- onActivate,
2724
- preventTooltipScroll
2793
+ getChartRoot,
2794
+ focusWithoutScrollIfNeeded,
2795
+ onActivate
2725
2796
  ])
2726
2797
  };
2727
2798
  };
@@ -2919,6 +2990,31 @@ const DefaultGlyph = (props) => {
2919
2990
  });
2920
2991
  };
2921
2992
  //#endregion
2993
+ //#region src/charts/private/readings.ts
2994
+ /**
2995
+ * Whether a value is a reading a scale can place.
2996
+ *
2997
+ * @param value - The point's value.
2998
+ * @return True for a finite number.
2999
+ */
3000
+ const isReading = (value) => typeof value === "number" && Number.isFinite(value);
3001
+ /**
3002
+ * Whether a data point's value cannot be drawn.
3003
+ *
3004
+ * @param value - The point's value.
3005
+ * @param options - Validation options.
3006
+ * @param options.allowMissing - Whether `null` is a bucket with no reading rather than a fault.
3007
+ * @return True when the value is invalid.
3008
+ */
3009
+ const isInvalidReading = (value, { allowMissing }) => value === null ? !allowMissing : value === void 0 || isNaN(value);
3010
+ /**
3011
+ * Formats a reading for a tooltip.
3012
+ *
3013
+ * @param value - The reading, or null when the bucket has none.
3014
+ * @return The formatted value.
3015
+ */
3016
+ const formatReading = (value) => value == null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(value);
3017
+ //#endregion
2922
3018
  //#region src/charts/private/center/center.module.scss
2923
3019
  var center_module_default = { "center": "a8ccharts-w3qxlG-center" };
2924
3020
  //#endregion
@@ -3291,6 +3387,54 @@ const buildTimeAxisOptions = ({ dataSorted, width, axisOptions, scaleDomain, zoo
3291
3387
  };
3292
3388
  };
3293
3389
  //#endregion
3390
+ //#region src/charts/private/whole-number-ticks.tsx
3391
+ /**
3392
+ * Whether every reading in the series is a whole number.
3393
+ *
3394
+ * @param series - The series visx renders.
3395
+ * @return True when at least one reading exists and none has a fraction.
3396
+ */
3397
+ const hasOnlyWholeNumbers = (series) => {
3398
+ let sawReading = false;
3399
+ for (const { data } of series) for (const point of data) {
3400
+ const value = point?.value;
3401
+ if (!isReading(value)) continue;
3402
+ if (!Number.isInteger(value)) return false;
3403
+ sawReading = true;
3404
+ }
3405
+ return sawReading;
3406
+ };
3407
+ /**
3408
+ * The scale's ticks with the fractional ones dropped.
3409
+ *
3410
+ * d3 steps by 1, 2 or 5 times a power of ten, so every whole number inside the domain is one of its ticks.
3411
+ *
3412
+ * @param scale - The value scale visx built.
3413
+ * @param count - Ticks to ask the scale for; undefined uses the scale's own default, as visx does.
3414
+ * @return Whole-number ticks, or undefined when visx's own ticks need no change.
3415
+ */
3416
+ const getWholeNumberTickValues = (scale, count) => {
3417
+ const ticks = scale?.ticks?.(count);
3418
+ if (!ticks?.every((tick) => typeof tick === "number")) return;
3419
+ const whole = ticks.filter((tick) => Number.isInteger(tick));
3420
+ return whole.length > 1 && whole.length < ticks.length ? whole : void 0;
3421
+ };
3422
+ /**
3423
+ * Hands its children the whole-number ticks of the chart's value scale.
3424
+ *
3425
+ * @param props - Component props.
3426
+ * @param props.axis - Which scale carries the values.
3427
+ * @param props.numTicks - Ticks to ask the scale for.
3428
+ * @param props.enabled - Whether the plotted values are all whole numbers and the caller pinned neither the ticks nor the value domain.
3429
+ * @param props.children - Renders the axis and grid with the tick values.
3430
+ * @return The rendered children.
3431
+ */
3432
+ const WholeNumberTicks = ({ axis, numTicks, enabled, children }) => {
3433
+ const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext);
3434
+ const tickValues = enabled ? getWholeNumberTickValues(axis === "x" ? xScale : yScale, numTicks) : void 0;
3435
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: children(tickValues) });
3436
+ };
3437
+ //#endregion
3294
3438
  //#region src/charts/private/with-responsive/with-responsive.module.scss
3295
3439
  var with_responsive_module_default = {
3296
3440
  "container": "a8ccharts-sP1gHa-container",
@@ -3957,11 +4101,15 @@ const toNumber$1 = (val) => {
3957
4101
  const LineChartGlyph = ({ data, index, color, glyphStyle, renderGlyph, accessors, position }) => {
3958
4102
  const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext) || {};
3959
4103
  if (!xScale || !yScale) return null;
3960
- if (data.data.length === 0) return null;
3961
- const point = position === "start" ? data.data[0] : data.data[data.data.length - 1];
4104
+ const hasFiniteY = (datum) => {
4105
+ const scaledY = yScale(accessors.yAccessor(datum));
4106
+ return typeof scaledY === "number" && Number.isFinite(scaledY);
4107
+ };
4108
+ const point = position === "start" ? data.data.find(hasFiniteY) : data.data.findLast(hasFiniteY);
4109
+ if (!point) return null;
3962
4110
  const x = xScale(accessors.xAccessor(point));
3963
4111
  const y = yScale(accessors.yAccessor(point));
3964
- if (typeof x !== "number" || typeof y !== "number") return null;
4112
+ if (typeof x !== "number" || typeof y !== "number" || !Number.isFinite(x) || !Number.isFinite(y)) return null;
3965
4113
  const size = Math.max(0, toNumber$1(glyphStyle?.radius) ?? 4);
3966
4114
  return renderGlyph({
3967
4115
  key: `${position}-glyph-${data.label}`,
@@ -3976,6 +4124,106 @@ const LineChartGlyph = ({ data, index, color, glyphStyle, renderGlyph, accessors
3976
4124
  });
3977
4125
  };
3978
4126
  //#endregion
4127
+ //#region src/charts/line-chart/private/nearest-pointer-events.tsx
4128
+ /**
4129
+ * Find the datum nearest a pointer along x, in one series sorted by x.
4130
+ *
4131
+ * @param data - The series' registered data, in ascending x order.
4132
+ * @param xAccessor - Reads a datum's x value.
4133
+ * @param xScale - The chart's x scale.
4134
+ * @param x - Pointer x in SVG coordinates.
4135
+ * @return Index of the nearest datum and its distance, or index -1 when none has a position.
4136
+ */
4137
+ function nearestByX(data, xAccessor, xScale, x) {
4138
+ return (typeof xScale.invert === "function" ? [(0, _visx_vendor_d3_array.bisector)(xAccessor).left(data, xScale.invert(x))].flatMap((index) => [index - 1, index]) : data.map((_datum, index) => index)).filter((index) => index >= 0 && index < data.length).reduce((best, index) => {
4139
+ const distance = Math.abs(Number(xScale(xAccessor(data[index]))) - x);
4140
+ return distance < best.distance ? {
4141
+ index,
4142
+ distance
4143
+ } : best;
4144
+ }, {
4145
+ index: -1,
4146
+ distance: Infinity
4147
+ });
4148
+ }
4149
+ /**
4150
+ * Pick the candidate to report: the closest placed reading, else the closest bucket with no reading along x.
4151
+ *
4152
+ * @param candidates - One candidate per series.
4153
+ * @return The chosen candidate's params, or undefined when there are none.
4154
+ */
4155
+ function pickNearest(candidates) {
4156
+ const placed = candidates.map(({ params }) => params).filter((params) => Number.isFinite(params.distanceY));
4157
+ const missing = candidates.filter(({ isMissing }) => isMissing).map(({ params }) => params);
4158
+ const distance = (params) => placed.length ? Math.hypot(params.distanceX ?? 0, params.distanceY ?? 0) : params.distanceX ?? 0;
4159
+ return (placed.length ? placed : missing).reduce((best, params) => !best || distance(params) < distance(best) ? params : best, void 0);
4160
+ }
4161
+ /**
4162
+ * Report pointer events at the nearest datum, including a bucket with no reading.
4163
+ *
4164
+ * visx's own nearest search measures y distance too, which is NaN for a null value, so a pointer
4165
+ * over a bucket where no series has a reading fires no event at all.
4166
+ *
4167
+ * @param props - Handlers to call.
4168
+ * @param props.onPointerDown - Receives the nearest datum on pointer down.
4169
+ * @param props.onPointerMove - Receives the nearest datum on pointer move.
4170
+ * @param props.onPointerUp - Receives the nearest datum on pointer up.
4171
+ * @return No visual content.
4172
+ */
4173
+ function NearestPointerEvents({ onPointerDown, onPointerMove, onPointerUp }) {
4174
+ const { xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext);
4175
+ const findNearest = (0, react.useCallback)((params) => {
4176
+ const point = params?.svgPoint;
4177
+ if (!point || !xScale || !yScale || !dataRegistry) return;
4178
+ const candidates = [];
4179
+ for (const key of dataRegistry.keys()) {
4180
+ const entry = dataRegistry.get(key);
4181
+ if (!entry) continue;
4182
+ const { index, distance } = nearestByX(entry.data, entry.xAccessor, xScale, point.x);
4183
+ if (index < 0) continue;
4184
+ const datum = entry.data[index];
4185
+ const value = entry.yAccessor(datum);
4186
+ candidates.push({
4187
+ params: {
4188
+ event: params.event,
4189
+ svgPoint: point,
4190
+ key,
4191
+ datum,
4192
+ index,
4193
+ distanceX: distance,
4194
+ distanceY: Math.abs(Number(yScale(value)) - point.y)
4195
+ },
4196
+ isMissing: value == null
4197
+ });
4198
+ }
4199
+ return pickNearest(candidates);
4200
+ }, [
4201
+ xScale,
4202
+ yScale,
4203
+ dataRegistry
4204
+ ]);
4205
+ const handlers = (0, react.useMemo)(() => {
4206
+ const report = (handler) => handler && ((params) => {
4207
+ const nearest = findNearest(params);
4208
+ if (nearest) handler(nearest);
4209
+ });
4210
+ return {
4211
+ pointerdown: report(onPointerDown),
4212
+ pointermove: report(onPointerMove),
4213
+ pointerup: report(onPointerUp)
4214
+ };
4215
+ }, [
4216
+ findNearest,
4217
+ onPointerDown,
4218
+ onPointerMove,
4219
+ onPointerUp
4220
+ ]);
4221
+ (0, _visx_xychart.useEventEmitter)("pointerdown", handlers.pointerdown);
4222
+ (0, _visx_xychart.useEventEmitter)("pointermove", handlers.pointermove);
4223
+ (0, _visx_xychart.useEventEmitter)("pointerup", handlers.pointerup);
4224
+ return null;
4225
+ }
4226
+ //#endregion
3979
4227
  //#region src/charts/line-chart/line-chart.tsx
3980
4228
  const defaultRenderGlyph = (props) => {
3981
4229
  return /* @__PURE__ */ (0, react.createElement)(DefaultGlyph, {
@@ -4019,8 +4267,13 @@ const renderDefaultTooltip = (params, contentStyle) => {
4019
4267
  if (!nearestDatum) return null;
4020
4268
  const tooltipPoints = Object.entries(tooltipData?.datumByKey || {}).map(([key, { datum }]) => ({
4021
4269
  key,
4022
- value: datum.value
4023
- })).sort((a, b) => b.value - a.value);
4270
+ value: datum.value ?? null
4271
+ })).sort((a, b) => {
4272
+ if (a.value === null && b.value === null) return 0;
4273
+ if (a.value === null) return 1;
4274
+ if (b.value === null) return -1;
4275
+ return b.value - a.value;
4276
+ });
4024
4277
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4025
4278
  className: line_chart_module_default["line-chart__tooltip"],
4026
4279
  style: contentStyle,
@@ -4040,16 +4293,23 @@ const renderDefaultTooltip = (params, contentStyle) => {
4040
4293
  children: [point.key, ":"]
4041
4294
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4042
4295
  className: line_chart_module_default["line-chart__tooltip-value"],
4043
- children: (0, _automattic_number_formatters.formatNumber)(point.value)
4296
+ children: formatReading(point.value)
4044
4297
  })]
4045
4298
  }, point.key))]
4046
4299
  });
4047
4300
  };
4048
4301
  const validateData$4 = (data) => {
4049
- if (!data?.length) return "No data available";
4050
- if (data.some((series) => series.data.some((point) => isNaN(point.value) || point.value === null || point.value === void 0 || "date" in point && point.date && isNaN(point.date.getTime())))) return "Invalid data";
4302
+ if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
4303
+ if (data.some((series) => series.data.some((point) => isInvalidReading(point.value, { allowMissing: true }) || "date" in point && point.date && isNaN(point.date.getTime())))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
4051
4304
  return null;
4052
4305
  };
4306
+ const getFallbackYDomain = (readingExtent, isLogScale) => {
4307
+ const emptyDomain = isLogScale ? [1, 10] : [0, 1];
4308
+ if (!readingExtent) return emptyDomain;
4309
+ const [min, max] = readingExtent;
4310
+ if (min !== max || isLogScale) return;
4311
+ return min === 0 ? emptyDomain : [Math.min(0, min), Math.max(0, max)];
4312
+ };
4053
4313
  const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
4054
4314
  const context = (0, react.useContext)(_visx_xychart.DataContext);
4055
4315
  (0, react.useImperativeHandle)(chartRef, () => ({
@@ -4076,6 +4336,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
4076
4336
  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) => {
4077
4337
  const legendInteractive = legend.interactive ?? false;
4078
4338
  const legendCollapseGroups = legend.collapseGroups ?? false;
4339
+ const legendComparisonItem = legend.comparisonItem ?? false;
4079
4340
  const legendShape = legend.shape ?? "line";
4080
4341
  const legendPosition = legend.position ?? "bottom";
4081
4342
  const formatting = useChartFormatting();
@@ -4122,19 +4383,32 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4122
4383
  const allSeriesHidden = (0, react.useMemo)(() => {
4123
4384
  return seriesWithVisibility.every(({ isVisible }) => !isVisible);
4124
4385
  }, [seriesWithVisibility]);
4125
- const stableYDomain = (0, react.useMemo)(() => {
4126
- if (rescaleYOnVisibilityChange) return;
4386
+ const yDomain = (0, react.useMemo)(() => {
4387
+ const includeZero = options?.yScale?.zero === true && (0, _visx_scale.scaleCanBeZeroed)({ type: options.yScale.type ?? "linear" });
4388
+ if (rescaleYOnVisibilityChange && !includeZero) return;
4127
4389
  let min = Infinity;
4128
4390
  let max = -Infinity;
4129
- for (const series of dataSorted) for (const point of series.data ?? []) {
4130
- const value = point?.value;
4131
- if (typeof value === "number" && Number.isFinite(value)) {
4132
- min = Math.min(min, value);
4133
- max = Math.max(max, value);
4391
+ for (const series of dataSorted) {
4392
+ if (rescaleYOnVisibilityChange && !isSeriesVisible(series.label)) continue;
4393
+ for (const point of series.data ?? []) {
4394
+ const value = point?.value;
4395
+ if (isReading(value)) {
4396
+ min = Math.min(min, value);
4397
+ max = Math.max(max, value);
4398
+ }
4134
4399
  }
4135
4400
  }
4401
+ if (includeZero) {
4402
+ if (min === Infinity) return;
4403
+ return max === 0 && min === 0 ? [0, 1] : [Math.min(0, min), Math.max(0, max)];
4404
+ }
4136
4405
  return min < max ? [min, max] : void 0;
4137
- }, [rescaleYOnVisibilityChange, dataSorted]);
4406
+ }, [
4407
+ rescaleYOnVisibilityChange,
4408
+ dataSorted,
4409
+ isSeriesVisible,
4410
+ options?.yScale
4411
+ ]);
4138
4412
  const activateSelectedPoint = (0, react.useCallback)((index) => {
4139
4413
  const series = dataSorted[0];
4140
4414
  const datum = series?.data[index];
@@ -4154,7 +4428,24 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4154
4428
  onActivate: activateSelectedPoint,
4155
4429
  preventTooltipScroll: tooltipPlacement === "below-axis"
4156
4430
  });
4431
+ const visibleReadingExtent = (0, react.useMemo)(() => {
4432
+ let min = Infinity;
4433
+ let max = -Infinity;
4434
+ for (const series of dataSorted) {
4435
+ if (!isSeriesVisible(series.label)) continue;
4436
+ for (const point of series.data) {
4437
+ const value = point?.value;
4438
+ if (isReading(value)) {
4439
+ min = Math.min(min, value);
4440
+ max = Math.max(max, value);
4441
+ }
4442
+ }
4443
+ }
4444
+ return min <= max ? [min, max] : void 0;
4445
+ }, [dataSorted, isSeriesVisible]);
4446
+ const hasWholeNumberValues = (0, react.useMemo)(() => hasOnlyWholeNumbers(dataSorted.filter((series) => isSeriesVisible(series.label))), [dataSorted, isSeriesVisible]);
4157
4447
  const chartOptions = (0, react.useMemo)(() => {
4448
+ const fallbackYDomain = getFallbackYDomain(visibleReadingExtent, options?.yScale?.type === "log");
4158
4449
  return {
4159
4450
  axis: {
4160
4451
  x: buildTimeAxisOptions({
@@ -4183,7 +4474,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4183
4474
  type: "linear",
4184
4475
  nice: true,
4185
4476
  zero: false,
4186
- ...stableYDomain ? { domain: stableYDomain } : {},
4477
+ ...fallbackYDomain ? { domain: fallbackYDomain } : {},
4478
+ ...yDomain ? { domain: yDomain } : {},
4187
4479
  ...options?.yScale
4188
4480
  }
4189
4481
  };
@@ -4192,7 +4484,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4192
4484
  dataSorted,
4193
4485
  width,
4194
4486
  zoom.domain,
4195
- stableYDomain,
4487
+ yDomain,
4488
+ visibleReadingExtent,
4196
4489
  formatting,
4197
4490
  isSeriesVisible
4198
4491
  ]);
@@ -4227,11 +4520,13 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4227
4520
  withGlyph: withLegendGlyph,
4228
4521
  glyphSize: Math.max(0, toNumber(glyphStyle?.radius) ?? 4),
4229
4522
  collapseGroups: legendCollapseGroups,
4523
+ comparisonItem: legendComparisonItem,
4230
4524
  renderGlyph
4231
4525
  }), [
4232
4526
  withLegendGlyph,
4233
4527
  glyphStyle?.radius,
4234
4528
  legendCollapseGroups,
4529
+ legendComparisonItem,
4235
4530
  renderGlyph
4236
4531
  ]), legendShape);
4237
4532
  const chartMetadata = (0, react.useMemo)(() => ({
@@ -4343,18 +4638,26 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4343
4638
  },
4344
4639
  xScale: chartOptions.xScale,
4345
4640
  yScale: chartOptions.yScale,
4346
- onPointerDown: zoom.handlers.onPointerDown,
4347
- onPointerUp: zoom.handlers.onPointerUp,
4348
- onPointerMove: zoom.handlers.onPointerMove,
4349
4641
  onPointerOut,
4350
- pointerEventsDataKey: "nearest",
4351
4642
  children: [
4352
- !allSeriesHidden && gridVisibility !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
4353
- columns: false,
4354
- numTicks: 4
4643
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NearestPointerEvents, { ...zoom.handlers }),
4644
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
4645
+ axis: "y",
4646
+ numTicks: chartOptions.axis.y.numTicks,
4647
+ enabled: hasWholeNumberValues && !chartOptions.axis.y.tickValues && !options?.yScale?.domain,
4648
+ children: (tickValues) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4649
+ !allSeriesHidden && gridVisibility !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
4650
+ columns: false,
4651
+ numTicks: chartOptions.axis.y.numTicks,
4652
+ tickValues: tickValues ?? chartOptions.axis.y.tickValues
4653
+ }),
4654
+ !allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
4655
+ !allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
4656
+ ...chartOptions.axis.y,
4657
+ ...tickValues ? { tickValues } : {}
4658
+ })
4659
+ ] })
4355
4660
  }),
4356
- !allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
4357
- !allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.y }),
4358
4661
  allSeriesHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
4359
4662
  x: width / 2,
4360
4663
  y: chartHeight / 2,
@@ -4495,7 +4798,7 @@ var area_chart_module_default = {
4495
4798
  const validateData$3 = (data) => {
4496
4799
  if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
4497
4800
  if (data.some((series) => !series.data?.length)) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
4498
- if (data.some((series) => series.data.some((point) => isNaN(point.value) || point.value === null || point.value === void 0 || "date" in point && point.date && isNaN(point.date.getTime())))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
4801
+ if (data.some((series) => series.data.some((point) => isInvalidReading(point.value, { allowMissing: false }) || "date" in point && point.date && isNaN(point.date.getTime())))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
4499
4802
  return null;
4500
4803
  };
4501
4804
  //#endregion
@@ -4611,6 +4914,12 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4611
4914
  }));
4612
4915
  }, [dataSorted, hiddenSeries]);
4613
4916
  const allSeriesHidden = (0, react.useMemo)(() => seriesWithVisibility.every(({ isVisible }) => !isVisible), [seriesWithVisibility]);
4917
+ const hasWholeNumberValues = (0, react.useMemo)(() => (!stacked || stackOffset === "none") && hasOnlyWholeNumbers(dataSorted.filter((series) => isSeriesVisible(series.label))), [
4918
+ dataSorted,
4919
+ isSeriesVisible,
4920
+ stacked,
4921
+ stackOffset
4922
+ ]);
4614
4923
  const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
4615
4924
  selectedIndex,
4616
4925
  setSelectedIndex,
@@ -4703,8 +5012,9 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4703
5012
  const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
4704
5013
  withGlyph: false,
4705
5014
  glyphSize: 0,
4706
- collapseGroups: legend.collapseGroups ?? false
4707
- }), [legend.collapseGroups]), legendShape);
5015
+ collapseGroups: legend.collapseGroups ?? false,
5016
+ comparisonItem: legend.comparisonItem ?? false
5017
+ }), [legend.collapseGroups, legend.comparisonItem]), legendShape);
4708
5018
  const chartMetadata = (0, react.useMemo)(() => ({
4709
5019
  stacked,
4710
5020
  stackOffset,
@@ -4854,12 +5164,23 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4854
5164
  onPointerOut,
4855
5165
  pointerEventsDataKey: "nearest",
4856
5166
  children: [
4857
- !allSeriesHidden && gridVisibility !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
4858
- columns: false,
4859
- numTicks: 4
5167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
5168
+ axis: "y",
5169
+ numTicks: chartOptions.axis.y.numTicks,
5170
+ enabled: hasWholeNumberValues && !chartOptions.axis.y.tickValues && !options?.yScale?.domain,
5171
+ children: (tickValues) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5172
+ !allSeriesHidden && gridVisibility !== "none" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
5173
+ columns: false,
5174
+ numTicks: chartOptions.axis.y.numTicks,
5175
+ tickValues: tickValues ?? chartOptions.axis.y.tickValues
5176
+ }),
5177
+ !allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
5178
+ !allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
5179
+ ...chartOptions.axis.y,
5180
+ ...tickValues ? { tickValues } : {}
5181
+ })
5182
+ ] })
4860
5183
  }),
4861
- !allSeriesHidden && chartOptions.axis.x.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }),
4862
- !allSeriesHidden && chartOptions.axis.y.display && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.y }),
4863
5184
  allSeriesHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgEmptyState, {
4864
5185
  x: width / 2,
4865
5186
  y: chartHeight / 2,
@@ -5048,6 +5369,65 @@ const createTruncatedTickComponent = (axis) => (props) => {
5048
5369
  const TruncatedXTickComponent = createTruncatedTickComponent("x");
5049
5370
  const TruncatedYTickComponent = createTruncatedTickComponent("y");
5050
5371
  //#endregion
5372
+ //#region src/charts/bar-chart/private/value-domain.ts
5373
+ /** Domain for a value axis that has nothing to scale, so its ticks still read 0 to 1. */
5374
+ const EMPTY_DOMAIN = [0, 1];
5375
+ /**
5376
+ * The value a bar is drawn at, preferring `visualValue` so a zero still shows a sliver.
5377
+ *
5378
+ * A missing reading is NaN, not null: `ComparisonBars` coerces the value with `Number()`,
5379
+ * which turns null into 0 and would draw a comparison shadow at the baseline.
5380
+ *
5381
+ * @param point - A data point handed to visx.
5382
+ * @return The bar value, or NaN when the bucket has no reading.
5383
+ */
5384
+ const getBarValue = (point) => {
5385
+ const enhanced = point;
5386
+ return (enhanced?.visualValue !== void 0 ? enhanced.visualValue : point?.value) ?? NaN;
5387
+ };
5388
+ /**
5389
+ * Whether visx renders a bar for a point.
5390
+ *
5391
+ * @param point - A data point handed to visx.
5392
+ * @return True when the point has a finite bar value.
5393
+ */
5394
+ const isBarRendered = (point) => Number.isFinite(getBarValue(point));
5395
+ /**
5396
+ * How many bars visx renders for a list of points.
5397
+ *
5398
+ * @param points - Data points handed to visx.
5399
+ * @return The number of points that get a bar.
5400
+ */
5401
+ const countRenderedBars = (points) => points.filter(isBarRendered).length;
5402
+ /**
5403
+ * An explicit domain for the value scale, or null to let visx fit one to the data.
5404
+ *
5405
+ * Zero goes into the domain here rather than through the scale's `zero` flag: visx applies
5406
+ * `nice` before `zero`, which would leave the axis topping out at a raw maximum.
5407
+ *
5408
+ * @param data - Every series handed to the chart.
5409
+ * @param includeZero - Whether the domain must span zero.
5410
+ * @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
5411
+ * @return Value domain, or null when visx should fit its own.
5412
+ */
5413
+ const getValueScaleDomain = (data, includeZero, isSeriesRendered) => {
5414
+ let min = Infinity;
5415
+ let max = -Infinity;
5416
+ for (const series of data) {
5417
+ if (!isSeriesRendered(series)) continue;
5418
+ for (const point of series.data) {
5419
+ const value = getBarValue(point);
5420
+ if (!Number.isFinite(value)) continue;
5421
+ min = Math.min(min, value);
5422
+ max = Math.max(max, value);
5423
+ }
5424
+ }
5425
+ if (min === Infinity) return [...EMPTY_DOMAIN];
5426
+ if (min === max) return min === 0 ? [...EMPTY_DOMAIN] : [Math.min(0, min), Math.max(0, max)];
5427
+ if (includeZero) return [Math.min(0, min), Math.max(0, max)];
5428
+ return null;
5429
+ };
5430
+ //#endregion
5051
5431
  //#region src/charts/bar-chart/private/use-bar-chart-options.ts
5052
5432
  /** Outer padding of the category band scale (space at the chart edges). */
5053
5433
  const BASE_BAND_PADDING = .2;
@@ -5172,10 +5552,6 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
5172
5552
  const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution, formatting));
5173
5553
  const valueFormatter = _automattic_number_formatters.formatNumberCompact;
5174
5554
  const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
5175
- const valueAccessor = (d) => {
5176
- const enhancedPoint = d;
5177
- return enhancedPoint?.visualValue !== void 0 ? enhancedPoint.visualValue : d?.value;
5178
- };
5179
5555
  return {
5180
5556
  timeAxis: bandDomain && timeTickFormatter && {
5181
5557
  domain: bandDomain,
@@ -5186,7 +5562,7 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
5186
5562
  yTickFormat: valueFormatter,
5187
5563
  tooltipLabelFormatter: tooltipDatumFormatter,
5188
5564
  xAccessor: bucketAccessor,
5189
- yAccessor: valueAccessor,
5565
+ yAccessor: getBarValue,
5190
5566
  gridVisibility: "x",
5191
5567
  xScale: bandScale,
5192
5568
  yScale: linearScale
@@ -5195,7 +5571,7 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
5195
5571
  xTickFormat: valueFormatter,
5196
5572
  yTickFormat: labelFormatter,
5197
5573
  tooltipLabelFormatter: tooltipDatumFormatter,
5198
- xAccessor: valueAccessor,
5574
+ xAccessor: getBarValue,
5199
5575
  yAccessor: bucketAccessor,
5200
5576
  gridVisibility: "y",
5201
5577
  xScale: linearScale,
@@ -5210,20 +5586,10 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
5210
5586
  ]);
5211
5587
  return (0, react.useMemo)(() => {
5212
5588
  const { xTickFormat: defaultXTickFormat, yTickFormat: defaultYTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
5213
- let valueScaleDomainOverride = {};
5214
- if (data.some((s) => s.options?.type === "comparison")) {
5215
- if (!(!horizontal ? stableOptions.yScale?.domain : stableOptions.xScale?.domain)) {
5216
- const allValues = [];
5217
- data.forEach((series) => {
5218
- series.data.forEach((d) => {
5219
- const enhanced = d;
5220
- const v = enhanced.visualValue !== void 0 ? enhanced.visualValue : d.value;
5221
- if (typeof v === "number" && Number.isFinite(v)) allValues.push(v);
5222
- });
5223
- });
5224
- if (allValues.length > 0) valueScaleDomainOverride = { domain: [Math.min(0, ...allValues), Math.max(0, ...allValues)] };
5225
- }
5226
- }
5589
+ const valueScaleOptions = horizontal ? stableOptions.xScale : stableOptions.yScale;
5590
+ const includeZero = data.some((s) => s.options?.type === "comparison") || valueScaleOptions?.zero !== false;
5591
+ const domain = valueScaleOptions?.domain ? null : getValueScaleDomain(data, includeZero, isSeriesRendered);
5592
+ const valueScaleDomainOverride = domain ? { domain } : {};
5227
5593
  const xScale = {
5228
5594
  ...baseXScale,
5229
5595
  ...stableOptions.xScale || {},
@@ -5274,10 +5640,25 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
5274
5640
  axisConfig,
5275
5641
  stableOptions,
5276
5642
  horizontal,
5277
- data
5643
+ data,
5644
+ isSeriesRendered
5278
5645
  ]);
5279
5646
  }
5280
5647
  //#endregion
5648
+ //#region src/charts/bar-chart/private/band-scale.ts
5649
+ /**
5650
+ * Match the inner slot scale used by visx BarGroup.
5651
+ * @param keys - Visible primary series keys.
5652
+ * @param bandwidth - Category band width.
5653
+ * @param padding - Padding between slots.
5654
+ * @return The grouped bar scale.
5655
+ */
5656
+ const createGroupScale = (keys, bandwidth, padding) => (0, _visx_scale.scaleBand)({
5657
+ domain: keys,
5658
+ range: [0, bandwidth],
5659
+ padding
5660
+ });
5661
+ //#endregion
5281
5662
  //#region src/charts/bar-chart/private/comparison-bars-geometry.ts
5282
5663
  /**
5283
5664
  * Output position of a value scale's baseline: zero if in-domain, else the
@@ -5350,11 +5731,7 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
5350
5731
  const valueScale = horizontal ? xScale : yScale;
5351
5732
  const bandwidth = bandScale.bandwidth ? bandScale.bandwidth() : 0;
5352
5733
  if (!bandwidth) return null;
5353
- const groupScale = (0, _visx_scale.scaleBand)({
5354
- domain: primaryKeys,
5355
- range: [0, bandwidth],
5356
- padding: groupPadding
5357
- });
5734
+ const groupScale = createGroupScale(primaryKeys, bandwidth, groupPadding);
5358
5735
  const slotThickness = groupScale.bandwidth();
5359
5736
  const baseline = getValueScaleBaseline(valueScale);
5360
5737
  const bandAccessor = horizontal ? yAccessor : xAccessor;
@@ -5406,10 +5783,169 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
5406
5783
  });
5407
5784
  };
5408
5785
  //#endregion
5786
+ //#region src/charts/bar-chart/private/band-highlight.tsx
5787
+ /**
5788
+ * Render and report the active band using the chart’s registered scales.
5789
+ * @param root0 - Highlight options.
5790
+ * @param root0.visible - Whether to paint the band.
5791
+ * @param root0.horizontal - Whether categories run vertically.
5792
+ * @param root0.onChange - Receives selected band geometry.
5793
+ * @return The optional band highlight.
5794
+ */
5795
+ function BandHighlight({ visible, horizontal, onChange }) {
5796
+ const { xScale, yScale, dataRegistry, margin, innerWidth, innerHeight } = (0, react.useContext)(_visx_xychart.DataContext);
5797
+ const { tooltipOpen, tooltipData } = (0, react.useContext)(_visx_xychart.TooltipContext);
5798
+ const nearest = tooltipData?.nearestDatum;
5799
+ const selection = (0, react.useMemo)(() => {
5800
+ if (!tooltipOpen || !nearest) return null;
5801
+ const scale = horizontal ? yScale : xScale;
5802
+ const entry = dataRegistry?.get(nearest.key);
5803
+ const accessor = horizontal ? entry?.yAccessor : entry?.xAccessor;
5804
+ if (!scale?.bandwidth || !accessor) return null;
5805
+ const position = scale(accessor(nearest.datum));
5806
+ if (position === void 0 || !Number.isFinite(position)) return null;
5807
+ return {
5808
+ datum: nearest.datum,
5809
+ key: nearest.key,
5810
+ index: nearest.index,
5811
+ x: horizontal ? margin?.left ?? 0 : position,
5812
+ y: horizontal ? position : margin?.top ?? 0,
5813
+ width: horizontal ? innerWidth : scale.bandwidth(),
5814
+ height: horizontal ? scale.bandwidth() : innerHeight
5815
+ };
5816
+ }, [
5817
+ tooltipOpen,
5818
+ nearest,
5819
+ horizontal,
5820
+ xScale,
5821
+ yScale,
5822
+ dataRegistry,
5823
+ margin,
5824
+ innerWidth,
5825
+ innerHeight
5826
+ ]);
5827
+ const stableSelection = useDeepMemo(selection);
5828
+ const onChangeRef = (0, react.useRef)(onChange);
5829
+ onChangeRef.current = onChange;
5830
+ (0, react.useEffect)(() => {
5831
+ onChangeRef.current?.(stableSelection);
5832
+ }, [stableSelection]);
5833
+ (0, react.useEffect)(() => () => onChangeRef.current?.(null), []);
5834
+ if (!visible || !selection) return null;
5835
+ const { x, y, width, height } = selection;
5836
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
5837
+ x,
5838
+ y,
5839
+ width,
5840
+ height,
5841
+ pointerEvents: "none",
5842
+ fill: CATALOG_POINTERS.surfaceSecondary
5843
+ });
5844
+ }
5845
+ //#endregion
5846
+ //#region src/charts/bar-chart/private/band-tooltip.tsx
5847
+ /**
5848
+ * Find a category by its painted center, including outer padding and reversed ranges.
5849
+ * @param data - Registered series points.
5850
+ * @param accessor - Category accessor.
5851
+ * @param scale - Rendered category band scale.
5852
+ * @param position - Pointer coordinate on the category axis.
5853
+ * @return Index of the nearest category, or -1 when none is registered.
5854
+ */
5855
+ function nearestBandIndex(data, accessor, scale, position) {
5856
+ let nearest = -1;
5857
+ let distance = Infinity;
5858
+ data.forEach((datum, index) => {
5859
+ const start = scale(accessor(datum));
5860
+ if (start === void 0) return;
5861
+ const candidate = Math.abs(start + scale.bandwidth() / 2 - position);
5862
+ if (candidate < distance) {
5863
+ distance = candidate;
5864
+ nearest = index;
5865
+ }
5866
+ });
5867
+ return nearest;
5868
+ }
5869
+ /**
5870
+ * Correct visx's range-based band inversion, which omits outer padding.
5871
+ * @param root0 - Registered series selection.
5872
+ * @param root0.keys - Visible series keys.
5873
+ * @param root0.groupPadding - Padding between grouped bars.
5874
+ * @param root0.withTooltips - Whether pointer events update the tooltip.
5875
+ * @param root0.onPointerDown - Receives the corrected pointer-down datum.
5876
+ * @param root0.onPointerUp - Receives the corrected pointer-up datum.
5877
+ * @return No visual content.
5878
+ */
5879
+ function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp }) {
5880
+ const { xScale, yScale, dataRegistry, horizontal } = (0, react.useContext)(_visx_xychart.DataContext);
5881
+ const { showTooltip } = (0, react.useContext)(_visx_xychart.TooltipContext);
5882
+ const scale = horizontal ? yScale : xScale;
5883
+ const bandwidth = scale?.bandwidth?.() ?? 0;
5884
+ const groupScale = (0, react.useMemo)(() => createGroupScale(keys, bandwidth, groupPadding), [
5885
+ keys,
5886
+ bandwidth,
5887
+ groupPadding
5888
+ ]);
5889
+ const getSelections = (0, react.useCallback)((params) => {
5890
+ const point = params?.svgPoint;
5891
+ if (!point || !scale?.bandwidth) return [];
5892
+ const selections = [];
5893
+ for (const key of keys) {
5894
+ const entry = dataRegistry?.get(key);
5895
+ if (!entry) continue;
5896
+ const accessor = horizontal ? entry.yAccessor : entry.xAccessor;
5897
+ const index = nearestBandIndex(entry.data, accessor, scale, horizontal ? point.y : point.x);
5898
+ if (index < 0) continue;
5899
+ const datum = entry.data[index];
5900
+ const start = Number(scale(accessor(datum))) + Number(groupScale(key));
5901
+ const end = start + groupScale.step();
5902
+ const position = horizontal ? point.y : point.x;
5903
+ const distance = position >= start && position <= end ? 0 : Math.abs(position - (start + end) / 2);
5904
+ selections.push({
5905
+ event: params.event,
5906
+ key,
5907
+ datum,
5908
+ index,
5909
+ svgPoint: point,
5910
+ distanceX: horizontal ? 0 : distance,
5911
+ distanceY: horizontal ? distance : 0
5912
+ });
5913
+ }
5914
+ return selections;
5915
+ }, [
5916
+ scale,
5917
+ dataRegistry,
5918
+ horizontal,
5919
+ keys,
5920
+ groupScale
5921
+ ]);
5922
+ const handlePointer = (0, react.useCallback)((params) => {
5923
+ const selections = getSelections(params);
5924
+ if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
5925
+ const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
5926
+ if (params?.event.type === "pointermove" || !callback) return;
5927
+ const nearest = selections.reduce((best, selection) => {
5928
+ const distance = (value) => Math.hypot(value.distanceX, value.distanceY);
5929
+ return !best || distance(selection) <= distance(best) ? selection : best;
5930
+ }, void 0);
5931
+ if (nearest) callback(nearest);
5932
+ }, [
5933
+ getSelections,
5934
+ withTooltips,
5935
+ showTooltip,
5936
+ onPointerDown,
5937
+ onPointerUp
5938
+ ]);
5939
+ (0, _visx_xychart.useEventEmitter)("pointermove", withTooltips ? handlePointer : void 0);
5940
+ (0, _visx_xychart.useEventEmitter)("pointerdown", handlePointer);
5941
+ (0, _visx_xychart.useEventEmitter)("pointerup", onPointerUp ? handlePointer : void 0);
5942
+ return null;
5943
+ }
5944
+ //#endregion
5409
5945
  //#region src/charts/bar-chart/bar-chart.tsx
5410
5946
  const validateData$2 = (data) => {
5411
- if (!data?.length) return "No data available";
5412
- if (data.some((series) => series.data.some((point) => isNaN(point.value) || point.value === null || point.value === void 0 || !point.label && (!("date" in point && point.date) || isNaN(point.date.getTime()))))) return "Invalid data";
5947
+ if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
5948
+ if (data.some((series) => series.data.some((point) => isInvalidReading(point.value, { allowMissing: true }) || !point.label && (!("date" in point && point.date) || isNaN(point.date.getTime()))))) return (0, _wordpress_i18n.__)("Invalid data", "jetpack-charts");
5413
5949
  return null;
5414
5950
  };
5415
5951
  const getPatternId = (chartId, index) => `bar-pattern-${chartId}-${index}`;
@@ -5417,9 +5953,11 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ (0, react_jsx_runtime
5417
5953
  className: bar_chart_module_default["bar-chart__tooltip-row"],
5418
5954
  children: (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("%1$s: %2$s", "jetpack-charts"), label, value)
5419
5955
  });
5420
- const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
5956
+ const BarChartInternal = ({ data, chartId: providedChartId, width, height, className, margin, withTooltips = false, showLegend = false, legend = {}, gridVisibility: gridVisibilityProp, renderTooltip, tooltipPlacement, tooltipAnchorTop, options = {}, orientation = "vertical", withPatterns = false, showZeroValues = false, withBandHighlight = false, onBandHighlightChange, defaultHiddenSeries, animation, children, gap = "md", onPointerDown, onPointerUp, onDatumActivate }) => {
5957
+ if (!withTooltips && (withBandHighlight || onBandHighlightChange)) warnOnce("bar-chart-band-highlight-without-tooltips", "BarChart: withBandHighlight and onBandHighlightChange require withTooltips.");
5421
5958
  const legendInteractive = legend.interactive ?? false;
5422
5959
  const legendCollapseGroups = legend.collapseGroups ?? false;
5960
+ const legendComparisonItem = legend.comparisonItem ?? false;
5423
5961
  const horizontal = orientation === "horizontal";
5424
5962
  const chartId = useChartId(providedChartId);
5425
5963
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -5430,10 +5968,21 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5430
5968
  enabled: showZeroValues,
5431
5969
  valueAxisLength: horizontal ? width : height
5432
5970
  });
5433
- const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({ collapseGroups: legendCollapseGroups }), [legendCollapseGroups]));
5971
+ const legendItems = useChartLegendItems(dataSorted, (0, react.useMemo)(() => ({
5972
+ collapseGroups: legendCollapseGroups,
5973
+ comparisonItem: legendComparisonItem
5974
+ }), [legendCollapseGroups, legendComparisonItem]));
5434
5975
  const { getElementStyles } = useGlobalChartsContext();
5435
5976
  const isSeriesRendered = (0, react.useCallback)((series) => isSeriesVisible(series.label), [isSeriesVisible]);
5977
+ const hasWholeNumberValues = (0, react.useMemo)(() => hasOnlyWholeNumbers(dataSorted.filter(isSeriesRendered)), [dataSorted, isSeriesRendered]);
5436
5978
  const chartOptions = useBarChartOptions(dataWithVisibleZeros, horizontal, options, isSeriesRendered);
5979
+ const valueAxis = horizontal ? chartOptions.axis.x : chartOptions.axis.y;
5980
+ const callerValueDomain = horizontal ? options.xScale?.domain : options.yScale?.domain;
5981
+ const wholeNumberTicksProps = {
5982
+ axis: horizontal ? "x" : "y",
5983
+ numTicks: valueAxis.numTicks,
5984
+ enabled: hasWholeNumberValues && !valueAxis.tickValues && !callerValueDomain
5985
+ };
5437
5986
  const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme, horizontal);
5438
5987
  const chartRef = (0, react.useRef)(null);
5439
5988
  const { legendChildren, nonLegendChildren } = useChartChildren(children, "BarChart");
@@ -5443,8 +5992,6 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5443
5992
  }, [height]);
5444
5993
  const [selectedIndex, setSelectedIndex] = (0, react.useState)(void 0);
5445
5994
  const [isNavigating, setIsNavigating] = (0, react.useState)(false);
5446
- const primarySeriesForNav = dataWithVisibleZeros.filter((s) => s.options?.type !== "comparison");
5447
- const totalPoints = Math.max(0, ...primarySeriesForNav.map((s) => s.data?.length || 0)) * primarySeriesForNav.length;
5448
5995
  const seriesWithVisibility = (0, react.useMemo)(() => dataWithVisibleZeros.map((series, index) => ({
5449
5996
  series,
5450
5997
  index,
@@ -5454,6 +6001,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5454
6001
  return seriesWithVisibility.every(({ isVisible }) => !isVisible);
5455
6002
  }, [seriesWithVisibility]);
5456
6003
  const primaryEntries = (0, react.useMemo)(() => seriesWithVisibility.filter(({ isVisible, series }) => isVisible && series.options?.type !== "comparison"), [seriesWithVisibility]);
6004
+ const totalPoints = Math.max(0, ...primaryEntries.map((e) => e.series.data.length)) * primaryEntries.length;
5457
6005
  const primaryKeys = (0, react.useMemo)(() => primaryEntries.map(({ series }) => series.label), [primaryEntries]);
5458
6006
  const primarySeries = (0, react.useMemo)(() => primaryEntries.map(({ series }) => series), [primaryEntries]);
5459
6007
  const activateSelectedBar = (0, react.useCallback)((index) => {
@@ -5468,6 +6016,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5468
6016
  key: series.label
5469
6017
  });
5470
6018
  }, [primaryEntries, onDatumActivate]);
6019
+ const visibleSeriesKey = (0, react.useMemo)(() => JSON.stringify(primaryKeys), [primaryKeys]);
5471
6020
  const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
5472
6021
  selectedIndex,
5473
6022
  setSelectedIndex,
@@ -5475,7 +6024,8 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5475
6024
  setIsNavigating,
5476
6025
  chartRef,
5477
6026
  totalPoints,
5478
- onActivate: activateSelectedBar
6027
+ onActivate: activateSelectedBar,
6028
+ visibleSeriesKey
5479
6029
  });
5480
6030
  const comparisonEntries = (0, react.useMemo)(() => {
5481
6031
  const primaryByGroup = new Map(primaryEntries.map(({ series, index }) => [series.group, {
@@ -5538,7 +6088,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5538
6088
  chartOptions.yScale,
5539
6089
  horizontal
5540
6090
  ]);
5541
- const getBarBackground = (0, react.useCallback)((index) => () => withPatterns ? `url(#${getPatternId(chartId, index)})` : getElementStyles({
6091
+ const getBarBackground = (0, react.useCallback)((index) => (datum) => withPatterns ? `url(#${getPatternId(chartId, index)})` : datum.color ?? getElementStyles({
5542
6092
  data: dataSorted[index],
5543
6093
  index
5544
6094
  }).color, [
@@ -5565,15 +6115,15 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5565
6115
  const p = point;
5566
6116
  return nearestDatum.label != null ? p.label === nearestDatum.label : !!nearestDatum.date && !!p.date && p.date.getTime() === nearestDatum.date.getTime();
5567
6117
  });
5568
- if (comparisonEntry && comparisonDatum && comparisonDatum.value != null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6118
+ if (comparisonEntry && comparisonDatum) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5569
6119
  className: bar_chart_module_default["bar-chart__tooltip"],
5570
6120
  children: [
5571
6121
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5572
6122
  className: bar_chart_module_default["bar-chart__tooltip-header"],
5573
6123
  children: categoryLabel
5574
6124
  }),
5575
- renderTooltipRow(primaryKey, (0, _automattic_number_formatters.formatNumber)(nearestDatum.value)),
5576
- renderTooltipRow(comparisonEntry.series.label, (0, _automattic_number_formatters.formatNumber)(comparisonDatum.value))
6125
+ renderTooltipRow(primaryKey, formatReading(nearestDatum.value)),
6126
+ renderTooltipRow(comparisonEntry.series.label, formatReading(comparisonDatum.value))
5577
6127
  ]
5578
6128
  });
5579
6129
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5581,7 +6131,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5581
6131
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5582
6132
  className: bar_chart_module_default["bar-chart__tooltip-header"],
5583
6133
  children: primaryKey
5584
- }), renderTooltipRow(categoryLabel, (0, _automattic_number_formatters.formatNumber)(nearestDatum.value))]
6134
+ }), renderTooltipRow(categoryLabel, formatReading(nearestDatum.value))]
5585
6135
  });
5586
6136
  }, [chartOptions.tooltip, comparisonEntries]);
5587
6137
  const renderPattern = (0, react.useCallback)((index, color) => {
@@ -5632,15 +6182,15 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5632
6182
  const createKeyboardHighlightStyle = (0, react.useCallback)(() => {
5633
6183
  if (selectedIndex === void 0) return "";
5634
6184
  const primaryCount = primaryEntries.length;
5635
- const maxDataPoints = Math.max(...primaryEntries.map((e) => e.series.data.length));
6185
+ if (!primaryCount) return "";
5636
6186
  const dataPointIndex = Math.floor(selectedIndex / primaryCount);
5637
6187
  const seriesIndex = selectedIndex % primaryCount;
5638
- if (dataPointIndex >= maxDataPoints || seriesIndex >= primaryCount) return "";
5639
6188
  const seriesData = primaryEntries[seriesIndex]?.series;
5640
- if (!seriesData || dataPointIndex >= seriesData.data.length) return "";
5641
- const actualBarIndex = seriesIndex * maxDataPoints + dataPointIndex;
6189
+ const datum = seriesData?.data[dataPointIndex];
6190
+ if (!seriesData || !datum || !isBarRendered(datum)) return "";
6191
+ const domBarIndex = primaryEntries.slice(0, seriesIndex).reduce((count, { series }) => count + countRenderedBars(series.data), 0) + countRenderedBars(seriesData.data.slice(0, dataPointIndex));
5642
6192
  return `
5643
- .bar-chart[data-chart-id="bar-chart-${chartId}"] .visx-bar-group .visx-bar:nth-child(${actualBarIndex + 1}) {
6193
+ .bar-chart[data-chart-id="bar-chart-${chartId}"] .visx-bar-group .visx-bar:nth-child(${domBarIndex + 1}) {
5644
6194
  stroke: #005fcc;
5645
6195
  stroke-width: 2px;
5646
6196
  }
@@ -5708,13 +6258,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5708
6258
  const chartHeight = contentHeight > 0 ? contentHeight : height;
5709
6259
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5710
6260
  role: "grid",
6261
+ ref: chartRef,
5711
6262
  "aria-label": (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
5712
6263
  tabIndex: 0,
5713
6264
  onKeyDown: onChartKeyDown,
5714
6265
  onFocus: onChartFocus,
5715
6266
  onBlur: onChartBlur,
5716
6267
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5717
- ref: chartRef,
5718
6268
  className: xy_plot_module_default["xy-plot"],
5719
6269
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5720
6270
  theme,
@@ -5727,14 +6277,26 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5727
6277
  xScale,
5728
6278
  yScale,
5729
6279
  horizontal,
5730
- onPointerDown,
5731
- onPointerUp,
5732
6280
  pointerEventsDataKey: "nearest",
5733
6281
  children: [
5734
- !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
5735
- columns: gridVisibility.includes("y"),
5736
- rows: gridVisibility.includes("x"),
5737
- numTicks: 4
6282
+ withTooltips && (withBandHighlight || onBandHighlightChange) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandHighlight, {
6283
+ visible: withBandHighlight,
6284
+ horizontal,
6285
+ onChange: onBandHighlightChange
6286
+ }),
6287
+ !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
6288
+ ...wholeNumberTicksProps,
6289
+ children: (valueTicks) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
6290
+ columns: gridVisibility.includes("y"),
6291
+ rows: false,
6292
+ numTicks: chartOptions.axis.x.numTicks,
6293
+ tickValues: (horizontal ? valueTicks : void 0) ?? chartOptions.axis.x.tickValues
6294
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
6295
+ columns: false,
6296
+ rows: gridVisibility.includes("x"),
6297
+ numTicks: chartOptions.axis.y.numTicks,
6298
+ tickValues: (horizontal ? void 0 : valueTicks) ?? chartOptions.axis.y.tickValues
6299
+ })] })
5738
6300
  }),
5739
6301
  withPatterns && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("defs", { children: dataSorted.map((seriesData, index) => renderPattern(index, getElementStyles({
5740
6302
  data: seriesData,
@@ -5771,8 +6333,26 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5771
6333
  colorAccessor: getBarBackground(index)
5772
6334
  }, seriesData?.label))
5773
6335
  }),
5774
- !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.x }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, { ...chartOptions.axis.y })] }),
6336
+ (withTooltips || onPointerDown || onPointerUp) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandTooltip, {
6337
+ keys: primaryKeys,
6338
+ groupPadding,
6339
+ withTooltips,
6340
+ onPointerDown,
6341
+ onPointerUp
6342
+ }),
6343
+ !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
6344
+ ...wholeNumberTicksProps,
6345
+ children: (valueTicks) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
6346
+ ...chartOptions.axis.x,
6347
+ ...horizontal && valueTicks ? { tickValues: valueTicks } : {}
6348
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
6349
+ ...chartOptions.axis.y,
6350
+ ...!horizontal && valueTicks ? { tickValues: valueTicks } : {}
6351
+ })] })
6352
+ }),
5775
6353
  withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
6354
+ tooltipPlacement,
6355
+ tooltipAnchorTop,
5776
6356
  detectBounds: true,
5777
6357
  snapTooltipToDatumX: true,
5778
6358
  snapTooltipToDatumY: true,
@@ -6594,8 +7174,11 @@ var heatmap_chart_module_default = {
6594
7174
  "heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
6595
7175
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
6596
7176
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
7177
+ "heatmap-chart__grid--flex-gaps": "a8ccharts-O3YMOW-heatmap-chart__grid--flex-gaps",
6597
7178
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
7179
+ "heatmap-chart__group-label": "a8ccharts-O3YMOW-heatmap-chart__group-label",
6598
7180
  "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
7181
+ "heatmap-chart__legend-scale--bar": "a8ccharts-O3YMOW-heatmap-chart__legend-scale--bar",
6599
7182
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
6600
7183
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
6601
7184
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
@@ -6645,7 +7228,7 @@ const getNormalizedValue = (value, extent) => {
6645
7228
  const HeatmapContext = (0, react.createContext)(null);
6646
7229
  //#endregion
6647
7230
  //#region src/charts/heatmap-chart/private/heatmap-legend.tsx
6648
- const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
7231
+ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }) => {
6649
7232
  const context = (0, react.useContext)(HeatmapContext);
6650
7233
  const { legend } = useGlobalChartsTheme();
6651
7234
  if (!context) return null;
@@ -6664,11 +7247,12 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6664
7247
  }),
6665
7248
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
6666
7249
  direction: "row",
6667
- gap: "xs",
7250
+ gap: variant === "bar" ? void 0 : "xs",
7251
+ "aria-hidden": "true",
7252
+ className: variant === "bar" ? heatmap_chart_module_default["heatmap-chart__legend-scale--bar"] : void 0,
6668
7253
  children: Array.from({ length: steps }, (_, index) => {
6669
7254
  const intensity = steps <= 1 ? 1 : index / (steps - 1);
6670
7255
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6671
- "aria-hidden": "true",
6672
7256
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6673
7257
  style: {
6674
7258
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
@@ -6687,16 +7271,219 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6687
7271
  });
6688
7272
  };
6689
7273
  //#endregion
7274
+ //#region src/charts/heatmap-chart/private/column-groups.ts
7275
+ const isSpan = (span) => Number.isInteger(span) && span > 0;
7276
+ /**
7277
+ * Validates column groups and lays them over the columns.
7278
+ *
7279
+ * @param groups - Groups as passed to the chart.
7280
+ * @param columnCount - Number of data columns.
7281
+ * @param firstLine - Grid column line of the first data column.
7282
+ * @return Grid column lines for every column and group; no groups when the input was unusable.
7283
+ */
7284
+ const resolveColumnGroups = (groups, columnCount, firstLine) => {
7285
+ const ungrouped = () => ({
7286
+ columns: Array.from({ length: columnCount }, (_, index) => ({
7287
+ line: firstLine + index,
7288
+ gapBefore: false
7289
+ })),
7290
+ groups: []
7291
+ });
7292
+ if (!groups || groups.length === 0 || columnCount === 0) return ungrouped();
7293
+ const spans = groups.map((group) => group.span);
7294
+ const total = spans.reduce((sum, span) => sum + span, 0);
7295
+ if (!spans.every(isSpan) || total > columnCount) {
7296
+ warnOnce(`heatmap:columnGroups:${JSON.stringify(spans)}:${columnCount}`, `columnGroups spans ${JSON.stringify(spans)} must be positive integers that fit the ${columnCount} columns, so no groups are drawn.`);
7297
+ return ungrouped();
7298
+ }
7299
+ const columns = [];
7300
+ const laidOut = [];
7301
+ let line = firstLine;
7302
+ groups.forEach((group, groupIndex) => {
7303
+ if (groupIndex > 0) line += 1;
7304
+ laidOut.push({
7305
+ ...group,
7306
+ line
7307
+ });
7308
+ for (let offset = 0; offset < group.span; offset++) {
7309
+ columns.push({
7310
+ line,
7311
+ gapBefore: groupIndex > 0 && offset === 0,
7312
+ group: groupIndex
7313
+ });
7314
+ line += 1;
7315
+ }
7316
+ });
7317
+ while (columns.length < columnCount) {
7318
+ columns.push({
7319
+ line,
7320
+ gapBefore: false
7321
+ });
7322
+ line += 1;
7323
+ }
7324
+ return {
7325
+ columns,
7326
+ groups: laidOut
7327
+ };
7328
+ };
7329
+ //#endregion
7330
+ //#region src/charts/heatmap-chart/private/keyboard-navigation.ts
7331
+ const ARROW_KEYS = [
7332
+ "ArrowLeft",
7333
+ "ArrowRight",
7334
+ "ArrowUp",
7335
+ "ArrowDown"
7336
+ ];
7337
+ const PAGE_KEYS = ["PageUp", "PageDown"];
7338
+ const isNavigationKey = (key, mode) => ARROW_KEYS.includes(key) || mode === "calendar" && PAGE_KEYS.includes(key);
7339
+ const inGrid = (grid, { column, row }) => column >= 0 && column < grid.columns && row >= 0 && row < grid.rows;
7340
+ const firstSelectable = (grid, cells) => cells.find((cell) => inGrid(grid, cell) && !grid.isInert(cell.column, cell.row));
7341
+ /**
7342
+ * Column-major scan for the first selectable cell.
7343
+ *
7344
+ * @param grid - The grid.
7345
+ * @return The cell, or undefined when every slot is inert.
7346
+ */
7347
+ const firstGridCell = (grid) => {
7348
+ for (let column = 0; column < grid.columns; column++) for (let row = 0; row < grid.rows; row++) if (!grid.isInert(column, row)) return {
7349
+ column,
7350
+ row
7351
+ };
7352
+ };
7353
+ /**
7354
+ * Steps across the grid in the key's direction, past inert slots, staying put at the edge.
7355
+ *
7356
+ * @param grid - The grid.
7357
+ * @param from - The selected cell.
7358
+ * @param key - The arrow key.
7359
+ * @return The next cell, or undefined to keep the selection.
7360
+ */
7361
+ const stepGridCell = (grid, from, key) => {
7362
+ const step = {
7363
+ ArrowRight: [1, 0],
7364
+ ArrowLeft: [-1, 0],
7365
+ ArrowDown: [0, 1],
7366
+ ArrowUp: [0, -1]
7367
+ }[key];
7368
+ if (!step) return;
7369
+ const [stepColumn, stepRow] = step;
7370
+ let cell = from;
7371
+ do
7372
+ cell = {
7373
+ column: cell.column + stepColumn,
7374
+ row: cell.row + stepRow
7375
+ };
7376
+ while (inGrid(grid, cell) && grid.isInert(cell.column, cell.row));
7377
+ return inGrid(grid, cell) ? cell : void 0;
7378
+ };
7379
+ /**
7380
+ * Every slot of the blocks in reading order: block by block, each a row at a time.
7381
+ *
7382
+ * @param grid - The grid.
7383
+ * @param blocks - The blocks, left to right.
7384
+ * @return The slots in order.
7385
+ */
7386
+ const readingOrder = (grid, blocks) => blocks.flatMap((block) => Array.from({ length: grid.rows }, (_row, row) => Array.from({ length: block.end - block.start }, (_offset, offset) => ({
7387
+ column: block.start + offset,
7388
+ row
7389
+ }))).flat());
7390
+ const blockOf = (blocks, column) => blocks.findIndex((block) => column >= block.start && column < block.end);
7391
+ /**
7392
+ * The slots after a cell down its column (or before it, going up), continuing at the
7393
+ * same column offset of each following block.
7394
+ *
7395
+ * @param grid - The grid.
7396
+ * @param blocks - The blocks, left to right.
7397
+ * @param from - The selected cell.
7398
+ * @param direction - 1 downwards, -1 upwards.
7399
+ * @return The slots in order.
7400
+ */
7401
+ const columnOrderFrom = (grid, blocks, from, direction) => {
7402
+ const blockIndex = blockOf(blocks, from.column);
7403
+ if (blockIndex < 0) return [];
7404
+ const offset = from.column - blocks[blockIndex].start;
7405
+ const cells = [];
7406
+ const pushRows = (column, startRow) => {
7407
+ for (let row = startRow; row >= 0 && row < grid.rows; row += direction) cells.push({
7408
+ column,
7409
+ row
7410
+ });
7411
+ };
7412
+ pushRows(from.column, from.row + direction);
7413
+ for (let index = blockIndex + direction; index >= 0 && index < blocks.length; index += direction) {
7414
+ const column = blocks[index].start + offset;
7415
+ if (column < blocks[index].end) pushRows(column, direction === 1 ? 0 : grid.rows - 1);
7416
+ }
7417
+ return cells;
7418
+ };
7419
+ /**
7420
+ * The first selectable cell in calendar reading order.
7421
+ *
7422
+ * @param grid - The grid.
7423
+ * @param blocks - The blocks, left to right.
7424
+ * @return The cell, or undefined when every slot is inert.
7425
+ */
7426
+ const firstCalendarCell = (grid, blocks) => firstSelectable(grid, readingOrder(grid, blocks));
7427
+ /**
7428
+ * Steps through the blocks as pages of a calendar: Left/Right by one slot in reading
7429
+ * order, Up/Down by one row (into the neighboring block past the edge), Page Up/Down
7430
+ * to the same slot of the neighboring block or its nearest selectable cell.
7431
+ *
7432
+ * @param grid - The grid.
7433
+ * @param blocks - The blocks, left to right.
7434
+ * @param from - The selected cell.
7435
+ * @param key - The navigation key.
7436
+ * @return The next cell, or undefined to keep the selection.
7437
+ */
7438
+ const stepCalendarCell = (grid, blocks, from, key) => {
7439
+ if (key === "ArrowRight" || key === "ArrowLeft") {
7440
+ const order = readingOrder(grid, blocks);
7441
+ const index = order.findIndex((cell) => cell.column === from.column && cell.row === from.row);
7442
+ if (index < 0) return;
7443
+ const ahead = key === "ArrowRight" ? order.slice(index + 1) : order.slice(0, index).reverse();
7444
+ return firstSelectable(grid, ahead);
7445
+ }
7446
+ if (key === "ArrowDown" || key === "ArrowUp") return firstSelectable(grid, columnOrderFrom(grid, blocks, from, key === "ArrowDown" ? 1 : -1));
7447
+ const blockIndex = blockOf(blocks, from.column);
7448
+ const target = blocks[blockIndex + (key === "PageDown" ? 1 : -1)];
7449
+ if (blockIndex < 0 || !target) return;
7450
+ const same = {
7451
+ column: target.start + (from.column - blocks[blockIndex].start),
7452
+ row: from.row
7453
+ };
7454
+ const page = readingOrder(grid, [target]);
7455
+ const at = page.findIndex((cell) => cell.row > same.row || cell.row === same.row && cell.column >= same.column);
7456
+ const after = at === -1 ? [] : page.slice(at);
7457
+ const before = page.slice(0, at === -1 ? page.length : at).reverse();
7458
+ return firstSelectable(grid, after) ?? firstSelectable(grid, before);
7459
+ };
7460
+ //#endregion
6690
7461
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
6691
7462
  const CELL_MIX_FLOOR = .15;
6692
7463
  const NO_ROW_LABELS = [];
6693
- const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, primaryColor, gap = "md", withTooltips = false, renderTooltip, children }) => {
7464
+ const TOOLTIP_BOX_STYLES = {
7465
+ light: {
7466
+ ..._visx_tooltip.defaultStyles,
7467
+ zIndex: 3
7468
+ },
7469
+ dark: { zIndex: 3 }
7470
+ };
7471
+ const cellName = (info) => info.cellLabel || [
7472
+ info.groupLabel,
7473
+ info.columnLabel,
7474
+ info.rowLabel
7475
+ ].filter(Boolean).join(" ");
7476
+ 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, tooltipVariant = "light", tooltipStyle, children }) => {
6694
7477
  const chartId = useChartId(providedChartId);
7478
+ const tooltipBoxStyle = tooltipStyle ? {
7479
+ ...TOOLTIP_BOX_STYLES[tooltipVariant],
7480
+ ...tooltipStyle
7481
+ } : TOOLTIP_BOX_STYLES[tooltipVariant];
6695
7482
  const { getElementStyles, theme } = useGlobalChartsContext();
6696
7483
  const scopeElement = useChartScopeElement();
6697
7484
  const { heatmapChart: heatmapChartSettings } = theme;
6698
7485
  const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
6699
- const [selectedIndex, setSelectedIndex] = (0, react.useState)();
7486
+ const [selected, setSelected] = (0, react.useState)();
6700
7487
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
6701
7488
  const standaloneScopeClass = useStandaloneScopeClass();
6702
7489
  const containerRef = (0, react.useRef)(null);
@@ -6715,69 +7502,75 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6715
7502
  }), [extent, primaryColorHex]);
6716
7503
  const columns = data.length;
6717
7504
  const rows = Math.max(0, ...data.map((column) => column.data.length));
6718
- const { compactCellGap, compactCellSize } = heatmapChartSettings;
7505
+ const groupLayout = (0, react.useMemo)(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
7506
+ const { compactCellGap, compactCellSize, groupGap } = heatmapChartSettings;
6719
7507
  const drawValues = showValues ?? !compact;
6720
7508
  const buildTooltipData = (0, react.useCallback)((columnIndex, rowIndex) => {
6721
7509
  const cell = data[columnIndex]?.data[rowIndex];
7510
+ const group = groupLayout.columns[columnIndex]?.group;
6722
7511
  return {
6723
7512
  value: cell?.value ?? null,
6724
7513
  rowLabel: rowLabels[rowIndex],
6725
7514
  columnLabel: data[columnIndex]?.label,
7515
+ groupLabel: group === void 0 ? void 0 : groupLayout.groups[group]?.label,
6726
7516
  cellLabel: cell?.label,
6727
7517
  row: rowIndex,
6728
7518
  column: columnIndex
6729
7519
  };
6730
- }, [data, rowLabels]);
7520
+ }, [
7521
+ data,
7522
+ rowLabels,
7523
+ groupLayout
7524
+ ]);
6731
7525
  const onChartBlur = (0, react.useCallback)(() => {
6732
- setSelectedIndex(void 0);
7526
+ setSelected(void 0);
6733
7527
  hideTooltip();
6734
7528
  }, [hideTooltip]);
6735
7529
  const isCellInert = (0, react.useCallback)((col, row) => {
6736
7530
  const cell = data[col]?.data[row];
6737
7531
  return cell?.hidden === true || cell?.placeholder === true;
6738
7532
  }, [data]);
7533
+ const blocks = (0, react.useMemo)(() => {
7534
+ const grouped = groupLayout.groups.map((group, index) => {
7535
+ const start = groupLayout.columns.findIndex((column) => column.group === index);
7536
+ return {
7537
+ start,
7538
+ end: start + group.span
7539
+ };
7540
+ });
7541
+ const groupedEnd = grouped.length ? grouped[grouped.length - 1].end : 0;
7542
+ return groupedEnd < columns ? [...grouped, {
7543
+ start: groupedEnd,
7544
+ end: columns
7545
+ }] : grouped;
7546
+ }, [groupLayout, columns]);
6739
7547
  const onChartKeyDown = (0, react.useCallback)((event) => {
6740
- if (![
6741
- "ArrowLeft",
6742
- "ArrowRight",
6743
- "ArrowUp",
6744
- "ArrowDown",
6745
- "Escape",
6746
- "Tab"
6747
- ].includes(event.key)) return;
6748
7548
  if (event.key === "Tab" || event.key === "Escape") {
6749
- setSelectedIndex(void 0);
7549
+ setSelected(void 0);
6750
7550
  hideTooltip();
6751
7551
  return;
6752
7552
  }
7553
+ if (!isNavigationKey(event.key, keyboardNavigation)) return;
6753
7554
  event.preventDefault();
6754
- if (selectedIndex === void 0) {
6755
- for (let index = 0; index < columns * rows; index++) if (!isCellInert(Math.floor(index / rows), index % rows)) {
6756
- setSelectedIndex(index);
6757
- return;
6758
- }
7555
+ const grid = {
7556
+ columns,
7557
+ rows,
7558
+ isInert: isCellInert
7559
+ };
7560
+ if (selected === void 0) {
7561
+ setSelected(keyboardNavigation === "calendar" ? firstCalendarCell(grid, blocks) : firstGridCell(grid));
6759
7562
  return;
6760
7563
  }
6761
- let stepCol = 0;
6762
- let stepRow = 0;
6763
- if (event.key === "ArrowRight") stepCol = 1;
6764
- else if (event.key === "ArrowLeft") stepCol = -1;
6765
- else if (event.key === "ArrowDown") stepRow = 1;
6766
- else if (event.key === "ArrowUp") stepRow = -1;
6767
- let col = Math.floor(selectedIndex / rows);
6768
- let row = selectedIndex % rows;
6769
- do {
6770
- col += stepCol;
6771
- row += stepRow;
6772
- } while (col >= 0 && col < columns && row >= 0 && row < rows && isCellInert(col, row));
6773
- if (col < 0 || col >= columns || row < 0 || row >= rows) return;
6774
- setSelectedIndex(col * rows + row);
7564
+ const next = keyboardNavigation === "calendar" ? stepCalendarCell(grid, blocks, selected, event.key) : stepGridCell(grid, selected, event.key);
7565
+ if (next) setSelected(next);
6775
7566
  }, [
6776
7567
  rows,
6777
7568
  columns,
6778
- selectedIndex,
7569
+ selected,
6779
7570
  hideTooltip,
6780
- isCellInert
7571
+ isCellInert,
7572
+ keyboardNavigation,
7573
+ blocks
6781
7574
  ]);
6782
7575
  const handleCellMouseMove = (0, react.useCallback)((event) => {
6783
7576
  if (!withTooltips) return;
@@ -6797,35 +7590,47 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6797
7590
  buildTooltipData,
6798
7591
  getTooltipOrigin
6799
7592
  ]);
7593
+ const getCellElement = (0, react.useCallback)((col, row) => typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null, [chartId]);
6800
7594
  const handleCellMouseLeave = (0, react.useCallback)(() => {
6801
- if (withTooltips && selectedIndex === void 0) hideTooltip();
7595
+ if (withTooltips && selected === void 0) hideTooltip();
6802
7596
  }, [
6803
7597
  withTooltips,
6804
- selectedIndex,
7598
+ selected,
6805
7599
  hideTooltip
6806
7600
  ]);
6807
7601
  (0, react.useEffect)(() => {
6808
- if (!withTooltips || selectedIndex === void 0) return;
7602
+ if (selected && (selected.column >= columns || selected.row >= rows)) setSelected(void 0);
7603
+ }, [
7604
+ selected,
7605
+ columns,
7606
+ rows
7607
+ ]);
7608
+ (0, react.useEffect)(() => {
7609
+ if (selected === void 0) return;
7610
+ getCellElement(selected.column, selected.row)?.scrollIntoView?.({
7611
+ block: "nearest",
7612
+ inline: "nearest"
7613
+ });
7614
+ }, [selected, getCellElement]);
7615
+ (0, react.useEffect)(() => {
7616
+ if (!withTooltips || selected === void 0) return;
6809
7617
  const origin = getTooltipOrigin();
6810
7618
  if (!origin) return;
6811
- const col = Math.floor(selectedIndex / rows);
6812
- const row = selectedIndex % rows;
6813
- const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
7619
+ const rect = getCellElement(selected.column, selected.row)?.getBoundingClientRect();
6814
7620
  showTooltip({
6815
7621
  tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
6816
7622
  tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
6817
- tooltipData: buildTooltipData(col, row)
7623
+ tooltipData: buildTooltipData(selected.column, selected.row)
6818
7624
  });
6819
7625
  }, [
6820
- selectedIndex,
7626
+ selected,
6821
7627
  withTooltips,
6822
- rows,
6823
- chartId,
7628
+ getCellElement,
6824
7629
  buildTooltipData,
6825
7630
  showTooltip,
6826
7631
  getTooltipOrigin
6827
7632
  ]);
6828
- const defaultRenderTooltip = (0, react.useCallback)((info) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim() }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value) })] }), []);
7633
+ const defaultRenderTooltip = (0, react.useCallback)((info) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: cellName(info) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value) })] }), []);
6829
7634
  if (!columns || !rows) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
6830
7635
  className: (0, clsx.default)("heatmap-chart", heatmap_chart_module_default["heatmap-chart"], className),
6831
7636
  style: {
@@ -6839,10 +7644,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6839
7644
  });
6840
7645
  const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
6841
7646
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
7647
+ const hasColumnLabels = data.some((column) => Boolean(column.label));
7648
+ const hasGroups = groupLayout.groups.length > 0;
7649
+ const columnLine = (columnIndex) => groupLayout.columns[columnIndex].line;
7650
+ const firstDataRow = hasColumnLabels ? 2 : 1;
7651
+ const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
7652
+ const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
6842
7653
  const gridStyle = {
6843
7654
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6844
- gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
6845
- gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
7655
+ gridTemplateColumns: `auto ${data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ")}`,
7656
+ gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
6846
7657
  };
6847
7658
  if (compact) {
6848
7659
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
@@ -6855,7 +7666,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6855
7666
  [heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
6856
7667
  };
6857
7668
  };
6858
- const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
7669
+ const activeDescendant = selected ? `${chartId}-cell-${selected.column}-${selected.row}` : void 0;
6859
7670
  const heightCapped = !compact && Boolean(maxCellHeight);
6860
7671
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatmapContext.Provider, {
6861
7672
  value: heatmapContext,
@@ -6875,7 +7686,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6875
7686
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6876
7687
  ref: containerRef,
6877
7688
  role: "grid",
6878
- "aria-label": (0, _wordpress_i18n.__)("Heatmap chart", "jetpack-charts"),
7689
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Heatmap chart", "jetpack-charts"),
6879
7690
  "aria-rowcount": rows,
6880
7691
  "aria-colcount": columns,
6881
7692
  "aria-activedescendant": activeDescendant,
@@ -6884,76 +7695,115 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6884
7695
  onKeyDown: onChartKeyDown,
6885
7696
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__grid"], {
6886
7697
  [heatmap_chart_module_default["heatmap-chart__grid--compact"]]: compact,
7698
+ [heatmap_chart_module_default["heatmap-chart__grid--flex-gaps"]]: compact && hasGroups,
6887
7699
  [heatmap_chart_module_default["heatmap-chart__grid--height-capped"]]: heightCapped
6888
7700
  }),
6889
7701
  style: gridStyle,
6890
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6891
- role: "row",
6892
- "aria-hidden": "true",
6893
- className: heatmap_chart_module_default["heatmap-chart__row"],
6894
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6895
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
6896
- [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
6897
- ...summaryGaps(columnIndex)
6898
- }),
6899
- children: column.label
6900
- }, `col-${columnIndex}`))]
6901
- }), Array.from({ length: rows }).map((_row, rowIndex) => {
6902
- const labelVisible = !compact || rowIndex % 2 === 0;
6903
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7702
+ children: [
7703
+ hasColumnLabels && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6904
7704
  role: "row",
6905
- "aria-rowindex": rowIndex + 1,
7705
+ "aria-hidden": "true",
6906
7706
  className: heatmap_chart_module_default["heatmap-chart__row"],
6907
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6908
- "aria-hidden": "true",
6909
- className: heatmap_chart_module_default["heatmap-chart__row-label"],
6910
- children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
6911
- }), data.map((column, columnIndex) => {
6912
- const cell = column.data[rowIndex];
6913
- if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6914
- "aria-hidden": "true",
6915
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--hidden"])
6916
- }, `cell-${columnIndex}-${rowIndex}`);
6917
- if (cell?.placeholder) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7707
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
7708
+ gridColumn: 1,
7709
+ gridRow: 1
7710
+ } }), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7711
+ style: {
7712
+ gridColumn: columnLine(columnIndex),
7713
+ gridRow: 1
7714
+ },
7715
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
7716
+ [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
7717
+ ...summaryGaps(columnIndex)
7718
+ }),
7719
+ children: column.label
7720
+ }, `col-${columnIndex}`))]
7721
+ }),
7722
+ Array.from({ length: rows }).map((_row, rowIndex) => {
7723
+ const labelVisible = !compact || rowIndex % 2 === 0;
7724
+ const gridRow = firstDataRow + rowIndex;
7725
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7726
+ role: "row",
7727
+ "aria-rowindex": rowIndex + 1,
7728
+ className: heatmap_chart_module_default["heatmap-chart__row"],
7729
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6918
7730
  "aria-hidden": "true",
6919
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--placeholder"])
6920
- }, `cell-${columnIndex}-${rowIndex}`);
6921
- const value = cell?.value ?? null;
6922
- const present = isPresent(value);
6923
- const filled = present && !column.summary;
6924
- const normalized = filled ? getNormalizedValue(value, extent) : 0;
6925
- const flatIndex = columnIndex * rows + rowIndex;
6926
- const info = buildTooltipData(columnIndex, rowIndex);
6927
- const accessibleLabel = `${info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim()}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
6928
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6929
- id: `${chartId}-cell-${columnIndex}-${rowIndex}`,
6930
- role: "gridcell",
6931
- tabIndex: -1,
6932
- "aria-colindex": columnIndex + 1,
6933
- "aria-label": accessibleLabel,
6934
- "data-column": columnIndex,
6935
- "data-row": rowIndex,
6936
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
6937
- [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
6938
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
6939
- [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
6940
- ...summaryGaps(columnIndex),
6941
- [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
6942
- }),
6943
- style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6944
- onMouseMove: handleCellMouseMove,
6945
- onMouseLeave: handleCellMouseLeave,
6946
- children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6947
- className: heatmap_chart_module_default["heatmap-chart__cell-value"],
6948
- children: (0, _automattic_number_formatters.formatNumberCompact)(value)
6949
- })
6950
- }, `cell-${columnIndex}-${rowIndex}`);
6951
- })]
6952
- }, `row-${rowIndex}`);
6953
- })]
7731
+ className: heatmap_chart_module_default["heatmap-chart__row-label"],
7732
+ style: {
7733
+ gridColumn: 1,
7734
+ gridRow
7735
+ },
7736
+ children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
7737
+ }), data.map((column, columnIndex) => {
7738
+ const cell = column.data[rowIndex];
7739
+ const placement = {
7740
+ gridColumn: columnLine(columnIndex),
7741
+ gridRow
7742
+ };
7743
+ if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7744
+ "aria-hidden": "true",
7745
+ style: placement,
7746
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--hidden"])
7747
+ }, `cell-${columnIndex}-${rowIndex}`);
7748
+ if (cell?.placeholder) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7749
+ "aria-hidden": "true",
7750
+ style: placement,
7751
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--placeholder"])
7752
+ }, `cell-${columnIndex}-${rowIndex}`);
7753
+ const value = cell?.value ?? null;
7754
+ const present = isPresent(value);
7755
+ const filled = present && !column.summary;
7756
+ const normalized = filled ? getNormalizedValue(value, extent) : 0;
7757
+ const info = buildTooltipData(columnIndex, rowIndex);
7758
+ const accessibleLabel = `${cellName(info)}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
7759
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7760
+ id: `${chartId}-cell-${columnIndex}-${rowIndex}`,
7761
+ role: "gridcell",
7762
+ tabIndex: -1,
7763
+ "aria-colindex": columnIndex + 1,
7764
+ "aria-label": accessibleLabel,
7765
+ "data-column": columnIndex,
7766
+ "data-row": rowIndex,
7767
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
7768
+ [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
7769
+ [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
7770
+ [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
7771
+ ...summaryGaps(columnIndex),
7772
+ [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
7773
+ }),
7774
+ style: {
7775
+ ...placement,
7776
+ ...filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : {}
7777
+ },
7778
+ onMouseMove: handleCellMouseMove,
7779
+ onMouseLeave: handleCellMouseLeave,
7780
+ children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7781
+ className: heatmap_chart_module_default["heatmap-chart__cell-value"],
7782
+ children: (0, _automattic_number_formatters.formatNumberCompact)(value)
7783
+ })
7784
+ }, `cell-${columnIndex}-${rowIndex}`);
7785
+ })]
7786
+ }, `row-${rowIndex}`);
7787
+ }),
7788
+ hasGroups && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7789
+ role: "row",
7790
+ "aria-hidden": "true",
7791
+ className: heatmap_chart_module_default["heatmap-chart__row"],
7792
+ children: groupLayout.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7793
+ className: heatmap_chart_module_default["heatmap-chart__group-label"],
7794
+ style: {
7795
+ gridColumn: `${group.line} / span ${group.span}`,
7796
+ gridRow: firstDataRow + rows
7797
+ },
7798
+ children: group.label
7799
+ }, `group-${groupIndex}`))
7800
+ })
7801
+ ]
6954
7802
  }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
6955
7803
  top: tooltipTop,
6956
7804
  left: tooltipLeft,
7805
+ className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
7806
+ style: tooltipBoxStyle,
6957
7807
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6958
7808
  className: standaloneScopeClass,
6959
7809
  role: "tooltip",
@@ -6989,6 +7839,43 @@ const KEY_OPTIONS = {
6989
7839
  day: "2-digit"
6990
7840
  };
6991
7841
  const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
7842
+ /**
7843
+ * Label formatters for UTC proxies, so both calendar layouts name a day alike.
7844
+ *
7845
+ * `gregory` keeps a locale whose default calendar is not Gregorian (fa-IR) from
7846
+ * splitting one grid month across two names.
7847
+ *
7848
+ * @param locale - BCP 47 locale.
7849
+ * @return Weekday, month and full-day formatters.
7850
+ */
7851
+ const civilLabelFormatters = (locale) => {
7852
+ const formatting = {
7853
+ locale,
7854
+ timeZone: "UTC"
7855
+ };
7856
+ return {
7857
+ formatWeekday: createDateFormatter({
7858
+ weekday: "short",
7859
+ calendar: "gregory"
7860
+ }, formatting),
7861
+ formatMonth: createDateFormatter({
7862
+ month: "short",
7863
+ calendar: "gregory"
7864
+ }, formatting),
7865
+ formatMonthYear: createDateFormatter({
7866
+ month: "short",
7867
+ year: "numeric",
7868
+ calendar: "gregory"
7869
+ }, formatting),
7870
+ formatDay: createDateFormatter({
7871
+ weekday: "short",
7872
+ month: "short",
7873
+ day: "numeric",
7874
+ year: "numeric",
7875
+ calendar: "gregory"
7876
+ }, formatting)
7877
+ };
7878
+ };
6992
7879
  const pad = (value, length) => String(value).padStart(length, "0");
6993
7880
  /**
6994
7881
  * The day a UTC proxy stands for.
@@ -7154,18 +8041,7 @@ const buildCalendarHeatmapData = (series, options = {}) => {
7154
8041
  const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
7155
8042
  const requestedMinDate = civilDate(requestedMinDayKey);
7156
8043
  const gridMaxDate = civilDate(gridMaxDayKey);
7157
- const labelFormatting = {
7158
- locale,
7159
- timeZone: "UTC"
7160
- };
7161
- const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
7162
- const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
7163
- const formatDay = createDateFormatter({
7164
- weekday: "short",
7165
- month: "short",
7166
- day: "numeric",
7167
- year: "numeric"
7168
- }, labelFormatting);
8044
+ const { formatWeekday, formatMonth, formatDay } = civilLabelFormatters(locale);
7169
8045
  const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
7170
8046
  const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
7171
8047
  const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
@@ -7206,6 +8082,89 @@ const buildCalendarHeatmapData = (series, options = {}) => {
7206
8082
  };
7207
8083
  };
7208
8084
  //#endregion
8085
+ //#region src/charts/heatmap-chart/build-month-calendar-data.ts
8086
+ /** Week rows per month, fixed so every month block is the same height. */
8087
+ const MONTH_ROWS = 6;
8088
+ const DAYS_PER_WEEK = 7;
8089
+ const startOfCivilMonth = (date) => {
8090
+ const first = new Date(date.getTime());
8091
+ first.setUTCDate(1);
8092
+ return first;
8093
+ };
8094
+ const startOfNextCivilMonth = (date) => {
8095
+ const next = startOfCivilMonth(date);
8096
+ next.setUTCMonth(next.getUTCMonth() + 1);
8097
+ return next;
8098
+ };
8099
+ /**
8100
+ * Lay a day-keyed map out as one month calendar per month, weekdays across and
8101
+ * weeks down, for `HeatmapChart` with `columnGroups`.
8102
+ *
8103
+ * @param valueByDay - Value per `yyyy-MM-dd`. A missing day is `null`.
8104
+ * @param range - The measured days; every month from `start`'s through `end`'s is drawn.
8105
+ * @param options - Week start and label locale.
8106
+ * @return Columns and one group per month; empty for an invalid or reversed range.
8107
+ */
8108
+ const buildMonthCalendarHeatmapData = (valueByDay, range, options = {}) => {
8109
+ const start = civilDate(range.start);
8110
+ const end = civilDate(range.end);
8111
+ if (!start || !end || start.getTime() > end.getTime()) {
8112
+ warnOnce(`heatmap:monthRange:${range.start}:${range.end}`, `range ${JSON.stringify(range)} must be two \`yyyy-MM-dd\` days with start on or before end, so the calendar is empty.`);
8113
+ return {
8114
+ data: [],
8115
+ columnGroups: []
8116
+ };
8117
+ }
8118
+ const startKey = civilKey(start);
8119
+ const endKey = civilKey(end);
8120
+ const weekStartsOn = options.weekStartsOn ?? 1;
8121
+ const { locale } = sanitizeFormatting({ locale: options.locale });
8122
+ for (const key of Object.keys(valueByDay)) if (!civilDate(key)) warnOnce(`heatmap:monthKey:${key}`, `${JSON.stringify(key)} is not a \`yyyy-MM-dd\` day, so its value is left out of the calendar.`);
8123
+ const readValue = (key) => {
8124
+ const value = valueByDay[key];
8125
+ return isPresent(value) ? value : null;
8126
+ };
8127
+ const { formatMonth, formatMonthYear, formatDay } = civilLabelFormatters(locale);
8128
+ const data = [];
8129
+ const columnGroups = [];
8130
+ const lastMonthStart = startOfCivilMonth(end);
8131
+ const formatGroup = (lastMonthStart.getUTCFullYear() - start.getUTCFullYear()) * 12 + lastMonthStart.getUTCMonth() - start.getUTCMonth() + 1 > 12 ? formatMonthYear : formatMonth;
8132
+ for (let monthStart = startOfCivilMonth(start); monthStart.getTime() <= lastMonthStart.getTime(); monthStart = startOfNextCivilMonth(monthStart)) {
8133
+ const monthStartKey = civilKey(monthStart);
8134
+ const monthEndKey = civilKey(addCivilDays(startOfNextCivilMonth(monthStart), -1));
8135
+ const gridStart = startOfCivilWeek(monthStart, weekStartsOn);
8136
+ for (let weekday = 0; weekday < DAYS_PER_WEEK; weekday++) {
8137
+ const cells = [];
8138
+ for (let week = 0; week < MONTH_ROWS; week++) {
8139
+ const day = addCivilDays(gridStart, week * DAYS_PER_WEEK + weekday);
8140
+ const key = civilKey(day);
8141
+ if (key < monthStartKey || key > monthEndKey) cells.push({
8142
+ value: null,
8143
+ hidden: true
8144
+ });
8145
+ else if (key < startKey || key > endKey) cells.push({
8146
+ label: formatDay(day),
8147
+ value: null,
8148
+ placeholder: true
8149
+ });
8150
+ else cells.push({
8151
+ label: formatDay(day),
8152
+ value: readValue(key)
8153
+ });
8154
+ }
8155
+ data.push({ data: cells });
8156
+ }
8157
+ columnGroups.push({
8158
+ label: formatGroup(monthStart),
8159
+ span: DAYS_PER_WEEK
8160
+ });
8161
+ }
8162
+ return {
8163
+ data,
8164
+ columnGroups
8165
+ };
8166
+ };
8167
+ //#endregion
7209
8168
  //#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
7210
8169
  /**
7211
8170
  * `buildCalendarHeatmapData` with the locale and zone taken from
@@ -7242,6 +8201,36 @@ const useCalendarHeatmapData = (series, options = {}) => {
7242
8201
  ]);
7243
8202
  };
7244
8203
  //#endregion
8204
+ //#region src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts
8205
+ /**
8206
+ * `buildMonthCalendarHeatmapData` with the locale taken from `GlobalChartsProvider`
8207
+ * where the caller names none.
8208
+ *
8209
+ * @param valueByDay - Value per `yyyy-MM-dd`. Held by reference, so a caller that rebuilds it each render defeats the memo.
8210
+ * @param range - As for `buildMonthCalendarHeatmapData`.
8211
+ * @param options - As for `buildMonthCalendarHeatmapData`; `locale` wins over the provider.
8212
+ * @return Columns and groups for `HeatmapChart`.
8213
+ */
8214
+ const useMonthCalendarHeatmapData = (valueByDay, range, options = {}) => {
8215
+ const formatting = useChartFormatting();
8216
+ const locale = options.locale ?? formatting.locale;
8217
+ const { weekStartsOn } = options;
8218
+ const { start, end } = range;
8219
+ return (0, react.useMemo)(() => buildMonthCalendarHeatmapData(valueByDay, {
8220
+ start,
8221
+ end
8222
+ }, {
8223
+ locale,
8224
+ weekStartsOn
8225
+ }), [
8226
+ valueByDay,
8227
+ start,
8228
+ end,
8229
+ locale,
8230
+ weekStartsOn
8231
+ ]);
8232
+ };
8233
+ //#endregion
7245
8234
  //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
7246
8235
  /*!
7247
8236
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
@@ -7605,7 +8594,24 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
7605
8594
  tslib_1.__exportStar(require_dist$1(), exports);
7606
8595
  }));
7607
8596
  //#endregion
7608
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
8597
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.55.0/node_modules/@wordpress/warning/build-module/utils.mjs
8598
+ var logged = /* @__PURE__ */ new Set();
8599
+ //#endregion
8600
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.55.0/node_modules/@wordpress/warning/build-module/index.mjs
8601
+ function isDev() {
8602
+ return globalThis.SCRIPT_DEBUG === true;
8603
+ }
8604
+ function warning(message) {
8605
+ if (!isDev()) return;
8606
+ if (logged.has(message)) return;
8607
+ console.warn(message);
8608
+ try {
8609
+ throw Error(message);
8610
+ } catch {}
8611
+ logged.add(message);
8612
+ }
8613
+ //#endregion
8614
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
7609
8615
  function useUpdateEffect(effect, deps) {
7610
8616
  const mountedRef = (0, react.useRef)(false);
7611
8617
  (0, react.useEffect)(() => {
@@ -9243,7 +10249,7 @@ _createEmotion.css;
9243
10249
  _createEmotion.sheet;
9244
10250
  _createEmotion.cache;
9245
10251
  //#endregion
9246
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
10252
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
9247
10253
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
9248
10254
  var useCx = () => {
9249
10255
  const cache = __unsafe_useEmotionCache();
@@ -9350,7 +10356,7 @@ function memize(fn, options) {
9350
10356
  return memoized;
9351
10357
  }
9352
10358
  //#endregion
9353
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
10359
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
9354
10360
  var white = "#fff";
9355
10361
  var GRAY = {
9356
10362
  900: "#1e1e1e",
@@ -9400,8 +10406,8 @@ var UI = {
9400
10406
  border: THEME.gray[600],
9401
10407
  borderHover: THEME.gray[700],
9402
10408
  borderFocus: THEME.accent,
9403
- borderDisabled: THEME.gray[400],
9404
- textDisabled: THEME.gray[600],
10409
+ borderDisabled: `var(--wpds-color-stroke-interactive-neutral-disabled, #dbdbdb)`,
10410
+ textDisabled: `var(--wpds-color-foreground-interactive-neutral-disabled, #8d8d8d)`,
9405
10411
  darkGrayPlaceholder: `color-mix(in srgb, ${THEME.foreground}, transparent 38%)`,
9406
10412
  lightGrayPlaceholder: `color-mix(in srgb, ${THEME.background}, transparent 35%)`
9407
10413
  };
@@ -9429,7 +10435,7 @@ var COLORS = Object.freeze({
9429
10435
  ui: UI
9430
10436
  });
9431
10437
  //#endregion
9432
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/config-values.mjs
10438
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/config-values.mjs
9433
10439
  var CONTROL_HEIGHT = "36px";
9434
10440
  var CONTROL_PROPS = {
9435
10441
  controlPaddingX: 12,
@@ -9483,8 +10489,8 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
9483
10489
  transitionTimingFunctionControl: "cubic-bezier(0.12, 0.8, 0.32, 1)"
9484
10490
  });
9485
10491
  //#endregion
9486
- //#region ../../../node_modules/.pnpm/fast-deep-equal@3.1.3/node_modules/fast-deep-equal/es6/index.js
9487
- var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
10492
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
10493
+ var import_es6 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
9488
10494
  module.exports = function equal(a, b) {
9489
10495
  if (a === b) return true;
9490
10496
  if (a && b && typeof a == "object" && typeof b == "object") {
@@ -9528,31 +10534,8 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9528
10534
  }
9529
10535
  return a !== a && b !== b;
9530
10536
  };
9531
- }));
9532
- //#endregion
9533
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
9534
- var logged = /* @__PURE__ */ new Set();
9535
- //#endregion
9536
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
9537
- function isDev() {
9538
- return globalThis.SCRIPT_DEBUG === true;
9539
- }
9540
- function warning(message) {
9541
- if (!isDev()) return;
9542
- if (logged.has(message)) return;
9543
- console.warn(message);
9544
- try {
9545
- throw Error(message);
9546
- } catch {}
9547
- logged.add(message);
9548
- }
9549
- //#endregion
9550
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
9551
- var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
9552
- var ComponentsContext = (0, react.createContext)(
9553
- /** @type {Record<string, any>} */
9554
- {}
9555
- );
10537
+ })))(), 1);
10538
+ var ComponentsContext = (0, react.createContext)({});
9556
10539
  ComponentsContext.displayName = "ComponentsContext";
9557
10540
  var useComponentsContext = () => (0, react.useContext)(ComponentsContext);
9558
10541
  function useContextSystemBridge({ value }) {
@@ -9574,19 +10557,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
9574
10557
  };
9575
10558
  (0, react.memo)(BaseContextSystemProvider);
9576
10559
  //#endregion
9577
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/constants.mjs
10560
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/constants.mjs
9578
10561
  var COMPONENT_NAMESPACE = "data-wp-component";
9579
10562
  var CONNECTED_NAMESPACE = "data-wp-c16t";
9580
10563
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
9581
10564
  //#endregion
9582
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
10565
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
9583
10566
  var import_dist = require_dist();
9584
10567
  function getStyledClassName(namespace) {
9585
10568
  return `components-${(0, import_dist.paramCase)(namespace)}`;
9586
10569
  }
9587
10570
  var getStyledClassNameFromKey = memize(getStyledClassName);
9588
10571
  //#endregion
9589
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/context-connect.mjs
10572
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/context-connect.mjs
9590
10573
  function contextConnect(Component, namespace) {
9591
10574
  return _contextConnect(Component, namespace, { forwardsRef: true });
9592
10575
  }
@@ -9603,7 +10586,7 @@ function _contextConnect(Component, namespace, options) {
9603
10586
  });
9604
10587
  }
9605
10588
  //#endregion
9606
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/utils.mjs
10589
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/utils.mjs
9607
10590
  function getNamespace(componentName) {
9608
10591
  return { [COMPONENT_NAMESPACE]: componentName };
9609
10592
  }
@@ -9611,7 +10594,7 @@ function getConnectedNamespace() {
9611
10594
  return { [CONNECTED_NAMESPACE]: true };
9612
10595
  }
9613
10596
  //#endregion
9614
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
10597
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
9615
10598
  function useContextSystem(props, namespace) {
9616
10599
  const contextSystemProps = useComponentsContext();
9617
10600
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -9620,10 +10603,12 @@ function useContextSystem(props, namespace) {
9620
10603
  ...getConnectedNamespace(),
9621
10604
  ...getNamespace(namespace)
9622
10605
  };
9623
- const { _overrides: overrideProps, ...otherContextProps } = contextProps;
10606
+ const { _overrides, ...otherContextProps } = contextProps;
10607
+ const overrideProps = _overrides;
9624
10608
  const initialMergedProps = Object.entries(otherContextProps).length ? Object.assign({}, otherContextProps, props) : props;
10609
+ const propsWithChildren = initialMergedProps;
9625
10610
  const classes = useCx()(getStyledClassNameFromKey(namespace), props.className);
9626
- const rendered = typeof initialMergedProps.renderChildren === "function" ? initialMergedProps.renderChildren(initialMergedProps) : initialMergedProps.children;
10611
+ const rendered = typeof propsWithChildren.renderChildren === "function" ? propsWithChildren.renderChildren(initialMergedProps) : propsWithChildren.children;
9627
10612
  for (const key in initialMergedProps) finalComponentProps[key] = initialMergedProps[key];
9628
10613
  for (const key in overrideProps) finalComponentProps[key] = overrideProps[key];
9629
10614
  if (rendered !== void 0) finalComponentProps.children = rendered;
@@ -9631,7 +10616,7 @@ function useContextSystem(props, namespace) {
9631
10616
  return finalComponentProps;
9632
10617
  }
9633
10618
  //#endregion
9634
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
10619
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
9635
10620
  var visuallyHidden = {
9636
10621
  border: 0,
9637
10622
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -9647,7 +10632,7 @@ var visuallyHidden = {
9647
10632
  wordBreak: "normal"
9648
10633
  };
9649
10634
  //#endregion
9650
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
10635
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
9651
10636
  var customAttributeRegExp = /^(data|aria|x)-/i;
9652
10637
  var eventHandlerRegExp = /^on[A-Z]/;
9653
10638
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -9737,7 +10722,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
9737
10722
  }
9738
10723
  var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
9739
10724
  //#endregion
9740
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/view/component.mjs
10725
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/view/component.mjs
9741
10726
  function UnforwardedView({ css, ...restProps }, ref) {
9742
10727
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
9743
10728
  ref,
@@ -9746,7 +10731,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
9746
10731
  }
9747
10732
  var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
9748
10733
  //#endregion
9749
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
10734
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
9750
10735
  function UnconnectedVisuallyHidden(props, forwardedRef) {
9751
10736
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
9752
10737
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -9761,7 +10746,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
9761
10746
  }
9762
10747
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
9763
10748
  //#endregion
9764
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
10749
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
9765
10750
  var breakpoints = [
9766
10751
  "40em",
9767
10752
  "52em",
@@ -9795,7 +10780,7 @@ function useResponsiveValue(values, options = {}) {
9795
10780
  return array[index >= array.length ? array.length - 1 : index];
9796
10781
  }
9797
10782
  //#endregion
9798
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/utils.mjs
10783
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/grid/utils.mjs
9799
10784
  var ALIGNMENTS = {
9800
10785
  bottom: {
9801
10786
  alignItems: "flex-end",
@@ -9843,7 +10828,7 @@ function getAlignmentProps(alignment) {
9843
10828
  return alignment ? ALIGNMENTS[alignment] : {};
9844
10829
  }
9845
10830
  //#endregion
9846
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/hook.mjs
10831
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/grid/hook.mjs
9847
10832
  function useGrid(props) {
9848
10833
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
9849
10834
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -9885,7 +10870,7 @@ function useGrid(props) {
9885
10870
  };
9886
10871
  }
9887
10872
  //#endregion
9888
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/component.mjs
10873
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.1.0_@date-fns+tz@1.5.0_@types+react-dom@18.3.7_@types+react@18_c300d25e93ed6b8d3659e83df7c1508a/node_modules/@wordpress/components/build-module/grid/component.mjs
9889
10874
  function UnconnectedGrid(props, forwardedRef) {
9890
10875
  const gridProps = useGrid(props);
9891
10876
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -11171,6 +12156,7 @@ exports.SparklineUnresponsive = SparklineUnresponsive;
11171
12156
  exports.ThemeProvider = GlobalChartsProvider;
11172
12157
  exports.TrendIndicator = TrendIndicator;
11173
12158
  exports.buildCalendarHeatmapData = buildCalendarHeatmapData;
12159
+ exports.buildMonthCalendarHeatmapData = buildMonthCalendarHeatmapData;
11174
12160
  exports.defaultTheme = defaultTheme;
11175
12161
  exports.formatMetricValue = formatMetricValue;
11176
12162
  exports.formatPercentage = formatPercentage;
@@ -11195,6 +12181,7 @@ exports.useChartScopeElement = useChartScopeElement;
11195
12181
  exports.useGlobalChartsContext = useGlobalChartsContext;
11196
12182
  exports.useGlobalChartsTheme = useGlobalChartsTheme;
11197
12183
  exports.useLeaderboardLegendItems = useLeaderboardLegendItems;
12184
+ exports.useMonthCalendarHeatmapData = useMonthCalendarHeatmapData;
11198
12185
  exports.validateHexColor = validateHexColor;
11199
12186
 
11200
12187
  //# sourceMappingURL=index.cjs.map