@automattic/charts 4.1.0 → 4.2.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 (80) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/TOKENS.md +7 -5
  3. package/dist/index.cjs +1115 -471
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +39 -1
  6. package/dist/index.d.cts +116 -16
  7. package/dist/index.d.ts +116 -16
  8. package/dist/index.js +1115 -473
  9. package/dist/index.js.map +1 -1
  10. package/package.json +11 -11
  11. package/src/charts/area-chart/area-chart.tsx +2 -1
  12. package/src/charts/area-chart/private/overlays.tsx +1 -2
  13. package/src/charts/area-chart/test/area-chart.test.tsx +31 -0
  14. package/src/charts/bar-chart/bar-chart.tsx +67 -17
  15. package/src/charts/bar-chart/index.ts +1 -1
  16. package/src/charts/bar-chart/private/band-highlight.tsx +85 -0
  17. package/src/charts/bar-chart/private/band-scale.ts +13 -0
  18. package/src/charts/bar-chart/private/band-tooltip.tsx +138 -0
  19. package/src/charts/bar-chart/private/comparison-bars.tsx +5 -10
  20. package/src/charts/bar-chart/private/index.ts +2 -0
  21. package/src/charts/bar-chart/private/test/band-highlight.test.tsx +109 -0
  22. package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
  23. package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
  24. package/src/charts/bar-chart/private/use-bar-chart-options.ts +1 -1
  25. package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
  26. package/src/charts/bar-chart/test/bar-chart.test.tsx +565 -1
  27. package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -3
  28. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
  29. package/src/charts/conversion-funnel-chart/types.ts +4 -2
  30. package/src/charts/geo-chart/types.ts +4 -2
  31. package/src/charts/heatmap-chart/{private/build-calendar-data.ts → build-calendar-data.ts} +15 -45
  32. package/src/charts/heatmap-chart/build-month-calendar-data.ts +124 -0
  33. package/src/charts/heatmap-chart/heatmap-chart.module.scss +32 -0
  34. package/src/charts/heatmap-chart/heatmap-chart.tsx +191 -114
  35. package/src/charts/heatmap-chart/index.ts +15 -3
  36. package/src/charts/heatmap-chart/private/civil-day.ts +26 -0
  37. package/src/charts/heatmap-chart/private/column-groups.ts +71 -0
  38. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +19 -3
  39. package/src/charts/heatmap-chart/private/index.ts +2 -2
  40. package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
  41. package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +37 -1
  42. package/src/charts/heatmap-chart/test/build-month-calendar-data.test.ts +194 -0
  43. package/src/charts/heatmap-chart/test/column-groups.test.ts +83 -0
  44. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +428 -2
  45. package/src/charts/heatmap-chart/test/keyboard-navigation.test.ts +25 -0
  46. package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +1 -1
  47. package/src/charts/heatmap-chart/test/use-month-calendar-heatmap-data.test.tsx +90 -0
  48. package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +1 -1
  49. package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
  50. package/src/charts/heatmap-chart/types.ts +88 -3
  51. package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +2 -2
  52. package/src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts +34 -0
  53. package/src/charts/leaderboard-chart/types.ts +13 -14
  54. package/src/charts/line-chart/line-chart.tsx +3 -2
  55. package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
  56. package/src/charts/line-chart/test/line-chart.test.tsx +31 -0
  57. package/src/charts/line-chart/types.ts +2 -2
  58. package/src/charts/private/time-axis-options.ts +1 -1
  59. package/src/charts/private/with-responsive/with-responsive.tsx +2 -2
  60. package/src/charts/private/x-zoom/test/x-zoom.test.tsx +1 -3
  61. package/src/charts/private/x-zoom/x-zoom.tsx +1 -2
  62. package/src/components/legend/private/base-legend.tsx +1 -1
  63. package/src/components/tooltip/accessible-tooltip.tsx +146 -37
  64. package/src/components/tooltip/base-tooltip.module.scss +6 -1
  65. package/src/components/tooltip/private/bounded-tooltip.tsx +12 -6
  66. package/src/components/tooltip/test/accessible-tooltip.test.tsx +160 -0
  67. package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
  68. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +29 -3
  69. package/src/components/tooltip/xy-chart-tooltip.tsx +11 -11
  70. package/src/hooks/test/use-element-size.test.tsx +13 -13
  71. package/src/hooks/use-xychart-theme.ts +1 -1
  72. package/src/index.ts +7 -1
  73. package/src/providers/chart-context/themes.ts +1 -0
  74. package/src/styles/chart-scope.scss +11 -3
  75. package/src/styles/test/chart-scope.test.ts +36 -2
  76. package/src/types.ts +6 -13
  77. package/src/utils/format-metric-value.ts +4 -4
  78. package/src/utils/get-edge-tick-widths.ts +1 -1
  79. package/src/utils/get-styles.ts +1 -1
  80. package/src/visx/types.ts +7 -10
package/dist/index.cjs CHANGED
@@ -983,7 +983,8 @@ const defaultTheme = {
983
983
  },
984
984
  heatmapChart: {
985
985
  compactCellGap: 2,
986
- compactCellSize: 11
986
+ compactCellSize: 11,
987
+ groupGap: 24
987
988
  }
988
989
  };
989
990
  //#endregion
@@ -2183,7 +2184,10 @@ function useChartLegendItems(data, options = {}, legendShape) {
2183
2184
  }
2184
2185
  //#endregion
2185
2186
  //#region src/components/tooltip/base-tooltip.module.scss
2186
- var base_tooltip_module_default = { "tooltip": "a8ccharts--zY0xG-tooltip" };
2187
+ var base_tooltip_module_default = {
2188
+ "surface": "a8ccharts--zY0xG-surface",
2189
+ "tooltip": "a8ccharts--zY0xG-tooltip"
2190
+ };
2187
2191
  //#endregion
2188
2192
  //#region src/components/tooltip/base-tooltip.tsx
2189
2193
  const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
@@ -2233,7 +2237,8 @@ const findClippingAncestor = (wrapper) => {
2233
2237
  /**
2234
2238
  * Where the box goes, in wrapper coordinates. Automatic placement flips to
2235
2239
  * the side that clips less, then clamps inside `bounds`; below-axis placement
2236
- * is centered on the anchor and clamped horizontally.
2240
+ * is centered on the anchor and clamped horizontally; beside placement preserves
2241
+ * the anchor's y and only flips horizontally before bounds clamping.
2237
2242
  *
2238
2243
  * The flip measures against the visible part of the wrapper, its intersection
2239
2244
  * with `bounds`. Only the clamp is bound by the page: a box that fits inside the
@@ -2247,7 +2252,7 @@ const findClippingAncestor = (wrapper) => {
2247
2252
  * @param params.offsetTop - Gap between the anchor and the box, vertically.
2248
2253
  * @param params.box - Rendered size of the box.
2249
2254
  * @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
2250
- * @param params.placement - Fixed below-axis placement or automatic flipping.
2255
+ * @param params.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2251
2256
  * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2252
2257
  * @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
2253
2258
  */
@@ -2271,7 +2276,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2271
2276
  const upY = top - offsetTop - box.height;
2272
2277
  const downOverflow = downY + box.height - fit.bottom;
2273
2278
  const upOverflow = fit.top - upY;
2274
- let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2279
+ let y = top;
2280
+ if (placement !== "beside") y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2275
2281
  x = clamp(x, bounds.left, bounds.right, box.width);
2276
2282
  y = clamp(y, bounds.top, bounds.bottom, box.height);
2277
2283
  return {
@@ -2287,8 +2293,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2287
2293
  * alone would let the box spill into an `overflow: hidden` card and be cut off.
2288
2294
  *
2289
2295
  * 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.
2296
+ * two hovers is placed for its current size.
2292
2297
  *
2293
2298
  * @param props - visx `Tooltip` props.
2294
2299
  * @param props.left - Anchor x, in wrapper coordinates.
@@ -2298,7 +2303,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2298
2303
  * @param props.style - Box styles; visx's defaults unless `unstyled`.
2299
2304
  * @param props.unstyled - Skip `style` and leave the box bare.
2300
2305
  * @param props.children - Box content.
2301
- * @param props.placement - Fixed below-axis placement or automatic flipping.
2306
+ * @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2302
2307
  * @return The tooltip box.
2303
2308
  */
2304
2309
  const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
@@ -2401,7 +2406,6 @@ const crosshairPaintProps = (props = {}) => {
2401
2406
  };
2402
2407
  const DEFAULT_GLYPH_RADIUS = 4;
2403
2408
  const FALLBACK_COLOR = "#222";
2404
- const DEFAULT_TOOLTIP_Z_INDEX = 3;
2405
2409
  const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
2406
2410
  "scroll",
2407
2411
  "debounce",
@@ -2429,7 +2433,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_js
2429
2433
  seriesKey: key,
2430
2434
  ...props
2431
2435
  }, 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 }) => {
2436
+ 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
2437
  const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2434
2438
  const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2435
2439
  const tooltipContent = renderTooltip ? renderTooltip({
@@ -2484,12 +2488,12 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2484
2488
  const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2485
2489
  const marginTop = margin?.top ?? 0;
2486
2490
  const marginLeft = margin?.left ?? 0;
2487
- const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : _visx_tooltip.Tooltip;
2491
+ const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : _visx_tooltip.Tooltip;
2488
2492
  const boxStyle = {
2489
2493
  ..._visx_tooltip.defaultStyles,
2490
2494
  zIndex,
2491
2495
  backgroundColor: theme?.backgroundColor ?? "white",
2492
- boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2496
+ boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2493
2497
  ...theme?.htmlLabel,
2494
2498
  ...style
2495
2499
  };
@@ -2521,11 +2525,11 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2521
2525
  ]
2522
2526
  }), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2523
2527
  left: tooltipLeft,
2524
- top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
2528
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
2525
2529
  style: boxStyle,
2526
2530
  applyPositionStyle: true,
2527
2531
  ...tooltipProps,
2528
- ...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
2532
+ ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2529
2533
  children: tooltipContent
2530
2534
  }), container)] });
2531
2535
  };
@@ -2591,11 +2595,32 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2591
2595
  return flattened;
2592
2596
  }, [series, mode]);
2593
2597
  const hasKeyboardSelection = (0, react.useRef)(false);
2598
+ const closeTooltipNow = (0, react.useCallback)(() => {
2599
+ tooltipContext?.updateTooltip({
2600
+ tooltipOpen: false,
2601
+ tooltipLeft: void 0,
2602
+ tooltipTop: void 0,
2603
+ tooltipData: void 0
2604
+ });
2605
+ }, []);
2606
+ const chartId = (0, react.useContext)(ChartInstanceContext)?.chartId;
2607
+ const { getHiddenSeries } = useGlobalChartsContext();
2608
+ const hiddenSeriesKey = chartId ? JSON.stringify([...getHiddenSeries(chartId)].sort()) : void 0;
2609
+ const lastHiddenSeriesKey = (0, react.useRef)(hiddenSeriesKey);
2610
+ (0, react.useEffect)(() => {
2611
+ const changed = lastHiddenSeriesKey.current !== hiddenSeriesKey;
2612
+ lastHiddenSeriesKey.current = hiddenSeriesKey;
2613
+ if (changed && selectedIndex === void 0) closeTooltipNow();
2614
+ }, [
2615
+ hiddenSeriesKey,
2616
+ selectedIndex,
2617
+ closeTooltipNow
2618
+ ]);
2594
2619
  (0, react.useEffect)(() => {
2595
2620
  if (selectedIndex === void 0) {
2596
2621
  if (hasKeyboardSelection.current) {
2597
2622
  hasKeyboardSelection.current = false;
2598
- tooltipContext?.hideTooltip();
2623
+ closeTooltipNow();
2599
2624
  }
2600
2625
  return;
2601
2626
  }
@@ -2664,15 +2689,45 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2664
2689
  renderTooltip: focusableRenderTooltip
2665
2690
  });
2666
2691
  };
2667
- const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
2692
+ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
2693
+ const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
2694
+ const focusWithoutScrollIfNeeded = (0, react.useCallback)((element) => {
2695
+ if (preventTooltipScroll) element?.focus({ preventScroll: true });
2696
+ else element?.focus();
2697
+ }, [preventTooltipScroll]);
2698
+ const tooltipRef = (0, react.useCallback)((element) => {
2699
+ if (element && selectedIndex !== void 0) focusWithoutScrollIfNeeded(element);
2700
+ }, [focusWithoutScrollIfNeeded, selectedIndex]);
2701
+ const previousTotalPoints = (0, react.useRef)(totalPoints);
2702
+ const previousVisibleSeriesKey = (0, react.useRef)(visibleSeriesKey);
2703
+ (0, react.useEffect)(() => {
2704
+ const countChanged = previousTotalPoints.current !== totalPoints;
2705
+ const seriesChanged = previousVisibleSeriesKey.current !== visibleSeriesKey;
2706
+ previousTotalPoints.current = totalPoints;
2707
+ previousVisibleSeriesKey.current = visibleSeriesKey;
2708
+ if (!countChanged && !seriesChanged || selectedIndex === void 0) return;
2709
+ const { activeElement } = document;
2710
+ const focusIsInChart = activeElement !== null && !!getChartRoot()?.contains(activeElement);
2711
+ if (totalPoints === 0 || !focusIsInChart || !countChanged) {
2712
+ setSelectedIndex(void 0);
2713
+ setIsNavigating(false);
2714
+ if (focusIsInChart) focusWithoutScrollIfNeeded(getChartRoot());
2715
+ return;
2716
+ }
2717
+ setSelectedIndex(Math.min(selectedIndex, totalPoints - 1));
2718
+ }, [
2719
+ selectedIndex,
2720
+ totalPoints,
2721
+ visibleSeriesKey,
2722
+ setSelectedIndex,
2723
+ setIsNavigating,
2724
+ getChartRoot,
2725
+ focusWithoutScrollIfNeeded
2726
+ ]);
2668
2727
  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]),
2675
- onChartFocus: (0, react.useCallback)(() => {
2728
+ tooltipRef,
2729
+ onChartFocus: (0, react.useCallback)((event) => {
2730
+ if (event.currentTarget.contains(event.relatedTarget)) return;
2676
2731
  if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
2677
2732
  }, [
2678
2733
  isNavigating,
@@ -2683,38 +2738,34 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2683
2738
  setIsNavigating(false);
2684
2739
  }, [setIsNavigating]),
2685
2740
  onChartKeyDown: (0, react.useCallback)((event) => {
2686
- if (totalPoints === 0) return;
2687
- if (event.key === "Tab") {
2688
- chartRef.current?.focus();
2741
+ if (event.key === "Tab" || event.key === "Escape") {
2742
+ if (event.key === "Escape" && selectedIndex !== void 0) event.preventDefault();
2743
+ focusWithoutScrollIfNeeded(getChartRoot());
2689
2744
  setSelectedIndex(void 0);
2690
2745
  setIsNavigating(false);
2691
2746
  return;
2692
2747
  }
2748
+ if (totalPoints === 0) return;
2693
2749
  const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
2694
- if (currentSelectedIndex + 1 >= totalPoints && ["ArrowRight"].includes(event.key)) {
2695
- chartRef.current?.focus();
2696
- setSelectedIndex(void 0);
2697
- setIsNavigating(false);
2698
- return;
2699
- }
2700
- event.preventDefault();
2701
- if (["ArrowRight"].includes(event.key)) {
2750
+ if (event.key === "ArrowRight") {
2751
+ event.preventDefault();
2702
2752
  setIsNavigating(true);
2703
- setSelectedIndex((currentSelectedIndex + 1) % totalPoints);
2704
- } else if (["ArrowLeft"].includes(event.key)) {
2753
+ setSelectedIndex(Math.min(currentSelectedIndex + 1, totalPoints - 1));
2754
+ } else if (event.key === "ArrowLeft") {
2755
+ event.preventDefault();
2705
2756
  setIsNavigating(true);
2706
- setSelectedIndex((currentSelectedIndex - 1 + totalPoints) % totalPoints);
2707
- } else if (event.key === "Escape") {
2708
- setSelectedIndex(void 0);
2709
- setIsNavigating(false);
2710
- chartRef.current?.focus();
2711
- } else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
2757
+ setSelectedIndex(Math.max(currentSelectedIndex - 1, 0));
2758
+ } else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) {
2759
+ event.preventDefault();
2760
+ onActivate?.(selectedIndex);
2761
+ }
2712
2762
  }, [
2713
2763
  totalPoints,
2714
2764
  selectedIndex,
2715
2765
  setSelectedIndex,
2716
2766
  setIsNavigating,
2717
- chartRef,
2767
+ getChartRoot,
2768
+ focusWithoutScrollIfNeeded,
2718
2769
  onActivate
2719
2770
  ])
2720
2771
  };
@@ -4318,6 +4369,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4318
4369
  children: ({ contentHeight }) => {
4319
4370
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4320
4371
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4372
+ ref: chartRef,
4321
4373
  role: "grid",
4322
4374
  "aria-label": (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
4323
4375
  tabIndex: 0,
@@ -4325,7 +4377,6 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4325
4377
  onFocus: onChartFocus,
4326
4378
  onBlur: onChartBlur,
4327
4379
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4328
- ref: chartRef,
4329
4380
  className: xy_plot_module_default["xy-plot"],
4330
4381
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
4331
4382
  theme,
@@ -4823,6 +4874,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4823
4874
  children: ({ contentHeight }) => {
4824
4875
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4825
4876
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4877
+ ref: chartRef,
4826
4878
  role: "grid",
4827
4879
  "aria-label": (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
4828
4880
  tabIndex: 0,
@@ -4830,7 +4882,6 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4830
4882
  onFocus: onChartFocus,
4831
4883
  onBlur: onChartBlur,
4832
4884
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4833
- ref: chartRef,
4834
4885
  className: xy_plot_module_default["xy-plot"],
4835
4886
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
4836
4887
  theme,
@@ -5272,6 +5323,20 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
5272
5323
  ]);
5273
5324
  }
5274
5325
  //#endregion
5326
+ //#region src/charts/bar-chart/private/band-scale.ts
5327
+ /**
5328
+ * Match the inner slot scale used by visx BarGroup.
5329
+ * @param keys - Visible primary series keys.
5330
+ * @param bandwidth - Category band width.
5331
+ * @param padding - Padding between slots.
5332
+ * @return The grouped bar scale.
5333
+ */
5334
+ const createGroupScale = (keys, bandwidth, padding) => (0, _visx_scale.scaleBand)({
5335
+ domain: keys,
5336
+ range: [0, bandwidth],
5337
+ padding
5338
+ });
5339
+ //#endregion
5275
5340
  //#region src/charts/bar-chart/private/comparison-bars-geometry.ts
5276
5341
  /**
5277
5342
  * Output position of a value scale's baseline: zero if in-domain, else the
@@ -5344,11 +5409,7 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
5344
5409
  const valueScale = horizontal ? xScale : yScale;
5345
5410
  const bandwidth = bandScale.bandwidth ? bandScale.bandwidth() : 0;
5346
5411
  if (!bandwidth) return null;
5347
- const groupScale = (0, _visx_scale.scaleBand)({
5348
- domain: primaryKeys,
5349
- range: [0, bandwidth],
5350
- padding: groupPadding
5351
- });
5412
+ const groupScale = createGroupScale(primaryKeys, bandwidth, groupPadding);
5352
5413
  const slotThickness = groupScale.bandwidth();
5353
5414
  const baseline = getValueScaleBaseline(valueScale);
5354
5415
  const bandAccessor = horizontal ? yAccessor : xAccessor;
@@ -5400,6 +5461,163 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
5400
5461
  });
5401
5462
  };
5402
5463
  //#endregion
5464
+ //#region src/charts/bar-chart/private/band-highlight.tsx
5465
+ /**
5466
+ * Render and report the active band using the chart’s registered scales.
5467
+ * @param root0 - Highlight options.
5468
+ * @param root0.visible - Whether to paint the band.
5469
+ * @param root0.horizontal - Whether categories run vertically.
5470
+ * @param root0.onChange - Receives selected band geometry.
5471
+ * @return The optional band highlight.
5472
+ */
5473
+ function BandHighlight({ visible, horizontal, onChange }) {
5474
+ const { xScale, yScale, dataRegistry, margin, innerWidth, innerHeight } = (0, react.useContext)(_visx_xychart.DataContext);
5475
+ const { tooltipOpen, tooltipData } = (0, react.useContext)(_visx_xychart.TooltipContext);
5476
+ const nearest = tooltipData?.nearestDatum;
5477
+ const selection = (0, react.useMemo)(() => {
5478
+ if (!tooltipOpen || !nearest) return null;
5479
+ const scale = horizontal ? yScale : xScale;
5480
+ const entry = dataRegistry?.get(nearest.key);
5481
+ const accessor = horizontal ? entry?.yAccessor : entry?.xAccessor;
5482
+ if (!scale?.bandwidth || !accessor) return null;
5483
+ const position = scale(accessor(nearest.datum));
5484
+ if (position === void 0 || !Number.isFinite(position)) return null;
5485
+ return {
5486
+ datum: nearest.datum,
5487
+ x: horizontal ? margin?.left ?? 0 : position,
5488
+ y: horizontal ? position : margin?.top ?? 0,
5489
+ width: horizontal ? innerWidth : scale.bandwidth(),
5490
+ height: horizontal ? scale.bandwidth() : innerHeight
5491
+ };
5492
+ }, [
5493
+ tooltipOpen,
5494
+ nearest,
5495
+ horizontal,
5496
+ xScale,
5497
+ yScale,
5498
+ dataRegistry,
5499
+ margin,
5500
+ innerWidth,
5501
+ innerHeight
5502
+ ]);
5503
+ const stableSelection = useDeepMemo(selection);
5504
+ const onChangeRef = (0, react.useRef)(onChange);
5505
+ onChangeRef.current = onChange;
5506
+ (0, react.useEffect)(() => {
5507
+ onChangeRef.current?.(stableSelection);
5508
+ }, [stableSelection]);
5509
+ (0, react.useEffect)(() => () => onChangeRef.current?.(null), []);
5510
+ if (!visible || !selection) return null;
5511
+ const { x, y, width, height } = selection;
5512
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
5513
+ x,
5514
+ y,
5515
+ width,
5516
+ height,
5517
+ pointerEvents: "none",
5518
+ fill: CATALOG_POINTERS.surfaceSecondary
5519
+ });
5520
+ }
5521
+ //#endregion
5522
+ //#region src/charts/bar-chart/private/band-tooltip.tsx
5523
+ /**
5524
+ * Find a category by its painted center, including outer padding and reversed ranges.
5525
+ * @param data - Registered series points.
5526
+ * @param accessor - Category accessor.
5527
+ * @param scale - Rendered category band scale.
5528
+ * @param position - Pointer coordinate on the category axis.
5529
+ * @return Index of the nearest category, or -1 when none is registered.
5530
+ */
5531
+ function nearestBandIndex(data, accessor, scale, position) {
5532
+ let nearest = -1;
5533
+ let distance = Infinity;
5534
+ data.forEach((datum, index) => {
5535
+ const start = scale(accessor(datum));
5536
+ if (start === void 0) return;
5537
+ const candidate = Math.abs(start + scale.bandwidth() / 2 - position);
5538
+ if (candidate < distance) {
5539
+ distance = candidate;
5540
+ nearest = index;
5541
+ }
5542
+ });
5543
+ return nearest;
5544
+ }
5545
+ /**
5546
+ * Correct visx's range-based band inversion, which omits outer padding.
5547
+ * @param root0 - Registered series selection.
5548
+ * @param root0.keys - Visible series keys.
5549
+ * @param root0.groupPadding - Padding between grouped bars.
5550
+ * @param root0.withTooltips - Whether pointer events update the tooltip.
5551
+ * @param root0.onPointerDown - Receives the corrected pointer-down datum.
5552
+ * @param root0.onPointerUp - Receives the corrected pointer-up datum.
5553
+ * @return No visual content.
5554
+ */
5555
+ function BandTooltip({ keys, groupPadding, withTooltips, onPointerDown, onPointerUp }) {
5556
+ const { xScale, yScale, dataRegistry, horizontal } = (0, react.useContext)(_visx_xychart.DataContext);
5557
+ const { showTooltip } = (0, react.useContext)(_visx_xychart.TooltipContext);
5558
+ const scale = horizontal ? yScale : xScale;
5559
+ const bandwidth = scale?.bandwidth?.() ?? 0;
5560
+ const groupScale = (0, react.useMemo)(() => createGroupScale(keys, bandwidth, groupPadding), [
5561
+ keys,
5562
+ bandwidth,
5563
+ groupPadding
5564
+ ]);
5565
+ const getSelections = (0, react.useCallback)((params) => {
5566
+ const point = params?.svgPoint;
5567
+ if (!point || !scale?.bandwidth) return [];
5568
+ const selections = [];
5569
+ for (const key of keys) {
5570
+ const entry = dataRegistry?.get(key);
5571
+ if (!entry) continue;
5572
+ const accessor = horizontal ? entry.yAccessor : entry.xAccessor;
5573
+ const index = nearestBandIndex(entry.data, accessor, scale, horizontal ? point.y : point.x);
5574
+ if (index < 0) continue;
5575
+ const datum = entry.data[index];
5576
+ const start = Number(scale(accessor(datum))) + Number(groupScale(key));
5577
+ const end = start + groupScale.step();
5578
+ const position = horizontal ? point.y : point.x;
5579
+ const distance = position >= start && position <= end ? 0 : Math.abs(position - (start + end) / 2);
5580
+ selections.push({
5581
+ event: params.event,
5582
+ key,
5583
+ datum,
5584
+ index,
5585
+ svgPoint: point,
5586
+ distanceX: horizontal ? 0 : distance,
5587
+ distanceY: horizontal ? distance : 0
5588
+ });
5589
+ }
5590
+ return selections;
5591
+ }, [
5592
+ scale,
5593
+ dataRegistry,
5594
+ horizontal,
5595
+ keys,
5596
+ groupScale
5597
+ ]);
5598
+ const handlePointer = (0, react.useCallback)((params) => {
5599
+ const selections = getSelections(params);
5600
+ if (withTooltips && params?.event.type !== "pointerup") selections.forEach(showTooltip);
5601
+ const callback = params?.event.type === "pointerdown" ? onPointerDown : onPointerUp;
5602
+ if (params?.event.type === "pointermove" || !callback) return;
5603
+ const nearest = selections.reduce((best, selection) => {
5604
+ const distance = (value) => Math.hypot(value.distanceX, value.distanceY);
5605
+ return !best || distance(selection) <= distance(best) ? selection : best;
5606
+ }, void 0);
5607
+ if (nearest) callback(nearest);
5608
+ }, [
5609
+ getSelections,
5610
+ withTooltips,
5611
+ showTooltip,
5612
+ onPointerDown,
5613
+ onPointerUp
5614
+ ]);
5615
+ (0, _visx_xychart.useEventEmitter)("pointermove", withTooltips ? handlePointer : void 0);
5616
+ (0, _visx_xychart.useEventEmitter)("pointerdown", handlePointer);
5617
+ (0, _visx_xychart.useEventEmitter)("pointerup", onPointerUp ? handlePointer : void 0);
5618
+ return null;
5619
+ }
5620
+ //#endregion
5403
5621
  //#region src/charts/bar-chart/bar-chart.tsx
5404
5622
  const validateData$2 = (data) => {
5405
5623
  if (!data?.length) return "No data available";
@@ -5411,7 +5629,7 @@ const renderTooltipRow = (label, value) => /* @__PURE__ */ (0, react_jsx_runtime
5411
5629
  className: bar_chart_module_default["bar-chart__tooltip-row"],
5412
5630
  children: (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("%1$s: %2$s", "jetpack-charts"), label, value)
5413
5631
  });
5414
- 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 }) => {
5632
+ 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 }) => {
5415
5633
  const legendInteractive = legend.interactive ?? false;
5416
5634
  const legendCollapseGroups = legend.collapseGroups ?? false;
5417
5635
  const horizontal = orientation === "horizontal";
@@ -5437,8 +5655,6 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5437
5655
  }, [height]);
5438
5656
  const [selectedIndex, setSelectedIndex] = (0, react.useState)(void 0);
5439
5657
  const [isNavigating, setIsNavigating] = (0, react.useState)(false);
5440
- const primarySeriesForNav = dataWithVisibleZeros.filter((s) => s.options?.type !== "comparison");
5441
- const totalPoints = Math.max(0, ...primarySeriesForNav.map((s) => s.data?.length || 0)) * primarySeriesForNav.length;
5442
5658
  const seriesWithVisibility = (0, react.useMemo)(() => dataWithVisibleZeros.map((series, index) => ({
5443
5659
  series,
5444
5660
  index,
@@ -5448,6 +5664,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5448
5664
  return seriesWithVisibility.every(({ isVisible }) => !isVisible);
5449
5665
  }, [seriesWithVisibility]);
5450
5666
  const primaryEntries = (0, react.useMemo)(() => seriesWithVisibility.filter(({ isVisible, series }) => isVisible && series.options?.type !== "comparison"), [seriesWithVisibility]);
5667
+ const totalPoints = Math.max(0, ...primaryEntries.map((e) => e.series.data.length)) * primaryEntries.length;
5451
5668
  const primaryKeys = (0, react.useMemo)(() => primaryEntries.map(({ series }) => series.label), [primaryEntries]);
5452
5669
  const primarySeries = (0, react.useMemo)(() => primaryEntries.map(({ series }) => series), [primaryEntries]);
5453
5670
  const activateSelectedBar = (0, react.useCallback)((index) => {
@@ -5462,6 +5679,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5462
5679
  key: series.label
5463
5680
  });
5464
5681
  }, [primaryEntries, onDatumActivate]);
5682
+ const visibleSeriesKey = (0, react.useMemo)(() => JSON.stringify(primaryKeys), [primaryKeys]);
5465
5683
  const { tooltipRef, onChartFocus, onChartBlur, onChartKeyDown } = useKeyboardNavigation({
5466
5684
  selectedIndex,
5467
5685
  setSelectedIndex,
@@ -5469,7 +5687,8 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5469
5687
  setIsNavigating,
5470
5688
  chartRef,
5471
5689
  totalPoints,
5472
- onActivate: activateSelectedBar
5690
+ onActivate: activateSelectedBar,
5691
+ visibleSeriesKey
5473
5692
  });
5474
5693
  const comparisonEntries = (0, react.useMemo)(() => {
5475
5694
  const primaryByGroup = new Map(primaryEntries.map(({ series, index }) => [series.group, {
@@ -5532,7 +5751,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5532
5751
  chartOptions.yScale,
5533
5752
  horizontal
5534
5753
  ]);
5535
- const getBarBackground = (0, react.useCallback)((index) => () => withPatterns ? `url(#${getPatternId(chartId, index)})` : getElementStyles({
5754
+ const getBarBackground = (0, react.useCallback)((index) => (datum) => withPatterns ? `url(#${getPatternId(chartId, index)})` : datum.color ?? getElementStyles({
5536
5755
  data: dataSorted[index],
5537
5756
  index
5538
5757
  }).color, [
@@ -5702,13 +5921,13 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5702
5921
  const chartHeight = contentHeight > 0 ? contentHeight : height;
5703
5922
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5704
5923
  role: "grid",
5924
+ ref: chartRef,
5705
5925
  "aria-label": (0, _wordpress_i18n.__)("Bar chart", "jetpack-charts"),
5706
5926
  tabIndex: 0,
5707
5927
  onKeyDown: onChartKeyDown,
5708
5928
  onFocus: onChartFocus,
5709
5929
  onBlur: onChartBlur,
5710
5930
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5711
- ref: chartRef,
5712
5931
  className: xy_plot_module_default["xy-plot"],
5713
5932
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5714
5933
  theme,
@@ -5721,15 +5940,24 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5721
5940
  xScale,
5722
5941
  yScale,
5723
5942
  horizontal,
5724
- onPointerDown,
5725
- onPointerUp,
5726
5943
  pointerEventsDataKey: "nearest",
5727
5944
  children: [
5728
- !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
5945
+ withTooltips && (withBandHighlight || onBandHighlightChange) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandHighlight, {
5946
+ visible: withBandHighlight,
5947
+ horizontal,
5948
+ onChange: onBandHighlightChange
5949
+ }),
5950
+ !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
5729
5951
  columns: gridVisibility.includes("y"),
5952
+ rows: false,
5953
+ numTicks: 4,
5954
+ tickValues: chartOptions.axis.x.tickValues
5955
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Grid, {
5956
+ columns: false,
5730
5957
  rows: gridVisibility.includes("x"),
5731
- numTicks: 4
5732
- }),
5958
+ numTicks: 4,
5959
+ tickValues: chartOptions.axis.y.tickValues
5960
+ })] }),
5733
5961
  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({
5734
5962
  data: seriesData,
5735
5963
  index
@@ -5765,8 +5993,17 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5765
5993
  colorAccessor: getBarBackground(index)
5766
5994
  }, seriesData?.label))
5767
5995
  }),
5996
+ (withTooltips || onPointerDown || onPointerUp) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BandTooltip, {
5997
+ keys: primaryKeys,
5998
+ groupPadding,
5999
+ withTooltips,
6000
+ onPointerDown,
6001
+ onPointerUp
6002
+ }),
5768
6003
  !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 })] }),
5769
6004
  withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
6005
+ tooltipPlacement,
6006
+ tooltipAnchorTop,
5770
6007
  detectBounds: true,
5771
6008
  snapTooltipToDatumX: true,
5772
6009
  snapTooltipToDatumY: true,
@@ -6588,8 +6825,11 @@ var heatmap_chart_module_default = {
6588
6825
  "heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
6589
6826
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
6590
6827
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
6828
+ "heatmap-chart__grid--flex-gaps": "a8ccharts-O3YMOW-heatmap-chart__grid--flex-gaps",
6591
6829
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
6830
+ "heatmap-chart__group-label": "a8ccharts-O3YMOW-heatmap-chart__group-label",
6592
6831
  "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
6832
+ "heatmap-chart__legend-scale--bar": "a8ccharts-O3YMOW-heatmap-chart__legend-scale--bar",
6593
6833
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
6594
6834
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
6595
6835
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
@@ -6634,290 +6874,267 @@ const getNormalizedValue = (value, extent) => {
6634
6874
  return Math.min(1, Math.max(0, (value - min) / (max - min)));
6635
6875
  };
6636
6876
  //#endregion
6637
- //#region src/charts/heatmap-chart/private/civil-day.ts
6638
- const DAY_MS = 864e5;
6639
- const KEY_OPTIONS = {
6640
- calendar: "gregory",
6641
- numberingSystem: "latn",
6642
- year: "numeric",
6643
- month: "2-digit",
6644
- day: "2-digit"
6645
- };
6646
- const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
6647
- const pad = (value, length) => String(value).padStart(length, "0");
6648
- /**
6649
- * The day a UTC proxy stands for.
6650
- *
6651
- * @param date - UTC proxy.
6652
- * @return Its day key.
6653
- */
6654
- const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
6655
- /**
6656
- * A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
6657
- * `DAY_MS`.
6658
- *
6659
- * @param key - Day key.
6660
- * @return The proxy, or null when the key names no real day.
6661
- */
6662
- const civilDate = (key) => {
6663
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
6664
- if (!match) return null;
6665
- const date = /* @__PURE__ */ new Date(0);
6666
- date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
6667
- date.setUTCHours(0, 0, 0, 0);
6668
- return civilKey(date) === key ? date : null;
6877
+ //#region src/charts/heatmap-chart/private/heatmap-context.ts
6878
+ /** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
6879
+ const HeatmapContext = (0, react.createContext)(null);
6880
+ //#endregion
6881
+ //#region src/charts/heatmap-chart/private/heatmap-legend.tsx
6882
+ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }) => {
6883
+ const context = (0, react.useContext)(HeatmapContext);
6884
+ const { legend } = useGlobalChartsTheme();
6885
+ if (!context) return null;
6886
+ const { primaryColorHex } = context;
6887
+ const labelStyle = legend.labelStyles;
6888
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
6889
+ direction: "row",
6890
+ gap: "xs",
6891
+ align: "center",
6892
+ children: [
6893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6894
+ variant: "body-sm",
6895
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6896
+ style: labelStyle,
6897
+ children: lessLabel ?? (0, _wordpress_i18n.__)("Less", "jetpack-charts")
6898
+ }),
6899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
6900
+ direction: "row",
6901
+ gap: variant === "bar" ? void 0 : "xs",
6902
+ "aria-hidden": "true",
6903
+ className: variant === "bar" ? heatmap_chart_module_default["heatmap-chart__legend-scale--bar"] : void 0,
6904
+ children: Array.from({ length: steps }, (_, index) => {
6905
+ const intensity = steps <= 1 ? 1 : index / (steps - 1);
6906
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6907
+ className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6908
+ style: {
6909
+ "--a8c-charts-color-heatmap-primary": primaryColorHex,
6910
+ "--a8c-charts-heatmap-cell-intensity": intensity
6911
+ }
6912
+ }, index);
6913
+ })
6914
+ }),
6915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6916
+ variant: "body-sm",
6917
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6918
+ style: labelStyle,
6919
+ children: moreLabel ?? (0, _wordpress_i18n.__)("More", "jetpack-charts")
6920
+ })
6921
+ ]
6922
+ });
6669
6923
  };
6924
+ //#endregion
6925
+ //#region src/charts/heatmap-chart/private/column-groups.ts
6926
+ const isSpan = (span) => Number.isInteger(span) && span > 0;
6670
6927
  /**
6671
- * The day a written date names, ignoring any time and zone it carries.
6928
+ * Validates column groups and lays them over the columns.
6672
6929
  *
6673
- * @param text - `yyyy-MM-dd`, optionally followed by a time.
6674
- * @return Its day key, or null.
6930
+ * @param groups - Groups as passed to the chart.
6931
+ * @param columnCount - Number of data columns.
6932
+ * @param firstLine - Grid column line of the first data column.
6933
+ * @return Grid column lines for every column and group; no groups when the input was unusable.
6675
6934
  */
6676
- const writtenDayKey = (text) => {
6677
- const match = DATE_PREFIX.exec(text);
6678
- if (!match) return null;
6679
- const key = `${match[1]}-${match[2]}-${match[3]}`;
6680
- return civilDate(key) ? key : null;
6935
+ const resolveColumnGroups = (groups, columnCount, firstLine) => {
6936
+ const ungrouped = () => ({
6937
+ columns: Array.from({ length: columnCount }, (_, index) => ({
6938
+ line: firstLine + index,
6939
+ gapBefore: false
6940
+ })),
6941
+ groups: []
6942
+ });
6943
+ if (!groups || groups.length === 0 || columnCount === 0) return ungrouped();
6944
+ const spans = groups.map((group) => group.span);
6945
+ const total = spans.reduce((sum, span) => sum + span, 0);
6946
+ if (!spans.every(isSpan) || total > columnCount) {
6947
+ 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.`);
6948
+ return ungrouped();
6949
+ }
6950
+ const columns = [];
6951
+ const laidOut = [];
6952
+ let line = firstLine;
6953
+ groups.forEach((group, groupIndex) => {
6954
+ if (groupIndex > 0) line += 1;
6955
+ laidOut.push({
6956
+ ...group,
6957
+ line
6958
+ });
6959
+ for (let offset = 0; offset < group.span; offset++) {
6960
+ columns.push({
6961
+ line,
6962
+ gapBefore: groupIndex > 0 && offset === 0,
6963
+ group: groupIndex
6964
+ });
6965
+ line += 1;
6966
+ }
6967
+ });
6968
+ while (columns.length < columnCount) {
6969
+ columns.push({
6970
+ line,
6971
+ gapBefore: false
6972
+ });
6973
+ line += 1;
6974
+ }
6975
+ return {
6976
+ columns,
6977
+ groups: laidOut
6978
+ };
6681
6979
  };
6980
+ //#endregion
6981
+ //#region src/charts/heatmap-chart/private/keyboard-navigation.ts
6982
+ const ARROW_KEYS = [
6983
+ "ArrowLeft",
6984
+ "ArrowRight",
6985
+ "ArrowUp",
6986
+ "ArrowDown"
6987
+ ];
6988
+ const PAGE_KEYS = ["PageUp", "PageDown"];
6989
+ const isNavigationKey = (key, mode) => ARROW_KEYS.includes(key) || mode === "calendar" && PAGE_KEYS.includes(key);
6990
+ const inGrid = (grid, { column, row }) => column >= 0 && column < grid.columns && row >= 0 && row < grid.rows;
6991
+ const firstSelectable = (grid, cells) => cells.find((cell) => inGrid(grid, cell) && !grid.isInert(cell.column, cell.row));
6682
6992
  /**
6683
- * Reads the day an instant falls on in a zone.
6993
+ * Column-major scan for the first selectable cell.
6684
6994
  *
6685
- * @param timeZone - IANA zone name; absent leaves the runtime's own.
6686
- * @return Reader taking a `Date`, null for a day no key can hold.
6995
+ * @param grid - The grid.
6996
+ * @return The cell, or undefined when every slot is inert.
6687
6997
  */
6688
- const instantDayReader = (timeZone) => {
6689
- const formatter = new Intl.DateTimeFormat("en-US", {
6690
- ...KEY_OPTIONS,
6691
- timeZone
6692
- });
6693
- return (date) => {
6694
- const parts = formatter.formatToParts(date);
6695
- const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
6696
- const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
6697
- return civilDate(key) ? key : null;
6998
+ const firstGridCell = (grid) => {
6999
+ for (let column = 0; column < grid.columns; column++) for (let row = 0; row < grid.rows; row++) if (!grid.isInert(column, row)) return {
7000
+ column,
7001
+ row
6698
7002
  };
6699
7003
  };
6700
7004
  /**
6701
- * The day a series point belongs to.
6702
- *
6703
- * A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
6704
- * zone. Anything else is a wall clock the host wrote, so its date part is the day.
7005
+ * Steps across the grid in the key's direction, past inert slots, staying put at the edge.
6705
7006
  *
6706
- * @param point - Series point.
6707
- * @param readInstant - Reader from `instantDayReader`.
6708
- * @return Its day key, or null when the point carries no usable date.
7007
+ * @param grid - The grid.
7008
+ * @param from - The selected cell.
7009
+ * @param key - The arrow key.
7010
+ * @return The next cell, or undefined to keep the selection.
6709
7011
  */
6710
- const pointDayKey = (point, readInstant) => {
6711
- if (point.date instanceof Date && !isNaN(point.date.getTime())) {
6712
- const key = readInstant(point.date);
6713
- if (key) return key;
7012
+ const stepGridCell = (grid, from, key) => {
7013
+ const step = {
7014
+ ArrowRight: [1, 0],
7015
+ ArrowLeft: [-1, 0],
7016
+ ArrowDown: [0, 1],
7017
+ ArrowUp: [0, -1]
7018
+ }[key];
7019
+ if (!step) return;
7020
+ const [stepColumn, stepRow] = step;
7021
+ let cell = from;
7022
+ do
7023
+ cell = {
7024
+ column: cell.column + stepColumn,
7025
+ row: cell.row + stepRow
7026
+ };
7027
+ while (inGrid(grid, cell) && grid.isInert(cell.column, cell.row));
7028
+ return inGrid(grid, cell) ? cell : void 0;
7029
+ };
7030
+ /**
7031
+ * Every slot of the blocks in reading order: block by block, each a row at a time.
7032
+ *
7033
+ * @param grid - The grid.
7034
+ * @param blocks - The blocks, left to right.
7035
+ * @return The slots in order.
7036
+ */
7037
+ const readingOrder = (grid, blocks) => blocks.flatMap((block) => Array.from({ length: grid.rows }, (_row, row) => Array.from({ length: block.end - block.start }, (_offset, offset) => ({
7038
+ column: block.start + offset,
7039
+ row
7040
+ }))).flat());
7041
+ const blockOf = (blocks, column) => blocks.findIndex((block) => column >= block.start && column < block.end);
7042
+ /**
7043
+ * The slots after a cell down its column (or before it, going up), continuing at the
7044
+ * same column offset of each following block.
7045
+ *
7046
+ * @param grid - The grid.
7047
+ * @param blocks - The blocks, left to right.
7048
+ * @param from - The selected cell.
7049
+ * @param direction - 1 downwards, -1 upwards.
7050
+ * @return The slots in order.
7051
+ */
7052
+ const columnOrderFrom = (grid, blocks, from, direction) => {
7053
+ const blockIndex = blockOf(blocks, from.column);
7054
+ if (blockIndex < 0) return [];
7055
+ const offset = from.column - blocks[blockIndex].start;
7056
+ const cells = [];
7057
+ const pushRows = (column, startRow) => {
7058
+ for (let row = startRow; row >= 0 && row < grid.rows; row += direction) cells.push({
7059
+ column,
7060
+ row
7061
+ });
7062
+ };
7063
+ pushRows(from.column, from.row + direction);
7064
+ for (let index = blockIndex + direction; index >= 0 && index < blocks.length; index += direction) {
7065
+ const column = blocks[index].start + offset;
7066
+ if (column < blocks[index].end) pushRows(column, direction === 1 ? 0 : grid.rows - 1);
6714
7067
  }
6715
- const text = point.dateString;
6716
- if (!text) return null;
6717
- if (!hasTimezone(text)) return writtenDayKey(text);
6718
- const parsed = (0, date_fns.parseISO)(text);
6719
- return isNaN(parsed.getTime()) ? null : readInstant(parsed);
7068
+ return cells;
6720
7069
  };
6721
7070
  /**
6722
- * Steps a proxy by whole days.
7071
+ * The first selectable cell in calendar reading order.
6723
7072
  *
6724
- * @param date - UTC proxy.
6725
- * @param days - Days to add; may be negative.
6726
- * @return The shifted proxy.
7073
+ * @param grid - The grid.
7074
+ * @param blocks - The blocks, left to right.
7075
+ * @return The cell, or undefined when every slot is inert.
6727
7076
  */
6728
- const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
7077
+ const firstCalendarCell = (grid, blocks) => firstSelectable(grid, readingOrder(grid, blocks));
6729
7078
  /**
6730
- * Rounds a proxy down to its week.
7079
+ * Steps through the blocks as pages of a calendar: Left/Right by one slot in reading
7080
+ * order, Up/Down by one row (into the neighboring block past the edge), Page Up/Down
7081
+ * to the same slot of the neighboring block or its nearest selectable cell.
6731
7082
  *
6732
- * @param date - UTC proxy.
6733
- * @param weekStartsOn - 0 for Sunday, 1 for Monday.
6734
- * @return The proxy for that week's first day.
7083
+ * @param grid - The grid.
7084
+ * @param blocks - The blocks, left to right.
7085
+ * @param from - The selected cell.
7086
+ * @param key - The navigation key.
7087
+ * @return The next cell, or undefined to keep the selection.
6735
7088
  */
6736
- const startOfCivilWeek = (date, weekStartsOn) => {
6737
- const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
6738
- return addCivilDays(date, -daysSinceWeekStart);
6739
- };
6740
- /**
6741
- * Counts the columns a span needs.
6742
- *
6743
- * @param from - Earlier UTC proxy.
6744
- * @param to - Later UTC proxy.
6745
- * @param weekStartsOn - 0 for Sunday, 1 for Monday.
6746
- * @return Columns needed to cover the span, inclusive.
6747
- */
6748
- const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
6749
- //#endregion
6750
- //#region src/charts/heatmap-chart/private/build-calendar-data.ts
6751
- /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
6752
- const LABELLED_ROWS = [
6753
- 0,
6754
- 2,
6755
- 4
6756
- ];
6757
- /**
6758
- * Resolve one grid bound: an unparseable or narrowing value falls back to the
6759
- * series' own bound, so the grid can only ever be widened.
6760
- *
6761
- * @param bound - Requested bound as `yyyy-MM-dd`, if any.
6762
- * @param fallback - The series' own bound on this side.
6763
- * @param direction - Which way the bound is allowed to move.
6764
- * @return The day key to draw to.
6765
- */
6766
- const widenTo = (bound, fallback, direction) => {
6767
- const key = bound ? writtenDayKey(bound) : null;
6768
- if (!key) {
6769
- if (bound) warnOnce(`heatmap:gridSpan:${bound}`, `gridSpan.${direction === "earlier" ? "start" : "end"} ${JSON.stringify(bound)} is not a \`yyyy-MM-dd\` day, so the grid is drawn over the series' own span.`);
6770
- return fallback;
6771
- }
6772
- if (direction === "earlier") return key < fallback ? key : fallback;
6773
- return key > fallback ? key : fallback;
6774
- };
6775
- /**
6776
- * Lay a day-bucketed series out as calendar columns.
6777
- *
6778
- * @param series - Points to bucket.
6779
- * @param options - Grid shape, plus the locale and zone to read days in.
6780
- * @return Columns and row labels for `HeatmapChart`.
6781
- */
6782
- const buildCalendarHeatmapData = (series, options = {}) => {
6783
- const weekStartsOn = options.weekStartsOn ?? 1;
6784
- const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
6785
- const { locale, timeZone } = sanitizeFormatting({
6786
- locale: options.locale,
6787
- timeZone: options.timeZone
6788
- });
6789
- const readInstant = instantDayReader(timeZone);
6790
- const valueByDay = /* @__PURE__ */ new Map();
6791
- let minDayKey;
6792
- let maxDayKey;
6793
- for (const point of series) {
6794
- const key = pointDayKey(point, readInstant);
6795
- if (!key) {
6796
- const offending = point.dateString ?? (point.date && String(point.date));
6797
- warnOnce(`heatmap:unreadableDate:${offending}`, offending ? `${JSON.stringify(offending)} is not a day this can read, so its point is left out of the calendar. A \`dateString\` must start \`yyyy-MM-dd\`.` : "A point carries neither `date` nor `dateString`, so it is left out of the calendar.");
6798
- continue;
6799
- }
6800
- valueByDay.set(key, point.value);
6801
- if (!minDayKey || key < minDayKey) minDayKey = key;
6802
- if (!maxDayKey || key > maxDayKey) maxDayKey = key;
6803
- }
6804
- if (!minDayKey || !maxDayKey) return {
6805
- data: [],
6806
- rowLabels: []
6807
- };
6808
- const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
6809
- const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
6810
- const requestedMinDate = civilDate(requestedMinDayKey);
6811
- const gridMaxDate = civilDate(gridMaxDayKey);
6812
- const labelFormatting = {
6813
- locale,
6814
- timeZone: "UTC"
6815
- };
6816
- const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
6817
- const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
6818
- const formatDay = createDateFormatter({
6819
- weekday: "short",
6820
- month: "short",
6821
- day: "numeric",
6822
- year: "numeric"
6823
- }, labelFormatting);
6824
- const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
6825
- const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
6826
- const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
6827
- const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
6828
- const MIN_FIRST_MONTH_WEEKS = 2;
6829
- const firstMonth = gridStart.getUTCMonth();
6830
- let firstMonthWeeks = 0;
6831
- while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
6832
- const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
6833
- const data = [];
6834
- let previousMonth = -1;
6835
- for (let week = 0; week < weekCount; week++) {
6836
- const columnStart = addCivilDays(gridStart, week * 7);
6837
- const month = columnStart.getUTCMonth();
6838
- const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
6839
- previousMonth = month;
6840
- const cells = [];
6841
- for (let row = 0; row < 7; row++) {
6842
- const day = addCivilDays(gridStart, week * 7 + row);
6843
- const key = civilKey(day);
6844
- const cell = {
6845
- label: formatDay(day),
6846
- value: valueByDay.has(key) ? valueByDay.get(key) : null
6847
- };
6848
- if (key < gridMinDayKey || key > gridMaxDayKey) {
6849
- if (hideOutOfRangeDays) cell.hidden = true;
6850
- } else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
6851
- cells.push(cell);
6852
- }
6853
- data.push({
6854
- label,
6855
- data: cells
6856
- });
6857
- }
6858
- return {
6859
- data,
6860
- rowLabels
6861
- };
6862
- };
6863
- //#endregion
6864
- //#region src/charts/heatmap-chart/private/heatmap-context.ts
6865
- /** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
6866
- const HeatmapContext = (0, react.createContext)(null);
6867
- //#endregion
6868
- //#region src/charts/heatmap-chart/private/heatmap-legend.tsx
6869
- const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6870
- const context = (0, react.useContext)(HeatmapContext);
6871
- const { legend } = useGlobalChartsTheme();
6872
- if (!context) return null;
6873
- const { primaryColorHex } = context;
6874
- const labelStyle = legend.labelStyles;
6875
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
6876
- direction: "row",
6877
- gap: "xs",
6878
- align: "center",
6879
- children: [
6880
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6881
- variant: "body-sm",
6882
- className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6883
- style: labelStyle,
6884
- children: lessLabel ?? (0, _wordpress_i18n.__)("Less", "jetpack-charts")
6885
- }),
6886
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
6887
- direction: "row",
6888
- gap: "xs",
6889
- children: Array.from({ length: steps }, (_, index) => {
6890
- const intensity = steps <= 1 ? 1 : index / (steps - 1);
6891
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6892
- "aria-hidden": "true",
6893
- className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6894
- style: {
6895
- "--a8c-charts-color-heatmap-primary": primaryColorHex,
6896
- "--a8c-charts-heatmap-cell-intensity": intensity
6897
- }
6898
- }, index);
6899
- })
6900
- }),
6901
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6902
- variant: "body-sm",
6903
- className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6904
- style: labelStyle,
6905
- children: moreLabel ?? (0, _wordpress_i18n.__)("More", "jetpack-charts")
6906
- })
6907
- ]
6908
- });
7089
+ const stepCalendarCell = (grid, blocks, from, key) => {
7090
+ if (key === "ArrowRight" || key === "ArrowLeft") {
7091
+ const order = readingOrder(grid, blocks);
7092
+ const index = order.findIndex((cell) => cell.column === from.column && cell.row === from.row);
7093
+ if (index < 0) return;
7094
+ const ahead = key === "ArrowRight" ? order.slice(index + 1) : order.slice(0, index).reverse();
7095
+ return firstSelectable(grid, ahead);
7096
+ }
7097
+ if (key === "ArrowDown" || key === "ArrowUp") return firstSelectable(grid, columnOrderFrom(grid, blocks, from, key === "ArrowDown" ? 1 : -1));
7098
+ const blockIndex = blockOf(blocks, from.column);
7099
+ const target = blocks[blockIndex + (key === "PageDown" ? 1 : -1)];
7100
+ if (blockIndex < 0 || !target) return;
7101
+ const same = {
7102
+ column: target.start + (from.column - blocks[blockIndex].start),
7103
+ row: from.row
7104
+ };
7105
+ const page = readingOrder(grid, [target]);
7106
+ const at = page.findIndex((cell) => cell.row > same.row || cell.row === same.row && cell.column >= same.column);
7107
+ const after = at === -1 ? [] : page.slice(at);
7108
+ const before = page.slice(0, at === -1 ? page.length : at).reverse();
7109
+ return firstSelectable(grid, after) ?? firstSelectable(grid, before);
6909
7110
  };
6910
7111
  //#endregion
6911
7112
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
6912
7113
  const CELL_MIX_FLOOR = .15;
6913
7114
  const NO_ROW_LABELS = [];
6914
- 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 }) => {
7115
+ const TOOLTIP_BOX_STYLES = {
7116
+ light: {
7117
+ ..._visx_tooltip.defaultStyles,
7118
+ zIndex: 3
7119
+ },
7120
+ dark: { zIndex: 3 }
7121
+ };
7122
+ const cellName = (info) => info.cellLabel || [
7123
+ info.groupLabel,
7124
+ info.columnLabel,
7125
+ info.rowLabel
7126
+ ].filter(Boolean).join(" ");
7127
+ 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 }) => {
6915
7128
  const chartId = useChartId(providedChartId);
7129
+ const tooltipBoxStyle = tooltipStyle ? {
7130
+ ...TOOLTIP_BOX_STYLES[tooltipVariant],
7131
+ ...tooltipStyle
7132
+ } : TOOLTIP_BOX_STYLES[tooltipVariant];
6916
7133
  const { getElementStyles, theme } = useGlobalChartsContext();
6917
7134
  const scopeElement = useChartScopeElement();
6918
7135
  const { heatmapChart: heatmapChartSettings } = theme;
6919
7136
  const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
6920
- const [selectedIndex, setSelectedIndex] = (0, react.useState)();
7137
+ const [selected, setSelected] = (0, react.useState)();
6921
7138
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
6922
7139
  const standaloneScopeClass = useStandaloneScopeClass();
6923
7140
  const containerRef = (0, react.useRef)(null);
@@ -6936,69 +7153,75 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6936
7153
  }), [extent, primaryColorHex]);
6937
7154
  const columns = data.length;
6938
7155
  const rows = Math.max(0, ...data.map((column) => column.data.length));
6939
- const { compactCellGap, compactCellSize } = heatmapChartSettings;
7156
+ const groupLayout = (0, react.useMemo)(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
7157
+ const { compactCellGap, compactCellSize, groupGap } = heatmapChartSettings;
6940
7158
  const drawValues = showValues ?? !compact;
6941
7159
  const buildTooltipData = (0, react.useCallback)((columnIndex, rowIndex) => {
6942
7160
  const cell = data[columnIndex]?.data[rowIndex];
7161
+ const group = groupLayout.columns[columnIndex]?.group;
6943
7162
  return {
6944
7163
  value: cell?.value ?? null,
6945
7164
  rowLabel: rowLabels[rowIndex],
6946
7165
  columnLabel: data[columnIndex]?.label,
7166
+ groupLabel: group === void 0 ? void 0 : groupLayout.groups[group]?.label,
6947
7167
  cellLabel: cell?.label,
6948
7168
  row: rowIndex,
6949
7169
  column: columnIndex
6950
7170
  };
6951
- }, [data, rowLabels]);
7171
+ }, [
7172
+ data,
7173
+ rowLabels,
7174
+ groupLayout
7175
+ ]);
6952
7176
  const onChartBlur = (0, react.useCallback)(() => {
6953
- setSelectedIndex(void 0);
7177
+ setSelected(void 0);
6954
7178
  hideTooltip();
6955
7179
  }, [hideTooltip]);
6956
7180
  const isCellInert = (0, react.useCallback)((col, row) => {
6957
7181
  const cell = data[col]?.data[row];
6958
7182
  return cell?.hidden === true || cell?.placeholder === true;
6959
7183
  }, [data]);
7184
+ const blocks = (0, react.useMemo)(() => {
7185
+ const grouped = groupLayout.groups.map((group, index) => {
7186
+ const start = groupLayout.columns.findIndex((column) => column.group === index);
7187
+ return {
7188
+ start,
7189
+ end: start + group.span
7190
+ };
7191
+ });
7192
+ const groupedEnd = grouped.length ? grouped[grouped.length - 1].end : 0;
7193
+ return groupedEnd < columns ? [...grouped, {
7194
+ start: groupedEnd,
7195
+ end: columns
7196
+ }] : grouped;
7197
+ }, [groupLayout, columns]);
6960
7198
  const onChartKeyDown = (0, react.useCallback)((event) => {
6961
- if (![
6962
- "ArrowLeft",
6963
- "ArrowRight",
6964
- "ArrowUp",
6965
- "ArrowDown",
6966
- "Escape",
6967
- "Tab"
6968
- ].includes(event.key)) return;
6969
7199
  if (event.key === "Tab" || event.key === "Escape") {
6970
- setSelectedIndex(void 0);
7200
+ setSelected(void 0);
6971
7201
  hideTooltip();
6972
7202
  return;
6973
7203
  }
7204
+ if (!isNavigationKey(event.key, keyboardNavigation)) return;
6974
7205
  event.preventDefault();
6975
- if (selectedIndex === void 0) {
6976
- for (let index = 0; index < columns * rows; index++) if (!isCellInert(Math.floor(index / rows), index % rows)) {
6977
- setSelectedIndex(index);
6978
- return;
6979
- }
7206
+ const grid = {
7207
+ columns,
7208
+ rows,
7209
+ isInert: isCellInert
7210
+ };
7211
+ if (selected === void 0) {
7212
+ setSelected(keyboardNavigation === "calendar" ? firstCalendarCell(grid, blocks) : firstGridCell(grid));
6980
7213
  return;
6981
7214
  }
6982
- let stepCol = 0;
6983
- let stepRow = 0;
6984
- if (event.key === "ArrowRight") stepCol = 1;
6985
- else if (event.key === "ArrowLeft") stepCol = -1;
6986
- else if (event.key === "ArrowDown") stepRow = 1;
6987
- else if (event.key === "ArrowUp") stepRow = -1;
6988
- let col = Math.floor(selectedIndex / rows);
6989
- let row = selectedIndex % rows;
6990
- do {
6991
- col += stepCol;
6992
- row += stepRow;
6993
- } while (col >= 0 && col < columns && row >= 0 && row < rows && isCellInert(col, row));
6994
- if (col < 0 || col >= columns || row < 0 || row >= rows) return;
6995
- setSelectedIndex(col * rows + row);
7215
+ const next = keyboardNavigation === "calendar" ? stepCalendarCell(grid, blocks, selected, event.key) : stepGridCell(grid, selected, event.key);
7216
+ if (next) setSelected(next);
6996
7217
  }, [
6997
7218
  rows,
6998
7219
  columns,
6999
- selectedIndex,
7220
+ selected,
7000
7221
  hideTooltip,
7001
- isCellInert
7222
+ isCellInert,
7223
+ keyboardNavigation,
7224
+ blocks
7002
7225
  ]);
7003
7226
  const handleCellMouseMove = (0, react.useCallback)((event) => {
7004
7227
  if (!withTooltips) return;
@@ -7018,35 +7241,47 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7018
7241
  buildTooltipData,
7019
7242
  getTooltipOrigin
7020
7243
  ]);
7244
+ const getCellElement = (0, react.useCallback)((col, row) => typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null, [chartId]);
7021
7245
  const handleCellMouseLeave = (0, react.useCallback)(() => {
7022
- if (withTooltips && selectedIndex === void 0) hideTooltip();
7246
+ if (withTooltips && selected === void 0) hideTooltip();
7023
7247
  }, [
7024
7248
  withTooltips,
7025
- selectedIndex,
7249
+ selected,
7026
7250
  hideTooltip
7027
7251
  ]);
7028
7252
  (0, react.useEffect)(() => {
7029
- if (!withTooltips || selectedIndex === void 0) return;
7253
+ if (selected && (selected.column >= columns || selected.row >= rows)) setSelected(void 0);
7254
+ }, [
7255
+ selected,
7256
+ columns,
7257
+ rows
7258
+ ]);
7259
+ (0, react.useEffect)(() => {
7260
+ if (selected === void 0) return;
7261
+ getCellElement(selected.column, selected.row)?.scrollIntoView?.({
7262
+ block: "nearest",
7263
+ inline: "nearest"
7264
+ });
7265
+ }, [selected, getCellElement]);
7266
+ (0, react.useEffect)(() => {
7267
+ if (!withTooltips || selected === void 0) return;
7030
7268
  const origin = getTooltipOrigin();
7031
7269
  if (!origin) return;
7032
- const col = Math.floor(selectedIndex / rows);
7033
- const row = selectedIndex % rows;
7034
- const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
7270
+ const rect = getCellElement(selected.column, selected.row)?.getBoundingClientRect();
7035
7271
  showTooltip({
7036
7272
  tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
7037
7273
  tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
7038
- tooltipData: buildTooltipData(col, row)
7274
+ tooltipData: buildTooltipData(selected.column, selected.row)
7039
7275
  });
7040
7276
  }, [
7041
- selectedIndex,
7277
+ selected,
7042
7278
  withTooltips,
7043
- rows,
7044
- chartId,
7279
+ getCellElement,
7045
7280
  buildTooltipData,
7046
7281
  showTooltip,
7047
7282
  getTooltipOrigin
7048
7283
  ]);
7049
- 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) })] }), []);
7284
+ 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) })] }), []);
7050
7285
  if (!columns || !rows) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
7051
7286
  className: (0, clsx.default)("heatmap-chart", heatmap_chart_module_default["heatmap-chart"], className),
7052
7287
  style: {
@@ -7060,10 +7295,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7060
7295
  });
7061
7296
  const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
7062
7297
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
7298
+ const hasColumnLabels = data.some((column) => Boolean(column.label));
7299
+ const hasGroups = groupLayout.groups.length > 0;
7300
+ const columnLine = (columnIndex) => groupLayout.columns[columnIndex].line;
7301
+ const firstDataRow = hasColumnLabels ? 2 : 1;
7302
+ const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
7303
+ const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
7063
7304
  const gridStyle = {
7064
7305
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
7065
- gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
7066
- gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
7306
+ gridTemplateColumns: `auto ${data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ")}`,
7307
+ gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
7067
7308
  };
7068
7309
  if (compact) {
7069
7310
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
@@ -7076,7 +7317,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7076
7317
  [heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
7077
7318
  };
7078
7319
  };
7079
- const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
7320
+ const activeDescendant = selected ? `${chartId}-cell-${selected.column}-${selected.row}` : void 0;
7080
7321
  const heightCapped = !compact && Boolean(maxCellHeight);
7081
7322
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatmapContext.Provider, {
7082
7323
  value: heatmapContext,
@@ -7096,7 +7337,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7096
7337
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7097
7338
  ref: containerRef,
7098
7339
  role: "grid",
7099
- "aria-label": (0, _wordpress_i18n.__)("Heatmap chart", "jetpack-charts"),
7340
+ "aria-label": ariaLabel ?? (0, _wordpress_i18n.__)("Heatmap chart", "jetpack-charts"),
7100
7341
  "aria-rowcount": rows,
7101
7342
  "aria-colcount": columns,
7102
7343
  "aria-activedescendant": activeDescendant,
@@ -7105,76 +7346,115 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7105
7346
  onKeyDown: onChartKeyDown,
7106
7347
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__grid"], {
7107
7348
  [heatmap_chart_module_default["heatmap-chart__grid--compact"]]: compact,
7349
+ [heatmap_chart_module_default["heatmap-chart__grid--flex-gaps"]]: compact && hasGroups,
7108
7350
  [heatmap_chart_module_default["heatmap-chart__grid--height-capped"]]: heightCapped
7109
7351
  }),
7110
7352
  style: gridStyle,
7111
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7112
- role: "row",
7113
- "aria-hidden": "true",
7114
- className: heatmap_chart_module_default["heatmap-chart__row"],
7115
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7116
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
7117
- [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
7118
- ...summaryGaps(columnIndex)
7119
- }),
7120
- children: column.label
7121
- }, `col-${columnIndex}`))]
7122
- }), Array.from({ length: rows }).map((_row, rowIndex) => {
7123
- const labelVisible = !compact || rowIndex % 2 === 0;
7124
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7353
+ children: [
7354
+ hasColumnLabels && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7125
7355
  role: "row",
7126
- "aria-rowindex": rowIndex + 1,
7356
+ "aria-hidden": "true",
7127
7357
  className: heatmap_chart_module_default["heatmap-chart__row"],
7128
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7129
- "aria-hidden": "true",
7130
- className: heatmap_chart_module_default["heatmap-chart__row-label"],
7131
- children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
7132
- }), data.map((column, columnIndex) => {
7133
- const cell = column.data[rowIndex];
7134
- if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7135
- "aria-hidden": "true",
7136
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--hidden"])
7137
- }, `cell-${columnIndex}-${rowIndex}`);
7138
- if (cell?.placeholder) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7358
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
7359
+ gridColumn: 1,
7360
+ gridRow: 1
7361
+ } }), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7362
+ style: {
7363
+ gridColumn: columnLine(columnIndex),
7364
+ gridRow: 1
7365
+ },
7366
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
7367
+ [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
7368
+ ...summaryGaps(columnIndex)
7369
+ }),
7370
+ children: column.label
7371
+ }, `col-${columnIndex}`))]
7372
+ }),
7373
+ Array.from({ length: rows }).map((_row, rowIndex) => {
7374
+ const labelVisible = !compact || rowIndex % 2 === 0;
7375
+ const gridRow = firstDataRow + rowIndex;
7376
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7377
+ role: "row",
7378
+ "aria-rowindex": rowIndex + 1,
7379
+ className: heatmap_chart_module_default["heatmap-chart__row"],
7380
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7139
7381
  "aria-hidden": "true",
7140
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--placeholder"])
7141
- }, `cell-${columnIndex}-${rowIndex}`);
7142
- const value = cell?.value ?? null;
7143
- const present = isPresent(value);
7144
- const filled = present && !column.summary;
7145
- const normalized = filled ? getNormalizedValue(value, extent) : 0;
7146
- const flatIndex = columnIndex * rows + rowIndex;
7147
- const info = buildTooltipData(columnIndex, rowIndex);
7148
- 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)}`;
7149
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7150
- id: `${chartId}-cell-${columnIndex}-${rowIndex}`,
7151
- role: "gridcell",
7152
- tabIndex: -1,
7153
- "aria-colindex": columnIndex + 1,
7154
- "aria-label": accessibleLabel,
7155
- "data-column": columnIndex,
7156
- "data-row": rowIndex,
7157
- className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
7158
- [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
7159
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
7160
- [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
7161
- ...summaryGaps(columnIndex),
7162
- [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
7163
- }),
7164
- style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
7165
- onMouseMove: handleCellMouseMove,
7166
- onMouseLeave: handleCellMouseLeave,
7167
- children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7168
- className: heatmap_chart_module_default["heatmap-chart__cell-value"],
7169
- children: (0, _automattic_number_formatters.formatNumberCompact)(value)
7170
- })
7171
- }, `cell-${columnIndex}-${rowIndex}`);
7172
- })]
7173
- }, `row-${rowIndex}`);
7174
- })]
7382
+ className: heatmap_chart_module_default["heatmap-chart__row-label"],
7383
+ style: {
7384
+ gridColumn: 1,
7385
+ gridRow
7386
+ },
7387
+ children: labelVisible ? rowLabels[rowIndex] ?? "" : ""
7388
+ }), data.map((column, columnIndex) => {
7389
+ const cell = column.data[rowIndex];
7390
+ const placement = {
7391
+ gridColumn: columnLine(columnIndex),
7392
+ gridRow
7393
+ };
7394
+ if (cell?.hidden) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7395
+ "aria-hidden": "true",
7396
+ style: placement,
7397
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--hidden"])
7398
+ }, `cell-${columnIndex}-${rowIndex}`);
7399
+ if (cell?.placeholder) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7400
+ "aria-hidden": "true",
7401
+ style: placement,
7402
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], heatmap_chart_module_default["heatmap-chart__cell--placeholder"])
7403
+ }, `cell-${columnIndex}-${rowIndex}`);
7404
+ const value = cell?.value ?? null;
7405
+ const present = isPresent(value);
7406
+ const filled = present && !column.summary;
7407
+ const normalized = filled ? getNormalizedValue(value, extent) : 0;
7408
+ const info = buildTooltipData(columnIndex, rowIndex);
7409
+ const accessibleLabel = `${cellName(info)}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
7410
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7411
+ id: `${chartId}-cell-${columnIndex}-${rowIndex}`,
7412
+ role: "gridcell",
7413
+ tabIndex: -1,
7414
+ "aria-colindex": columnIndex + 1,
7415
+ "aria-label": accessibleLabel,
7416
+ "data-column": columnIndex,
7417
+ "data-row": rowIndex,
7418
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
7419
+ [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
7420
+ [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
7421
+ [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
7422
+ ...summaryGaps(columnIndex),
7423
+ [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
7424
+ }),
7425
+ style: {
7426
+ ...placement,
7427
+ ...filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : {}
7428
+ },
7429
+ onMouseMove: handleCellMouseMove,
7430
+ onMouseLeave: handleCellMouseLeave,
7431
+ children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7432
+ className: heatmap_chart_module_default["heatmap-chart__cell-value"],
7433
+ children: (0, _automattic_number_formatters.formatNumberCompact)(value)
7434
+ })
7435
+ }, `cell-${columnIndex}-${rowIndex}`);
7436
+ })]
7437
+ }, `row-${rowIndex}`);
7438
+ }),
7439
+ hasGroups && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7440
+ role: "row",
7441
+ "aria-hidden": "true",
7442
+ className: heatmap_chart_module_default["heatmap-chart__row"],
7443
+ children: groupLayout.groups.map((group, groupIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7444
+ className: heatmap_chart_module_default["heatmap-chart__group-label"],
7445
+ style: {
7446
+ gridColumn: `${group.line} / span ${group.span}`,
7447
+ gridRow: firstDataRow + rows
7448
+ },
7449
+ children: group.label
7450
+ }, `group-${groupIndex}`))
7451
+ })
7452
+ ]
7175
7453
  }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
7176
7454
  top: tooltipTop,
7177
7455
  left: tooltipLeft,
7456
+ className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
7457
+ style: tooltipBoxStyle,
7178
7458
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7179
7459
  className: standaloneScopeClass,
7180
7460
  role: "tooltip",
@@ -7200,6 +7480,342 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ (0, react_jsx_run
7200
7480
  HeatmapChartResponsiveInner.displayName = "HeatmapChart";
7201
7481
  const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
7202
7482
  //#endregion
7483
+ //#region src/charts/heatmap-chart/private/civil-day.ts
7484
+ const DAY_MS = 864e5;
7485
+ const KEY_OPTIONS = {
7486
+ calendar: "gregory",
7487
+ numberingSystem: "latn",
7488
+ year: "numeric",
7489
+ month: "2-digit",
7490
+ day: "2-digit"
7491
+ };
7492
+ const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
7493
+ /**
7494
+ * Label formatters for UTC proxies, so both calendar layouts name a day alike.
7495
+ *
7496
+ * `gregory` keeps a locale whose default calendar is not Gregorian (fa-IR) from
7497
+ * splitting one grid month across two names.
7498
+ *
7499
+ * @param locale - BCP 47 locale.
7500
+ * @return Weekday, month and full-day formatters.
7501
+ */
7502
+ const civilLabelFormatters = (locale) => {
7503
+ const formatting = {
7504
+ locale,
7505
+ timeZone: "UTC"
7506
+ };
7507
+ return {
7508
+ formatWeekday: createDateFormatter({
7509
+ weekday: "short",
7510
+ calendar: "gregory"
7511
+ }, formatting),
7512
+ formatMonth: createDateFormatter({
7513
+ month: "short",
7514
+ calendar: "gregory"
7515
+ }, formatting),
7516
+ formatMonthYear: createDateFormatter({
7517
+ month: "short",
7518
+ year: "numeric",
7519
+ calendar: "gregory"
7520
+ }, formatting),
7521
+ formatDay: createDateFormatter({
7522
+ weekday: "short",
7523
+ month: "short",
7524
+ day: "numeric",
7525
+ year: "numeric",
7526
+ calendar: "gregory"
7527
+ }, formatting)
7528
+ };
7529
+ };
7530
+ const pad = (value, length) => String(value).padStart(length, "0");
7531
+ /**
7532
+ * The day a UTC proxy stands for.
7533
+ *
7534
+ * @param date - UTC proxy.
7535
+ * @return Its day key.
7536
+ */
7537
+ const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
7538
+ /**
7539
+ * A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
7540
+ * `DAY_MS`.
7541
+ *
7542
+ * @param key - Day key.
7543
+ * @return The proxy, or null when the key names no real day.
7544
+ */
7545
+ const civilDate = (key) => {
7546
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
7547
+ if (!match) return null;
7548
+ const date = /* @__PURE__ */ new Date(0);
7549
+ date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
7550
+ date.setUTCHours(0, 0, 0, 0);
7551
+ return civilKey(date) === key ? date : null;
7552
+ };
7553
+ /**
7554
+ * The day a written date names, ignoring any time and zone it carries.
7555
+ *
7556
+ * @param text - `yyyy-MM-dd`, optionally followed by a time.
7557
+ * @return Its day key, or null.
7558
+ */
7559
+ const writtenDayKey = (text) => {
7560
+ const match = DATE_PREFIX.exec(text);
7561
+ if (!match) return null;
7562
+ const key = `${match[1]}-${match[2]}-${match[3]}`;
7563
+ return civilDate(key) ? key : null;
7564
+ };
7565
+ /**
7566
+ * Reads the day an instant falls on in a zone.
7567
+ *
7568
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
7569
+ * @return Reader taking a `Date`, null for a day no key can hold.
7570
+ */
7571
+ const instantDayReader = (timeZone) => {
7572
+ const formatter = new Intl.DateTimeFormat("en-US", {
7573
+ ...KEY_OPTIONS,
7574
+ timeZone
7575
+ });
7576
+ return (date) => {
7577
+ const parts = formatter.formatToParts(date);
7578
+ const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
7579
+ const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
7580
+ return civilDate(key) ? key : null;
7581
+ };
7582
+ };
7583
+ /**
7584
+ * The day a series point belongs to.
7585
+ *
7586
+ * A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
7587
+ * zone. Anything else is a wall clock the host wrote, so its date part is the day.
7588
+ *
7589
+ * @param point - Series point.
7590
+ * @param readInstant - Reader from `instantDayReader`.
7591
+ * @return Its day key, or null when the point carries no usable date.
7592
+ */
7593
+ const pointDayKey = (point, readInstant) => {
7594
+ if (point.date instanceof Date && !isNaN(point.date.getTime())) {
7595
+ const key = readInstant(point.date);
7596
+ if (key) return key;
7597
+ }
7598
+ const text = point.dateString;
7599
+ if (!text) return null;
7600
+ if (!hasTimezone(text)) return writtenDayKey(text);
7601
+ const parsed = (0, date_fns.parseISO)(text);
7602
+ return isNaN(parsed.getTime()) ? null : readInstant(parsed);
7603
+ };
7604
+ /**
7605
+ * Steps a proxy by whole days.
7606
+ *
7607
+ * @param date - UTC proxy.
7608
+ * @param days - Days to add; may be negative.
7609
+ * @return The shifted proxy.
7610
+ */
7611
+ const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
7612
+ /**
7613
+ * Rounds a proxy down to its week.
7614
+ *
7615
+ * @param date - UTC proxy.
7616
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
7617
+ * @return The proxy for that week's first day.
7618
+ */
7619
+ const startOfCivilWeek = (date, weekStartsOn) => {
7620
+ const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
7621
+ return addCivilDays(date, -daysSinceWeekStart);
7622
+ };
7623
+ /**
7624
+ * Counts the columns a span needs.
7625
+ *
7626
+ * @param from - Earlier UTC proxy.
7627
+ * @param to - Later UTC proxy.
7628
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
7629
+ * @return Columns needed to cover the span, inclusive.
7630
+ */
7631
+ const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
7632
+ //#endregion
7633
+ //#region src/charts/heatmap-chart/build-calendar-data.ts
7634
+ /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
7635
+ const LABELLED_ROWS = [
7636
+ 0,
7637
+ 2,
7638
+ 4
7639
+ ];
7640
+ /**
7641
+ * Resolve one grid bound: an unparseable or narrowing value falls back to the
7642
+ * series' own bound, so the grid can only ever be widened.
7643
+ *
7644
+ * @param bound - Requested bound as `yyyy-MM-dd`, if any.
7645
+ * @param fallback - The series' own bound on this side.
7646
+ * @param direction - Which way the bound is allowed to move.
7647
+ * @return The day key to draw to.
7648
+ */
7649
+ const widenTo = (bound, fallback, direction) => {
7650
+ const key = bound ? writtenDayKey(bound) : null;
7651
+ if (!key) {
7652
+ if (bound) warnOnce(`heatmap:gridSpan:${bound}`, `gridSpan.${direction === "earlier" ? "start" : "end"} ${JSON.stringify(bound)} is not a \`yyyy-MM-dd\` day, so the grid is drawn over the series' own span.`);
7653
+ return fallback;
7654
+ }
7655
+ if (direction === "earlier") return key < fallback ? key : fallback;
7656
+ return key > fallback ? key : fallback;
7657
+ };
7658
+ /**
7659
+ * Lay a day-bucketed series out as calendar columns.
7660
+ *
7661
+ * @param series - Points to bucket.
7662
+ * @param options - Grid shape, plus the locale and zone to read days in.
7663
+ * @return Columns and row labels for `HeatmapChart`.
7664
+ */
7665
+ const buildCalendarHeatmapData = (series, options = {}) => {
7666
+ const weekStartsOn = options.weekStartsOn ?? 1;
7667
+ const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
7668
+ const { locale, timeZone } = sanitizeFormatting({
7669
+ locale: options.locale,
7670
+ timeZone: options.timeZone
7671
+ });
7672
+ const readInstant = instantDayReader(timeZone);
7673
+ const valueByDay = /* @__PURE__ */ new Map();
7674
+ let minDayKey;
7675
+ let maxDayKey;
7676
+ for (const point of series) {
7677
+ const key = pointDayKey(point, readInstant);
7678
+ if (!key) {
7679
+ const offending = point.dateString ?? (point.date && String(point.date));
7680
+ warnOnce(`heatmap:unreadableDate:${offending}`, offending ? `${JSON.stringify(offending)} is not a day this can read, so its point is left out of the calendar. A \`dateString\` must start \`yyyy-MM-dd\`.` : "A point carries neither `date` nor `dateString`, so it is left out of the calendar.");
7681
+ continue;
7682
+ }
7683
+ valueByDay.set(key, point.value);
7684
+ if (!minDayKey || key < minDayKey) minDayKey = key;
7685
+ if (!maxDayKey || key > maxDayKey) maxDayKey = key;
7686
+ }
7687
+ if (!minDayKey || !maxDayKey) return {
7688
+ data: [],
7689
+ rowLabels: []
7690
+ };
7691
+ const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
7692
+ const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
7693
+ const requestedMinDate = civilDate(requestedMinDayKey);
7694
+ const gridMaxDate = civilDate(gridMaxDayKey);
7695
+ const { formatWeekday, formatMonth, formatDay } = civilLabelFormatters(locale);
7696
+ const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
7697
+ const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
7698
+ const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
7699
+ const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
7700
+ const MIN_FIRST_MONTH_WEEKS = 2;
7701
+ const firstMonth = gridStart.getUTCMonth();
7702
+ let firstMonthWeeks = 0;
7703
+ while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
7704
+ const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
7705
+ const data = [];
7706
+ let previousMonth = -1;
7707
+ for (let week = 0; week < weekCount; week++) {
7708
+ const columnStart = addCivilDays(gridStart, week * 7);
7709
+ const month = columnStart.getUTCMonth();
7710
+ const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
7711
+ previousMonth = month;
7712
+ const cells = [];
7713
+ for (let row = 0; row < 7; row++) {
7714
+ const day = addCivilDays(gridStart, week * 7 + row);
7715
+ const key = civilKey(day);
7716
+ const cell = {
7717
+ label: formatDay(day),
7718
+ value: valueByDay.has(key) ? valueByDay.get(key) : null
7719
+ };
7720
+ if (key < gridMinDayKey || key > gridMaxDayKey) {
7721
+ if (hideOutOfRangeDays) cell.hidden = true;
7722
+ } else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
7723
+ cells.push(cell);
7724
+ }
7725
+ data.push({
7726
+ label,
7727
+ data: cells
7728
+ });
7729
+ }
7730
+ return {
7731
+ data,
7732
+ rowLabels
7733
+ };
7734
+ };
7735
+ //#endregion
7736
+ //#region src/charts/heatmap-chart/build-month-calendar-data.ts
7737
+ /** Week rows per month, fixed so every month block is the same height. */
7738
+ const MONTH_ROWS = 6;
7739
+ const DAYS_PER_WEEK = 7;
7740
+ const startOfCivilMonth = (date) => {
7741
+ const first = new Date(date.getTime());
7742
+ first.setUTCDate(1);
7743
+ return first;
7744
+ };
7745
+ const startOfNextCivilMonth = (date) => {
7746
+ const next = startOfCivilMonth(date);
7747
+ next.setUTCMonth(next.getUTCMonth() + 1);
7748
+ return next;
7749
+ };
7750
+ /**
7751
+ * Lay a day-keyed map out as one month calendar per month, weekdays across and
7752
+ * weeks down, for `HeatmapChart` with `columnGroups`.
7753
+ *
7754
+ * @param valueByDay - Value per `yyyy-MM-dd`. A missing day is `null`.
7755
+ * @param range - The measured days; every month from `start`'s through `end`'s is drawn.
7756
+ * @param options - Week start and label locale.
7757
+ * @return Columns and one group per month; empty for an invalid or reversed range.
7758
+ */
7759
+ const buildMonthCalendarHeatmapData = (valueByDay, range, options = {}) => {
7760
+ const start = civilDate(range.start);
7761
+ const end = civilDate(range.end);
7762
+ if (!start || !end || start.getTime() > end.getTime()) {
7763
+ 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.`);
7764
+ return {
7765
+ data: [],
7766
+ columnGroups: []
7767
+ };
7768
+ }
7769
+ const startKey = civilKey(start);
7770
+ const endKey = civilKey(end);
7771
+ const weekStartsOn = options.weekStartsOn ?? 1;
7772
+ const { locale } = sanitizeFormatting({ locale: options.locale });
7773
+ 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.`);
7774
+ const readValue = (key) => {
7775
+ const value = valueByDay[key];
7776
+ return isPresent(value) ? value : null;
7777
+ };
7778
+ const { formatMonth, formatMonthYear, formatDay } = civilLabelFormatters(locale);
7779
+ const data = [];
7780
+ const columnGroups = [];
7781
+ const lastMonthStart = startOfCivilMonth(end);
7782
+ const formatGroup = (lastMonthStart.getUTCFullYear() - start.getUTCFullYear()) * 12 + lastMonthStart.getUTCMonth() - start.getUTCMonth() + 1 > 12 ? formatMonthYear : formatMonth;
7783
+ for (let monthStart = startOfCivilMonth(start); monthStart.getTime() <= lastMonthStart.getTime(); monthStart = startOfNextCivilMonth(monthStart)) {
7784
+ const monthStartKey = civilKey(monthStart);
7785
+ const monthEndKey = civilKey(addCivilDays(startOfNextCivilMonth(monthStart), -1));
7786
+ const gridStart = startOfCivilWeek(monthStart, weekStartsOn);
7787
+ for (let weekday = 0; weekday < DAYS_PER_WEEK; weekday++) {
7788
+ const cells = [];
7789
+ for (let week = 0; week < MONTH_ROWS; week++) {
7790
+ const day = addCivilDays(gridStart, week * DAYS_PER_WEEK + weekday);
7791
+ const key = civilKey(day);
7792
+ if (key < monthStartKey || key > monthEndKey) cells.push({
7793
+ value: null,
7794
+ hidden: true
7795
+ });
7796
+ else if (key < startKey || key > endKey) cells.push({
7797
+ label: formatDay(day),
7798
+ value: null,
7799
+ placeholder: true
7800
+ });
7801
+ else cells.push({
7802
+ label: formatDay(day),
7803
+ value: readValue(key)
7804
+ });
7805
+ }
7806
+ data.push({ data: cells });
7807
+ }
7808
+ columnGroups.push({
7809
+ label: formatGroup(monthStart),
7810
+ span: DAYS_PER_WEEK
7811
+ });
7812
+ }
7813
+ return {
7814
+ data,
7815
+ columnGroups
7816
+ };
7817
+ };
7818
+ //#endregion
7203
7819
  //#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
7204
7820
  /**
7205
7821
  * `buildCalendarHeatmapData` with the locale and zone taken from
@@ -7236,6 +7852,36 @@ const useCalendarHeatmapData = (series, options = {}) => {
7236
7852
  ]);
7237
7853
  };
7238
7854
  //#endregion
7855
+ //#region src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts
7856
+ /**
7857
+ * `buildMonthCalendarHeatmapData` with the locale taken from `GlobalChartsProvider`
7858
+ * where the caller names none.
7859
+ *
7860
+ * @param valueByDay - Value per `yyyy-MM-dd`. Held by reference, so a caller that rebuilds it each render defeats the memo.
7861
+ * @param range - As for `buildMonthCalendarHeatmapData`.
7862
+ * @param options - As for `buildMonthCalendarHeatmapData`; `locale` wins over the provider.
7863
+ * @return Columns and groups for `HeatmapChart`.
7864
+ */
7865
+ const useMonthCalendarHeatmapData = (valueByDay, range, options = {}) => {
7866
+ const formatting = useChartFormatting();
7867
+ const locale = options.locale ?? formatting.locale;
7868
+ const { weekStartsOn } = options;
7869
+ const { start, end } = range;
7870
+ return (0, react.useMemo)(() => buildMonthCalendarHeatmapData(valueByDay, {
7871
+ start,
7872
+ end
7873
+ }, {
7874
+ locale,
7875
+ weekStartsOn
7876
+ }), [
7877
+ valueByDay,
7878
+ start,
7879
+ end,
7880
+ locale,
7881
+ weekStartsOn
7882
+ ]);
7883
+ };
7884
+ //#endregion
7239
7885
  //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
7240
7886
  /*!
7241
7887
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
@@ -7599,7 +8245,24 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
7599
8245
  tslib_1.__exportStar(require_dist$1(), exports);
7600
8246
  }));
7601
8247
  //#endregion
7602
- //#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
8248
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.55.0/node_modules/@wordpress/warning/build-module/utils.mjs
8249
+ var logged = /* @__PURE__ */ new Set();
8250
+ //#endregion
8251
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.55.0/node_modules/@wordpress/warning/build-module/index.mjs
8252
+ function isDev() {
8253
+ return globalThis.SCRIPT_DEBUG === true;
8254
+ }
8255
+ function warning(message) {
8256
+ if (!isDev()) return;
8257
+ if (logged.has(message)) return;
8258
+ console.warn(message);
8259
+ try {
8260
+ throw Error(message);
8261
+ } catch {}
8262
+ logged.add(message);
8263
+ }
8264
+ //#endregion
8265
+ //#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
7603
8266
  function useUpdateEffect(effect, deps) {
7604
8267
  const mountedRef = (0, react.useRef)(false);
7605
8268
  (0, react.useEffect)(() => {
@@ -9237,7 +9900,7 @@ _createEmotion.css;
9237
9900
  _createEmotion.sheet;
9238
9901
  _createEmotion.cache;
9239
9902
  //#endregion
9240
- //#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
9903
+ //#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
9241
9904
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
9242
9905
  var useCx = () => {
9243
9906
  const cache = __unsafe_useEmotionCache();
@@ -9344,7 +10007,7 @@ function memize(fn, options) {
9344
10007
  return memoized;
9345
10008
  }
9346
10009
  //#endregion
9347
- //#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
10010
+ //#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
9348
10011
  var white = "#fff";
9349
10012
  var GRAY = {
9350
10013
  900: "#1e1e1e",
@@ -9394,8 +10057,8 @@ var UI = {
9394
10057
  border: THEME.gray[600],
9395
10058
  borderHover: THEME.gray[700],
9396
10059
  borderFocus: THEME.accent,
9397
- borderDisabled: THEME.gray[400],
9398
- textDisabled: THEME.gray[600],
10060
+ borderDisabled: `var(--wpds-color-stroke-interactive-neutral-disabled, #dbdbdb)`,
10061
+ textDisabled: `var(--wpds-color-foreground-interactive-neutral-disabled, #8d8d8d)`,
9399
10062
  darkGrayPlaceholder: `color-mix(in srgb, ${THEME.foreground}, transparent 38%)`,
9400
10063
  lightGrayPlaceholder: `color-mix(in srgb, ${THEME.background}, transparent 35%)`
9401
10064
  };
@@ -9423,7 +10086,7 @@ var COLORS = Object.freeze({
9423
10086
  ui: UI
9424
10087
  });
9425
10088
  //#endregion
9426
- //#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
10089
+ //#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
9427
10090
  var CONTROL_HEIGHT = "36px";
9428
10091
  var CONTROL_PROPS = {
9429
10092
  controlPaddingX: 12,
@@ -9477,8 +10140,8 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
9477
10140
  transitionTimingFunctionControl: "cubic-bezier(0.12, 0.8, 0.32, 1)"
9478
10141
  });
9479
10142
  //#endregion
9480
- //#region ../../../node_modules/.pnpm/fast-deep-equal@3.1.3/node_modules/fast-deep-equal/es6/index.js
9481
- var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
10143
+ //#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
10144
+ var import_es6 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
9482
10145
  module.exports = function equal(a, b) {
9483
10146
  if (a === b) return true;
9484
10147
  if (a && b && typeof a == "object" && typeof b == "object") {
@@ -9522,31 +10185,8 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9522
10185
  }
9523
10186
  return a !== a && b !== b;
9524
10187
  };
9525
- }));
9526
- //#endregion
9527
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
9528
- var logged = /* @__PURE__ */ new Set();
9529
- //#endregion
9530
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
9531
- function isDev() {
9532
- return globalThis.SCRIPT_DEBUG === true;
9533
- }
9534
- function warning(message) {
9535
- if (!isDev()) return;
9536
- if (logged.has(message)) return;
9537
- console.warn(message);
9538
- try {
9539
- throw Error(message);
9540
- } catch {}
9541
- logged.add(message);
9542
- }
9543
- //#endregion
9544
- //#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
9545
- var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
9546
- var ComponentsContext = (0, react.createContext)(
9547
- /** @type {Record<string, any>} */
9548
- {}
9549
- );
10188
+ })))(), 1);
10189
+ var ComponentsContext = (0, react.createContext)({});
9550
10190
  ComponentsContext.displayName = "ComponentsContext";
9551
10191
  var useComponentsContext = () => (0, react.useContext)(ComponentsContext);
9552
10192
  function useContextSystemBridge({ value }) {
@@ -9568,19 +10208,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
9568
10208
  };
9569
10209
  (0, react.memo)(BaseContextSystemProvider);
9570
10210
  //#endregion
9571
- //#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
10211
+ //#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
9572
10212
  var COMPONENT_NAMESPACE = "data-wp-component";
9573
10213
  var CONNECTED_NAMESPACE = "data-wp-c16t";
9574
10214
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
9575
10215
  //#endregion
9576
- //#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
10216
+ //#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
9577
10217
  var import_dist = require_dist();
9578
10218
  function getStyledClassName(namespace) {
9579
10219
  return `components-${(0, import_dist.paramCase)(namespace)}`;
9580
10220
  }
9581
10221
  var getStyledClassNameFromKey = memize(getStyledClassName);
9582
10222
  //#endregion
9583
- //#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
10223
+ //#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
9584
10224
  function contextConnect(Component, namespace) {
9585
10225
  return _contextConnect(Component, namespace, { forwardsRef: true });
9586
10226
  }
@@ -9597,7 +10237,7 @@ function _contextConnect(Component, namespace, options) {
9597
10237
  });
9598
10238
  }
9599
10239
  //#endregion
9600
- //#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
10240
+ //#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
9601
10241
  function getNamespace(componentName) {
9602
10242
  return { [COMPONENT_NAMESPACE]: componentName };
9603
10243
  }
@@ -9605,7 +10245,7 @@ function getConnectedNamespace() {
9605
10245
  return { [CONNECTED_NAMESPACE]: true };
9606
10246
  }
9607
10247
  //#endregion
9608
- //#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
10248
+ //#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
9609
10249
  function useContextSystem(props, namespace) {
9610
10250
  const contextSystemProps = useComponentsContext();
9611
10251
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -9614,10 +10254,12 @@ function useContextSystem(props, namespace) {
9614
10254
  ...getConnectedNamespace(),
9615
10255
  ...getNamespace(namespace)
9616
10256
  };
9617
- const { _overrides: overrideProps, ...otherContextProps } = contextProps;
10257
+ const { _overrides, ...otherContextProps } = contextProps;
10258
+ const overrideProps = _overrides;
9618
10259
  const initialMergedProps = Object.entries(otherContextProps).length ? Object.assign({}, otherContextProps, props) : props;
10260
+ const propsWithChildren = initialMergedProps;
9619
10261
  const classes = useCx()(getStyledClassNameFromKey(namespace), props.className);
9620
- const rendered = typeof initialMergedProps.renderChildren === "function" ? initialMergedProps.renderChildren(initialMergedProps) : initialMergedProps.children;
10262
+ const rendered = typeof propsWithChildren.renderChildren === "function" ? propsWithChildren.renderChildren(initialMergedProps) : propsWithChildren.children;
9621
10263
  for (const key in initialMergedProps) finalComponentProps[key] = initialMergedProps[key];
9622
10264
  for (const key in overrideProps) finalComponentProps[key] = overrideProps[key];
9623
10265
  if (rendered !== void 0) finalComponentProps.children = rendered;
@@ -9625,7 +10267,7 @@ function useContextSystem(props, namespace) {
9625
10267
  return finalComponentProps;
9626
10268
  }
9627
10269
  //#endregion
9628
- //#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
10270
+ //#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
9629
10271
  var visuallyHidden = {
9630
10272
  border: 0,
9631
10273
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -9641,7 +10283,7 @@ var visuallyHidden = {
9641
10283
  wordBreak: "normal"
9642
10284
  };
9643
10285
  //#endregion
9644
- //#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
10286
+ //#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
9645
10287
  var customAttributeRegExp = /^(data|aria|x)-/i;
9646
10288
  var eventHandlerRegExp = /^on[A-Z]/;
9647
10289
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -9731,7 +10373,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
9731
10373
  }
9732
10374
  var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
9733
10375
  //#endregion
9734
- //#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
10376
+ //#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
9735
10377
  function UnforwardedView({ css, ...restProps }, ref) {
9736
10378
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
9737
10379
  ref,
@@ -9740,7 +10382,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
9740
10382
  }
9741
10383
  var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
9742
10384
  //#endregion
9743
- //#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
10385
+ //#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
9744
10386
  function UnconnectedVisuallyHidden(props, forwardedRef) {
9745
10387
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
9746
10388
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -9755,7 +10397,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
9755
10397
  }
9756
10398
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
9757
10399
  //#endregion
9758
- //#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
10400
+ //#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
9759
10401
  var breakpoints = [
9760
10402
  "40em",
9761
10403
  "52em",
@@ -9789,7 +10431,7 @@ function useResponsiveValue(values, options = {}) {
9789
10431
  return array[index >= array.length ? array.length - 1 : index];
9790
10432
  }
9791
10433
  //#endregion
9792
- //#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
10434
+ //#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
9793
10435
  var ALIGNMENTS = {
9794
10436
  bottom: {
9795
10437
  alignItems: "flex-end",
@@ -9837,7 +10479,7 @@ function getAlignmentProps(alignment) {
9837
10479
  return alignment ? ALIGNMENTS[alignment] : {};
9838
10480
  }
9839
10481
  //#endregion
9840
- //#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
10482
+ //#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
9841
10483
  function useGrid(props) {
9842
10484
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
9843
10485
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -9879,7 +10521,7 @@ function useGrid(props) {
9879
10521
  };
9880
10522
  }
9881
10523
  //#endregion
9882
- //#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
10524
+ //#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
9883
10525
  function UnconnectedGrid(props, forwardedRef) {
9884
10526
  const gridProps = useGrid(props);
9885
10527
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -11165,6 +11807,7 @@ exports.SparklineUnresponsive = SparklineUnresponsive;
11165
11807
  exports.ThemeProvider = GlobalChartsProvider;
11166
11808
  exports.TrendIndicator = TrendIndicator;
11167
11809
  exports.buildCalendarHeatmapData = buildCalendarHeatmapData;
11810
+ exports.buildMonthCalendarHeatmapData = buildMonthCalendarHeatmapData;
11168
11811
  exports.defaultTheme = defaultTheme;
11169
11812
  exports.formatMetricValue = formatMetricValue;
11170
11813
  exports.formatPercentage = formatPercentage;
@@ -11189,6 +11832,7 @@ exports.useChartScopeElement = useChartScopeElement;
11189
11832
  exports.useGlobalChartsContext = useGlobalChartsContext;
11190
11833
  exports.useGlobalChartsTheme = useGlobalChartsTheme;
11191
11834
  exports.useLeaderboardLegendItems = useLeaderboardLegendItems;
11835
+ exports.useMonthCalendarHeatmapData = useMonthCalendarHeatmapData;
11192
11836
  exports.validateHexColor = validateHexColor;
11193
11837
 
11194
11838
  //# sourceMappingURL=index.cjs.map