@automattic/charts 4.6.0 → 4.8.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 (91) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/TOKENS.md +15 -10
  3. package/dist/index.cjs +427 -211
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +40 -26
  6. package/dist/index.d.cts +69 -29
  7. package/dist/index.d.ts +69 -29
  8. package/dist/index.js +427 -213
  9. package/dist/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/charts/area-chart/area-chart.tsx +2 -2
  12. package/src/charts/area-chart/test/area-chart.test.tsx +1 -1
  13. package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +1 -1
  14. package/src/charts/bar-chart/bar-chart.module.scss +7 -0
  15. package/src/charts/bar-chart/bar-chart.tsx +20 -14
  16. package/src/charts/bar-chart/private/comparison-bars.tsx +1 -0
  17. package/src/charts/bar-chart/test/bar-chart.test.tsx +25 -0
  18. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +0 -10
  19. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +5 -3
  20. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +20 -1
  21. package/src/charts/conversion-funnel-chart/types.ts +3 -0
  22. package/src/charts/heatmap-chart/heatmap-chart.module.scss +16 -7
  23. package/src/charts/heatmap-chart/heatmap-chart.tsx +80 -53
  24. package/src/charts/heatmap-chart/private/heatmap-context.ts +2 -2
  25. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +2 -2
  26. package/src/charts/heatmap-chart/private/heatmap-scale.ts +74 -0
  27. package/src/charts/heatmap-chart/private/index.ts +2 -1
  28. package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +24 -6
  29. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +221 -25
  30. package/src/charts/heatmap-chart/test/heatmap-scale.test.ts +78 -0
  31. package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +18 -6
  32. package/src/charts/heatmap-chart/types.ts +6 -9
  33. package/src/charts/line-chart/crosshair-style-types.fixture.ts +23 -0
  34. package/src/charts/line-chart/line-chart.module.scss +0 -5
  35. package/src/charts/line-chart/line-chart.tsx +10 -42
  36. package/src/charts/line-chart/test/line-chart.test.tsx +19 -47
  37. package/src/charts/pie-chart/pie-chart.module.scss +9 -0
  38. package/src/charts/pie-chart/pie-chart.tsx +26 -80
  39. package/src/charts/pie-chart/test/pie-chart.test.tsx +21 -8
  40. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +4 -4
  41. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +1 -1
  42. package/src/charts/private/chart-composition/chart-html.tsx +1 -1
  43. package/src/charts/private/chart-composition/chart-svg.tsx +1 -1
  44. package/src/charts/private/label-text-color.ts +116 -0
  45. package/src/charts/private/radial-wipe-animation/radial-wipe-animation.tsx +2 -0
  46. package/src/charts/private/svg-empty-state/svg-empty-state.tsx +1 -1
  47. package/src/charts/private/test/label-text-color.test.ts +189 -0
  48. package/src/charts/private/with-responsive/test/with-responsive.test.tsx +1 -0
  49. package/src/charts/private/with-responsive/with-responsive.tsx +2 -5
  50. package/src/components/tooltip/base-tooltip.tsx +31 -30
  51. package/src/components/tooltip/index.ts +3 -1
  52. package/src/components/tooltip/private/bounded-tooltip.tsx +32 -13
  53. package/src/components/tooltip/private/label-value-content.tsx +8 -0
  54. package/src/components/tooltip/private/tooltip-theme.tsx +37 -0
  55. package/src/components/tooltip/private/xy-chart-tooltip-overlay.tsx +336 -0
  56. package/src/components/tooltip/test/bounded-tooltip.test.tsx +82 -1
  57. package/src/components/tooltip/test/tool-tip.test.tsx +10 -3
  58. package/src/components/tooltip/test/tooltip-box.test.tsx +53 -0
  59. package/src/components/tooltip/test/tooltip-theme.test.tsx +61 -0
  60. package/src/components/tooltip/test/xy-chart-tooltip-overlay.test.tsx +445 -0
  61. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +286 -356
  62. package/src/components/tooltip/tooltip-box.module.scss +21 -0
  63. package/src/components/tooltip/tooltip-box.tsx +43 -0
  64. package/src/components/tooltip/types.ts +7 -1
  65. package/src/components/tooltip/xy-chart-tooltip.tsx +411 -301
  66. package/src/components/trend-indicator/trend-indicator.tsx +1 -0
  67. package/src/hooks/index.ts +1 -0
  68. package/src/hooks/test/use-chart-margin.test.tsx +8 -4
  69. package/src/hooks/test/use-xychart-theme.test.tsx +3 -3
  70. package/src/hooks/use-chart-margin.tsx +21 -18
  71. package/src/hooks/use-isomorphic-layout-effect.ts +5 -0
  72. package/src/hooks/use-xychart-theme.ts +4 -12
  73. package/src/index.ts +8 -3
  74. package/src/providers/chart-context/global-charts-provider.tsx +5 -14
  75. package/src/providers/chart-context/hooks/use-default-hidden-series.ts +3 -5
  76. package/src/providers/chart-context/private/catalog-pointers.ts +1 -0
  77. package/src/providers/chart-context/private/perceptual-color.ts +45 -4
  78. package/src/providers/chart-context/private/resolve-opaque-hex.ts +16 -0
  79. package/src/providers/chart-context/test/perceptual-color.test.ts +31 -0
  80. package/src/providers/chart-context/test/use-default-hidden-series.test.tsx +1 -1
  81. package/src/styles/chart-scope-class.ts +5 -0
  82. package/src/styles/chart-scope.scss +10 -6
  83. package/src/styles/test/chart-scope.test.ts +3 -3
  84. package/src/test-utils/zoned-parsing-suite.ts +0 -10
  85. package/src/utils/color-utils.ts +2 -1
  86. package/src/utils/test/date-formatting.test.ts +0 -10
  87. package/src/visx/types.ts +1 -1
  88. package/src/charts/line-chart/test/crosshair-style-types.test.ts +0 -31
  89. package/src/components/tooltip/accessible-tooltip.tsx +0 -433
  90. package/src/components/tooltip/base-tooltip.module.scss +0 -20
  91. package/src/components/tooltip/test/accessible-tooltip.test.tsx +0 -331
package/dist/index.cjs CHANGED
@@ -29,18 +29,19 @@ let clsx = require("clsx");
29
29
  clsx = __toESM(clsx, 1);
30
30
  let react = require("react");
31
31
  react = __toESM(react);
32
- let _visx_vendor_d3_color = require("@visx/vendor/d3-color");
33
32
  let date_fns = require("date-fns");
34
33
  let _date_fns_tz = require("@date-fns/tz");
35
34
  let _visx_text = require("@visx/text");
36
35
  let deepmerge = require("deepmerge");
37
36
  deepmerge = __toESM(deepmerge, 1);
37
+ let _visx_vendor_d3_color = require("@visx/vendor/d3-color");
38
38
  let react_jsx_runtime = require("react/jsx-runtime");
39
39
  let _visx_tooltip = require("@visx/tooltip");
40
40
  let _visx_scale = require("@visx/scale");
41
41
  let _visx_group = require("@visx/group");
42
42
  let _visx_legend = require("@visx/legend");
43
43
  let _wordpress_ui = require("@wordpress/ui");
44
+ let _wordpress_theme = require("@wordpress/theme");
44
45
  let react_dom = require("react-dom");
45
46
  let _visx_gradient = require("@visx/gradient");
46
47
  let _visx_curve = require("@visx/curve");
@@ -562,7 +563,8 @@ const relativeLuminance = (hex) => {
562
563
  * Whether light text reads better than dark text on the given background, using the W3C
563
564
  * luminance threshold (0.179) that maximizes contrast against black vs white.
564
565
  *
565
- * @param backgroundHex - Hex background color
566
+ * @deprecated Assumes black and white text, so it ignores label role overrides and cannot guarantee AA; charts no longer use it, and it will be removed in a future major version.
567
+ * @param backgroundHex - Hex background color
566
568
  * @return true if light text should be used; false (dark text) for malformed colors
567
569
  */
568
570
  const prefersLightText = (backgroundHex) => {
@@ -834,6 +836,7 @@ const CATALOG_POINTERS = {
834
836
  annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
835
837
  surface: "var(--a8c-charts-color-surface, #fff)",
836
838
  surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
839
+ track: "var(--a8c-charts-color-track, #f0f0f0)",
837
840
  trendUp: "var(--a8c-charts-color-trend-up, #008030)",
838
841
  trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
839
842
  series: SERIES_PALETTE_POINTERS
@@ -881,14 +884,27 @@ const PROTANOPIA = [
881
884
  const DEGREES = Math.PI / 180;
882
885
  const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
883
886
  const fromLinear = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - .055;
884
- const hexToLinear = (hex) => {
887
+ /**
888
+ * The sRGB channels of a hex color.
889
+ *
890
+ * @param hex - A six-digit hex color.
891
+ * @return Channels from 0 to 255.
892
+ * @throws {Error} if the hex string is malformed
893
+ */
894
+ const hexToRgb = (hex) => {
885
895
  validateHexColor(hex);
886
- return [
896
+ const [r, g, b] = [
887
897
  1,
888
898
  3,
889
899
  5
890
- ].map((start) => toLinear(parseInt(hex.slice(start, start + 2), 16) / 255));
900
+ ].map((start) => parseInt(hex.slice(start, start + 2), 16));
901
+ return [
902
+ r,
903
+ g,
904
+ b
905
+ ];
891
906
  };
907
+ const hexToLinear = (hex) => hexToRgb(hex).map((channel) => toLinear(channel / 255));
892
908
  const linearToHex = (linear) => "#" + linear.map((channel) => Math.round(Math.min(1, Math.max(0, fromLinear(channel))) * 255).toString(16).padStart(2, "0")).join("");
893
909
  const simulate = (matrix, [r, g, b]) => [
894
910
  0,
@@ -1014,6 +1030,35 @@ const hexToOklch = (hex) => {
1014
1030
  };
1015
1031
  };
1016
1032
  /**
1033
+ * `foreground` over `background` at `weight`, left unrounded as CSS `color-mix` and alpha compositing leave it.
1034
+ *
1035
+ * A mix rounded to hex can land on the other side of a contrast threshold from the fill the browser paints.
1036
+ *
1037
+ * @param foreground - The color weighted by `weight`.
1038
+ * @param background - The color it is mixed over.
1039
+ * @param weight - Share of `foreground`, from 0 to 1.
1040
+ * @return The mixed channels.
1041
+ */
1042
+ const blendRgb = (foreground, background, weight) => {
1043
+ const share = Math.min(1, Math.max(0, weight));
1044
+ const [r, g, b] = foreground.map((channel, i) => background[i] + (channel - background[i]) * share);
1045
+ return [
1046
+ r,
1047
+ g,
1048
+ b
1049
+ ];
1050
+ };
1051
+ /**
1052
+ * WCAG relative luminance of unrounded sRGB channels.
1053
+ *
1054
+ * @param rgb - Channels from 0 to 255.
1055
+ * @return Relative luminance from 0 to 1.
1056
+ */
1057
+ const rgbLuminance = (rgb) => {
1058
+ const [r, g, b] = rgb.map((channel) => toLinear(channel / 255));
1059
+ return .2126 * r + .7152 * g + .0722 * b;
1060
+ };
1061
+ /**
1017
1062
  * WCAG contrast ratio between two relative luminances.
1018
1063
  *
1019
1064
  * @param first - First relative luminance.
@@ -1220,6 +1265,14 @@ const buildPaletteGenerator = (normalizedSeeds, background, labelColors) => {
1220
1265
  };
1221
1266
  };
1222
1267
  //#endregion
1268
+ //#region src/providers/chart-context/private/resolve-opaque-hex.ts
1269
+ const resolveOpaqueHex = (pointer, resolve) => {
1270
+ const raw = resolve(pointer);
1271
+ if (!raw || (0, _visx_vendor_d3_color.color)(raw)?.opacity !== 1) return null;
1272
+ const hex = normalizeColorToHex(raw);
1273
+ return isValidHexColor(hex) ? hex : null;
1274
+ };
1275
+ //#endregion
1223
1276
  //#region src/providers/chart-context/themes.ts
1224
1277
  /**
1225
1278
  * Default theme configuration: the shape and spacing a consumer can override.
@@ -1268,12 +1321,6 @@ const defaultTheme = {
1268
1321
  };
1269
1322
  //#endregion
1270
1323
  //#region src/providers/chart-context/global-charts-provider.tsx
1271
- const resolveOpaqueHex = (pointer, element) => {
1272
- const raw = resolveCssVariable(pointer, element);
1273
- if (!raw || (0, _visx_vendor_d3_color.color)(raw)?.opacity !== 1) return null;
1274
- const hex = normalizeColorToHex(pointer, element, resolveCssVariable);
1275
- return isValidHexColor(hex) ? hex : null;
1276
- };
1277
1324
  const PLACEHOLDER_LABEL_COLORS = [LABEL_FALLBACK, LABEL_INVERSE_FALLBACK];
1278
1325
  const GlobalChartsContext = (0, react.createContext)(null);
1279
1326
  const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
@@ -1301,8 +1348,9 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
1301
1348
  const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
1302
1349
  if (isValidHexColor(normalizedColor)) resolvedColors.push(normalizedColor);
1303
1350
  }
1304
- const backgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, wrapperRef.current) ?? "#ffffff";
1305
- const labelColors = [CATALOG_POINTERS.label, CATALOG_POINTERS.labelInverse].map((pointer) => resolveOpaqueHex(pointer, wrapperRef.current)).filter((hex) => hex !== null);
1351
+ const resolveAtWrapper = createCssVariableResolver(wrapperRef.current);
1352
+ const backgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, resolveAtWrapper) ?? "#ffffff";
1353
+ const labelColors = [CATALOG_POINTERS.label, CATALOG_POINTERS.labelInverse].map((pointer) => resolveOpaqueHex(pointer, resolveAtWrapper)).filter((hex) => hex !== null);
1306
1354
  setColorCache({
1307
1355
  colors: resolvedColors,
1308
1356
  background: backgroundHex,
@@ -1534,15 +1582,13 @@ const useXYChartTheme = (data) => {
1534
1582
  const resolve = createCssVariableResolver(scopeElement);
1535
1583
  const resolveColor = (value) => value ? resolve(value) ?? value : value;
1536
1584
  const paletteColors = [...JSON.parse(seriesColorKey), ...CATALOG_POINTERS.series].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1537
- const resolvedLabelColor = resolveColor(CATALOG_POINTERS.labelAxis);
1538
- const htmlLabelColor = resolvedLabelColor ? normalizeColorToHex(resolvedLabelColor) || resolvedLabelColor : resolvedLabelColor;
1539
1585
  return (0, _visx_xychart.buildChartTheme)({
1540
1586
  ...theme,
1541
1587
  gridColor: "",
1542
1588
  gridColorDark: "",
1543
1589
  colors: paletteColors,
1544
1590
  backgroundColor: resolveColor(CATALOG_POINTERS.background),
1545
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1591
+ htmlLabel: { color: CATALOG_POINTERS.labelAxis },
1546
1592
  gridStyles: {
1547
1593
  ...theme.gridStyles,
1548
1594
  stroke: CATALOG_POINTERS.grid
@@ -1640,10 +1686,10 @@ const DEFAULT_MARGIN_BOTTOM = 20;
1640
1686
  */
1641
1687
  const DEFAULT_MARGIN_LEFT = 20;
1642
1688
  /**
1643
- * Bottom margin to use when the X-axis is rendered at the top.
1689
+ * Bottom margin when no X-axis sits below the chart, because it is on top or hidden.
1644
1690
  * We only need a small buffer below the chart in that case.
1645
1691
  */
1646
- const DEFAULT_BOTTOM_FOR_TOP_AXIS = 10;
1692
+ const DEFAULT_BOTTOM_WITHOUT_X_AXIS = 10;
1647
1693
  /**
1648
1694
  * Fallback font size used when we cannot derive a font size
1649
1695
  * from the theme or axis styles for X-axis tick labels.
@@ -1723,18 +1769,18 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1723
1769
  if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1724
1770
  else defaultMargin.left = yMarginValue;
1725
1771
  }
1726
- const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1727
- const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1728
- const computedXMargin = fontSize + tickLength;
1729
- if (xOrientation === "top") {
1730
- defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1731
- defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
1732
- } else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
1733
1772
  if (options.axis?.x?.display !== false) {
1773
+ const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1774
+ const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1775
+ const computedXMargin = fontSize + tickLength;
1776
+ if (xOrientation === "top") {
1777
+ defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1778
+ defaultMargin.bottom = DEFAULT_BOTTOM_WITHOUT_X_AXIS;
1779
+ } else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
1734
1780
  const { first, last } = getEdgeTickWidths(options.axis?.x?.tickValues ?? [], options.axis?.x?.tickFormat, tickLabelStyle);
1735
1781
  defaultMargin.left = Math.max(defaultMargin.left, Math.ceil(first / 2));
1736
1782
  defaultMargin.right = Math.max(defaultMargin.right, Math.ceil(last / 2));
1737
- }
1783
+ } else defaultMargin.bottom = DEFAULT_BOTTOM_WITHOUT_X_AXIS;
1738
1784
  return defaultMargin;
1739
1785
  }, [
1740
1786
  options,
@@ -1977,6 +2023,9 @@ function usePrefersReducedMotion() {
1977
2023
  return prefersReducedMotion;
1978
2024
  }
1979
2025
  //#endregion
2026
+ //#region src/hooks/use-isomorphic-layout-effect.ts
2027
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
2028
+ //#endregion
1980
2029
  //#region src/providers/chart-context/hooks/use-chart-registration.ts
1981
2030
  const useChartRegistration = ({ chartId, legendItems, chartType, isDataValid, metadata }) => {
1982
2031
  const { registerChart, unregisterChart } = useGlobalChartsContext();
@@ -2443,33 +2492,92 @@ function useChartLegendItems(data, options = {}, legendShape) {
2443
2492
  ]);
2444
2493
  }
2445
2494
  //#endregion
2446
- //#region src/components/tooltip/base-tooltip.module.scss
2447
- var base_tooltip_module_default = {
2448
- "surface": "a8ccharts--zY0xG-surface",
2449
- "tooltip": "a8ccharts--zY0xG-tooltip"
2495
+ //#region src/components/tooltip/private/label-value-content.tsx
2496
+ const LabelValueContent = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2497
+ data.label,
2498
+ ": ",
2499
+ data.valueDisplay || (0, _automattic_number_formatters.formatNumber)(data.value)
2500
+ ] });
2501
+ //#endregion
2502
+ //#region src/components/tooltip/private/tooltip-theme.tsx
2503
+ const DEFAULT_SURFACE = "#1e1e1e";
2504
+ const TOOLTIP_SURFACE = `var(--a8c-charts-color-tooltip-surface, ${DEFAULT_SURFACE})`;
2505
+ const toSeed = (value) => {
2506
+ const parsed = value ? (0, _visx_vendor_d3_color.color)(value) : null;
2507
+ return parsed && parsed.opacity === 1 ? parsed.formatHex() : DEFAULT_SURFACE;
2508
+ };
2509
+ /**
2510
+ * Themes the tooltip box for its dark surface, as `@wordpress/ui`'s Tooltip themes its popup. The box re-declares the catalog under this theme, so every role it reads is tuned for the surface, whatever theme the chart is in.
2511
+ *
2512
+ * @param props - Component props.
2513
+ * @param props.children - The tooltip box.
2514
+ * @return The themed box.
2515
+ */
2516
+ const TooltipTheme = ({ children }) => {
2517
+ const scopeElement = useChartScopeElement();
2518
+ const color = (0, react.useMemo)(() => ({ background: toSeed(resolveCssVariable(TOOLTIP_SURFACE, scopeElement)) }), [scopeElement]);
2519
+ if (!_wordpress_theme.ThemeProvider) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
2520
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_theme.ThemeProvider, {
2521
+ color,
2522
+ children
2523
+ });
2450
2524
  };
2451
2525
  //#endregion
2526
+ //#region src/components/tooltip/tooltip-box.module.scss
2527
+ var tooltip_box_module_default = { "surface": "a8ccharts-ddpU1W-surface" };
2528
+ //#endregion
2529
+ //#region src/components/tooltip/tooltip-box.tsx
2530
+ /**
2531
+ * The chart tooltip box: the dark surface every chart tooltip draws. It does not position itself; place it with `style` or a wrapper.
2532
+ *
2533
+ * @param props - Div attributes, plus `unstyled`.
2534
+ * @param props.unstyled - Drop the surface, scope class and theme.
2535
+ * @param ref - Forwarded to the box element.
2536
+ * @return The tooltip box.
2537
+ */
2538
+ const TooltipBox = (0, react.forwardRef)(({ unstyled = false, role = "tooltip", className, children, ...rest }, ref) => {
2539
+ const box = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2540
+ ref,
2541
+ role,
2542
+ className: (0, clsx.default)("visx-tooltip", !unstyled && ["a8c-charts-tooltip-scope", tooltip_box_module_default.surface], className),
2543
+ ...rest,
2544
+ children
2545
+ });
2546
+ return unstyled ? box : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTheme, { children: box });
2547
+ });
2548
+ TooltipBox.displayName = "TooltipBox";
2549
+ //#endregion
2452
2550
  //#region src/components/tooltip/base-tooltip.tsx
2453
- const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2454
- data?.label,
2455
- ": ",
2456
- data?.valueDisplay || (0, _automattic_number_formatters.formatNumber)(data?.value)
2457
- ] });
2458
- const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipContent, children, className, style, renderContainer = true }) => {
2551
+ /**
2552
+ * A tooltip box with children or `label: value` content. Prefer `TooltipBox`.
2553
+ *
2554
+ * @param props - Tooltip props.
2555
+ * @param props.data - Data for the default content.
2556
+ * @param props.top - Deprecated vertical position.
2557
+ * @param props.left - Deprecated horizontal position.
2558
+ * @param props.component - Deprecated content component.
2559
+ * @param props.children - Tooltip content.
2560
+ * @param props.className - Class name passed to the content component.
2561
+ * @param props.style - Styles for the box.
2562
+ * @param props.renderContainer - Deprecated; render the content alone when false.
2563
+ * @return The tooltip.
2564
+ */
2565
+ const BaseTooltip = ({ data, top, left, component: Component = LabelValueContent, children, className, style, renderContainer = true }) => {
2459
2566
  const content = children || data && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
2460
2567
  data,
2461
2568
  className
2462
2569
  });
2463
- const standaloneScopeClass = useStandaloneScopeClass();
2464
2570
  if (!renderContainer) return content;
2465
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2466
- className: (0, clsx.default)(standaloneScopeClass, base_tooltip_module_default.tooltip),
2571
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipBox, {
2467
2572
  style: {
2468
- top,
2469
- left,
2573
+ ...top === void 0 && left === void 0 ? void 0 : {
2574
+ position: "absolute",
2575
+ top,
2576
+ left,
2577
+ transform: "translate(-50%, -100%)"
2578
+ },
2470
2579
  ...style
2471
2580
  },
2472
- role: "tooltip",
2473
2581
  children: content
2474
2582
  });
2475
2583
  };
@@ -2486,6 +2594,11 @@ const isClipping = (element) => {
2486
2594
  overflowY
2487
2595
  ].some((value) => value && value !== "visible");
2488
2596
  };
2597
+ const findLayoutParent = (node) => {
2598
+ let element = node.parentElement;
2599
+ while (element && getComputedStyle(element).display === "contents") element = element.parentElement;
2600
+ return element;
2601
+ };
2489
2602
  const findClippingAncestor = (wrapper) => {
2490
2603
  let element = wrapper.parentElement;
2491
2604
  while (element && element !== document.body) {
@@ -2546,7 +2659,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2546
2659
  };
2547
2660
  };
2548
2661
  /**
2549
- * visx's `Tooltip`, positioned like its `TooltipWithBounds` but kept inside the
2662
+ * A `TooltipBox`, positioned like visx's `TooltipWithBounds` but kept inside the
2550
2663
  * nearest clipping ancestor — or the viewport when there is none — rather than
2551
2664
  * inside its own parent. Rendered in-tree, a tooltip's parent is the chart
2552
2665
  * wrapper, which is often narrower than the box; measuring against the parent
@@ -2555,24 +2668,25 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2555
2668
  * Re-measures on every render, so a box whose content changes width between
2556
2669
  * two hovers is placed for its current size.
2557
2670
  *
2558
- * @param props - visx `Tooltip` props.
2671
+ * @param props - `TooltipBox` props plus placement.
2559
2672
  * @param props.left - Anchor x, in wrapper coordinates.
2560
2673
  * @param props.top - Anchor y, in wrapper coordinates.
2561
2674
  * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2562
2675
  * @param props.offsetTop - Gap between the anchor and the box, vertically.
2563
- * @param props.style - Box styles; visx's defaults unless `unstyled`.
2564
- * @param props.unstyled - Skip `style` and leave the box bare.
2676
+ * @param props.style - Inline overrides on the surface; ignored when `unstyled`.
2677
+ * @param props.unstyled - Drop the surface and `style`, leaving the box bare.
2678
+ * @param props.className - Extra classes beside the surface.
2565
2679
  * @param props.children - Box content.
2566
2680
  * @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2567
2681
  * @return The tooltip box.
2568
2682
  */
2569
- const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2683
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style, unstyled = false, className, children, placement = "auto", ...rest }) => {
2570
2684
  const nodeRef = (0, react.useRef)(null);
2571
2685
  const clipRef = (0, react.useRef)(null);
2572
2686
  const [position, setPosition] = (0, react.useState)(null);
2573
2687
  (0, react.useLayoutEffect)(() => {
2574
2688
  const node = nodeRef.current;
2575
- const wrapper = node?.parentElement;
2689
+ const wrapper = node && findLayoutParent(node);
2576
2690
  if (!node || !wrapper) return;
2577
2691
  if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2578
2692
  wrapper,
@@ -2620,8 +2734,11 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2620
2734
  ]);
2621
2735
  const x = position?.x ?? left + offsetLeft;
2622
2736
  const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2623
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_tooltip.Tooltip, {
2737
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TooltipBox, {
2624
2738
  ref: nodeRef,
2739
+ role: "presentation",
2740
+ unstyled,
2741
+ className,
2625
2742
  style: {
2626
2743
  position: "absolute",
2627
2744
  left: 0,
@@ -2637,16 +2754,16 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2637
2754
  left: left - x - POINTER_HEIGHT,
2638
2755
  top: -6,
2639
2756
  width: 12,
2640
- height: POINTER_HEIGHT,
2757
+ height: 7,
2641
2758
  background: "inherit",
2642
- clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
2759
+ clipPath: `polygon(50% 0, 100% ${POINTER_HEIGHT}px, 100% 100%, 0 100%, 0 ${POINTER_HEIGHT}px)`,
2643
2760
  pointerEvents: "none"
2644
2761
  }
2645
2762
  }), children]
2646
2763
  });
2647
2764
  };
2648
2765
  //#endregion
2649
- //#region src/components/tooltip/xy-chart-tooltip.tsx
2766
+ //#region src/components/tooltip/private/xy-chart-tooltip-overlay.tsx
2650
2767
  const CROSSHAIR_STROKE_WIDTH = 1.5;
2651
2768
  const CROSSHAIR_PAINT_PROPERTIES = /* @__PURE__ */ new Set([
2652
2769
  "stroke",
@@ -2748,15 +2865,36 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2748
2865
  const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2749
2866
  const marginTop = margin?.top ?? 0;
2750
2867
  const marginLeft = margin?.left ?? 0;
2751
- const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : _visx_tooltip.Tooltip;
2752
2868
  const boxStyle = {
2753
- ..._visx_tooltip.defaultStyles,
2754
2869
  zIndex,
2755
- backgroundColor: theme?.backgroundColor ?? "white",
2756
- boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2757
- ...theme?.htmlLabel,
2758
2870
  ...style
2759
2871
  };
2872
+ const { offsetLeft = 10, offsetTop = 10, unstyled, className, ...boxProps } = tooltipProps;
2873
+ const anchorTop = tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop;
2874
+ const box = detectBounds || tooltipPlacement !== "auto" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
2875
+ left: tooltipLeft,
2876
+ top: anchorTop,
2877
+ offsetLeft,
2878
+ offsetTop,
2879
+ unstyled,
2880
+ className,
2881
+ style: boxStyle,
2882
+ ...boxProps,
2883
+ ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2884
+ children: tooltipContent
2885
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipBox, {
2886
+ role: "presentation",
2887
+ unstyled,
2888
+ className,
2889
+ style: {
2890
+ position: "absolute",
2891
+ left: (tooltipLeft ?? 0) + offsetLeft,
2892
+ top: (anchorTop ?? 0) + offsetTop,
2893
+ ...!unstyled && boxStyle
2894
+ },
2895
+ ...boxProps,
2896
+ children: tooltipContent
2897
+ });
2760
2898
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2761
2899
  className: "visx-tooltip-overlay",
2762
2900
  pointerEvents: "none",
@@ -2783,15 +2921,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2783
2921
  }),
2784
2922
  glyphs
2785
2923
  ]
2786
- }), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2787
- left: tooltipLeft,
2788
- top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
2789
- style: boxStyle,
2790
- applyPositionStyle: true,
2791
- ...tooltipProps,
2792
- ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2793
- children: tooltipContent
2794
- }), container)] });
2924
+ }), container && (0, react_dom.createPortal)(box, container)] });
2795
2925
  };
2796
2926
  /**
2797
2927
  * In-tree replacement for `@visx/xychart`'s `Tooltip`.
@@ -2816,7 +2946,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2816
2946
  * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2817
2947
  * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
2818
2948
  */
2819
- const XyChartTooltip = (props) => {
2949
+ const XyChartTooltipOverlay = (props) => {
2820
2950
  const tooltipContext = (0, react.useContext)(_visx_xychart.TooltipContext);
2821
2951
  const [container, setContainer] = (0, react.useState)(null);
2822
2952
  const anchorRef = (0, react.useCallback)((node) => {
@@ -2829,8 +2959,8 @@ const XyChartTooltip = (props) => {
2829
2959
  })] });
2830
2960
  };
2831
2961
  //#endregion
2832
- //#region src/components/tooltip/accessible-tooltip.tsx
2833
- const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2962
+ //#region src/components/tooltip/xy-chart-tooltip.tsx
2963
+ const XYChartTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2834
2964
  const tooltipContext = (0, react.useContext)(_visx_xychart.TooltipContext);
2835
2965
  const scopeElement = useChartScopeElement();
2836
2966
  const crosshairStroke = (0, react.useMemo)(() => {
@@ -2936,7 +3066,7 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2936
3066
  keyboardFocusedClassName,
2937
3067
  standaloneScopeClass
2938
3068
  ]);
2939
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XyChartTooltip, {
3069
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XyChartTooltipOverlay, {
2940
3070
  ...props,
2941
3071
  verticalCrosshairStyle: {
2942
3072
  ...crosshairStroke,
@@ -2949,6 +3079,8 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2949
3079
  renderTooltip: focusableRenderTooltip
2950
3080
  });
2951
3081
  };
3082
+ /** @deprecated Use `XYChartTooltip`. */
3083
+ const AccessibleTooltip = XYChartTooltip;
2952
3084
  const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
2953
3085
  const getChartRoot = (0, react.useCallback)(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
2954
3086
  const focusWithoutScrollIfNeeded = (0, react.useCallback)((element) => {
@@ -3058,7 +3190,6 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
3058
3190
  };
3059
3191
  //#endregion
3060
3192
  //#region src/providers/chart-context/hooks/use-default-hidden-series.ts
3061
- const useIsomorphicLayoutEffect$1 = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
3062
3193
  /**
3063
3194
  * Seeds a chart's hidden series once per chart ID, the first time the provider
3064
3195
  * sees that ID.
@@ -3079,7 +3210,7 @@ const useDefaultHiddenSeries = (chartId, seriesLabels) => {
3079
3210
  labelsRef.current = seriesLabels;
3080
3211
  const hasSeriesLabels = seriesLabels !== void 0;
3081
3212
  const shouldUseDefaults = hasSeriesLabels && !hasSeededChart(chartId);
3082
- useIsomorphicLayoutEffect$1(() => {
3213
+ useIsomorphicLayoutEffect(() => {
3083
3214
  if (labelsRef.current === void 0) return;
3084
3215
  seedChartHiddenSeries(chartId, labelsRef.current);
3085
3216
  }, [
@@ -3704,7 +3835,6 @@ var with_responsive_module_default = {
3704
3835
  };
3705
3836
  //#endregion
3706
3837
  //#region src/charts/private/with-responsive/with-responsive.tsx
3707
- const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
3708
3838
  /**
3709
3839
  * A higher-order component that provides responsive dimensions
3710
3840
  * to the wrapped chart component using useParentSize from `@visx/responsive`.
@@ -3988,10 +4118,8 @@ var line_chart_module_default = {
3988
4118
  "line-chart__annotation-label-popover-content": "a8ccharts-inuQka-line-chart__annotation-label-popover-content",
3989
4119
  "line-chart__annotation-label-trigger-button": "a8ccharts-inuQka-line-chart__annotation-label-trigger-button",
3990
4120
  "line-chart__annotations-overlay": "a8ccharts-inuQka-line-chart__annotations-overlay",
3991
- "line-chart__tooltip": "a8ccharts-inuQka-line-chart__tooltip",
3992
4121
  "line-chart__tooltip-date": "a8ccharts-inuQka-line-chart__tooltip-date",
3993
4122
  "line-chart__tooltip-label": "a8ccharts-inuQka-line-chart__tooltip-label",
3994
- "line-chart__tooltip-row": "a8ccharts-inuQka-line-chart__tooltip-row",
3995
4123
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
3996
4124
  "rise": "a8ccharts-inuQka-rise"
3997
4125
  };
@@ -4518,11 +4646,10 @@ const TooltipDate = ({ date, displayResolution }) => {
4518
4646
  * one row per visible series (label + formatted value), sorted descending by
4519
4647
  * value. Reused by AreaChart, which has the same multi-series shape.
4520
4648
  *
4521
- * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4522
- * @param contentStyle - Explicit tooltip content color overrides.
4649
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4523
4650
  * @return Tooltip JSX, or `null` when no datum is hovered.
4524
4651
  */
4525
- const renderDefaultTooltip = (params, contentStyle) => {
4652
+ const renderDefaultTooltip = (params) => {
4526
4653
  const { tooltipData, bucketInfo } = params;
4527
4654
  const nearestDatum = tooltipData?.nearestDatum?.datum;
4528
4655
  if (!nearestDatum) return null;
@@ -4535,29 +4662,24 @@ const renderDefaultTooltip = (params, contentStyle) => {
4535
4662
  if (b.value === null) return -1;
4536
4663
  return b.value - a.value;
4537
4664
  });
4538
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4539
- className: line_chart_module_default["line-chart__tooltip"],
4540
- style: contentStyle,
4541
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4542
- className: line_chart_module_default["line-chart__tooltip-date"],
4543
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
4544
- date: nearestDatum.date,
4545
- displayResolution: bucketInfo?.displayResolution ?? "day"
4546
- })
4547
- }), tooltipPoints.map((point) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
4548
- direction: "row",
4549
- align: "center",
4550
- justify: "space-between",
4551
- className: line_chart_module_default["line-chart__tooltip-row"],
4552
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4553
- className: line_chart_module_default["line-chart__tooltip-label"],
4554
- children: [point.key, ":"]
4555
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4556
- className: line_chart_module_default["line-chart__tooltip-value"],
4557
- children: formatReading(point.value)
4558
- })]
4559
- }, point.key))]
4560
- });
4665
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4666
+ className: line_chart_module_default["line-chart__tooltip-date"],
4667
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
4668
+ date: nearestDatum.date,
4669
+ displayResolution: bucketInfo?.displayResolution ?? "day"
4670
+ })
4671
+ }), tooltipPoints.map((point) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
4672
+ direction: "row",
4673
+ align: "center",
4674
+ justify: "space-between",
4675
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4676
+ className: line_chart_module_default["line-chart__tooltip-label"],
4677
+ children: [point.key, ":"]
4678
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4679
+ className: line_chart_module_default["line-chart__tooltip-value"],
4680
+ children: formatReading(point.value)
4681
+ })]
4682
+ }, point.key))] });
4561
4683
  };
4562
4684
  const validateData$4 = (data) => {
4563
4685
  if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
@@ -4817,29 +4939,10 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4817
4939
  xAccessor: (d) => d?.date,
4818
4940
  yAccessor: (d) => d?.value
4819
4941
  };
4820
- const resolvedTooltipStyle = (0, react.useMemo)(() => {
4821
- if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
4822
- if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
4823
- return {
4824
- backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
4825
- ...tooltipStyle
4826
- };
4827
- }, [renderTooltip, tooltipStyle]);
4828
- const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
4829
- ...params,
4830
- bucketInfo
4831
- }, {
4832
- color: tooltipStyle?.color,
4833
- background: tooltipStyle?.background,
4834
- backgroundColor: tooltipStyle?.backgroundColor
4835
- }) : renderTooltip({
4942
+ const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip({
4836
4943
  ...params,
4837
4944
  bucketInfo
4838
- }), [
4839
- renderTooltip,
4840
- bucketInfo,
4841
- tooltipStyle
4842
- ]);
4945
+ }), [renderTooltip, bucketInfo]);
4843
4946
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4844
4947
  className: (0, clsx.default)("line-chart", line_chart_module_default["line-chart"]),
4845
4948
  children: error
@@ -4983,11 +5086,11 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4983
5086
  ] }, seriesData?.label || index);
4984
5087
  })
4985
5088
  }),
4986
- withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
5089
+ withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XYChartTooltip, {
4987
5090
  detectBounds: true,
4988
5091
  snapTooltipToDatumX: true,
4989
5092
  tooltipPlacement,
4990
- style: resolvedTooltipStyle,
5093
+ style: tooltipStyle,
4991
5094
  snapTooltipToDatumY: true,
4992
5095
  showSeriesGlyphs: true,
4993
5096
  renderTooltip: tooltipRenderer,
@@ -5459,7 +5562,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
5459
5562
  children: seriesWithVisibility.map(renderSeries)
5460
5563
  }), !allSeriesHidden && !stacked && seriesWithVisibility.map(renderSeries)]
5461
5564
  }),
5462
- withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
5565
+ withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(XYChartTooltip, {
5463
5566
  detectBounds: true,
5464
5567
  snapTooltipToDatumX: true,
5465
5568
  snapTooltipToDatumY: !stacked,
@@ -6070,6 +6173,7 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
6070
6173
  widthFactor
6071
6174
  });
6072
6175
  rects.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
6176
+ className: "bar-chart__comparison-bar",
6073
6177
  x: rect.x,
6074
6178
  y: rect.y,
6075
6179
  width: rect.width,
@@ -6485,7 +6589,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6485
6589
  const patternId = getPatternId(chartId, index);
6486
6590
  return `
6487
6591
  .visx-bar[fill="url(#${patternId})"],
6488
- .bar-chart__comparison-bars rect[fill="url(#${patternId})"] {
6592
+ .bar-chart__comparison-bar[fill="url(#${patternId})"] {
6489
6593
  stroke: ${color};
6490
6594
  stroke-width: 1;
6491
6595
  }
@@ -6667,15 +6771,15 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6667
6771
  }),
6668
6772
  !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WholeNumberTicks, {
6669
6773
  ...wholeNumberTicksProps,
6670
- children: (valueTicks) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
6774
+ children: (valueTicks) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [chartOptions.axis.x.display !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
6671
6775
  ...chartOptions.axis.x,
6672
6776
  ...horizontal && valueTicks ? { tickValues: valueTicks } : {}
6673
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
6777
+ }), chartOptions.axis.y.display !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Axis, {
6674
6778
  ...chartOptions.axis.y,
6675
6779
  ...!horizontal && valueTicks ? { tickValues: valueTicks } : {}
6676
6780
  })] })
6677
6781
  }),
6678
- withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
6782
+ withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XYChartTooltip, {
6679
6783
  tooltipPlacement,
6680
6784
  tooltipAnchorTop,
6681
6785
  style: tooltipStyle,
@@ -6895,8 +6999,7 @@ var conversion_funnel_chart_module_default = {
6895
6999
  "step-rate": "a8ccharts-mGEVca-step-rate",
6896
7000
  "stretch": "a8ccharts-mGEVca-stretch",
6897
7001
  "tooltip-content": "a8ccharts-mGEVca-tooltip-content",
6898
- "tooltip-title": "a8ccharts-mGEVca-tooltip-title",
6899
- "tooltip-wrapper": "a8ccharts-mGEVca-tooltip-wrapper"
7002
+ "tooltip-title": "a8ccharts-mGEVca-tooltip-title"
6900
7003
  };
6901
7004
  //#endregion
6902
7005
  //#region src/charts/conversion-funnel-chart/private/use-funnel-selection.ts
@@ -6957,9 +7060,10 @@ const useFunnelSelection = (hideTooltip) => {
6957
7060
  * @param props.renderStepRate - Custom render function for step rates
6958
7061
  * @param props.renderMainMetric - Custom render function for the entire main metric section
6959
7062
  * @param props.renderTooltip - Custom render function for tooltip content
7063
+ * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
6960
7064
  * @return JSX element representing the conversion funnel chart
6961
7065
  */
6962
- const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
7066
+ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
6963
7067
  const chartId = useChartId(providedChartId);
6964
7068
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
6965
7069
  const chartRef = (0, react.useRef)(null);
@@ -7194,15 +7298,18 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7194
7298
  step: tooltipData,
7195
7299
  index: steps.findIndex((s) => s.id === tooltipData.id),
7196
7300
  top: tooltipTop,
7197
- left: tooltipLeft,
7198
- className: conversion_funnel_chart_module_default["tooltip-wrapper"]
7301
+ left: tooltipLeft
7199
7302
  }) : renderDefaultTooltip(tooltipData);
7200
7303
  if (!tooltipContent) return null;
7201
7304
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
7202
7305
  top: tooltipTop,
7203
7306
  left: tooltipLeft,
7204
- className: (0, clsx.default)(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
7205
- children: tooltipContent
7307
+ className: standaloneScopeClass,
7308
+ style: tooltipStyle,
7309
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7310
+ role: "tooltip",
7311
+ children: tooltipContent
7312
+ })
7206
7313
  });
7207
7314
  })()]
7208
7315
  });
@@ -7487,6 +7594,73 @@ let GoogleDataTableColumnRoleType = /* @__PURE__ */ function(GoogleDataTableColu
7487
7594
  GoogleDataTableColumnRoleType["domain"] = "domain";
7488
7595
  return GoogleDataTableColumnRoleType;
7489
7596
  }({});
7597
+ const resolveRole = (pointer, resolve) => {
7598
+ const raw = resolve(pointer);
7599
+ const parsed = raw ? (0, _visx_vendor_d3_color.color)(raw) : null;
7600
+ if (!parsed) return {
7601
+ kind: "unreadable",
7602
+ raw
7603
+ };
7604
+ const { r, g, b } = parsed.opacity > 0 ? parsed.rgb() : {
7605
+ r: 0,
7606
+ g: 0,
7607
+ b: 0
7608
+ };
7609
+ return {
7610
+ kind: "color",
7611
+ rgb: [
7612
+ r,
7613
+ g,
7614
+ b
7615
+ ],
7616
+ alpha: parsed.opacity
7617
+ };
7618
+ };
7619
+ /**
7620
+ * Reads both label roles at the element the label text inherits from, so JS and CSS agree on what paints.
7621
+ *
7622
+ * @param resolve - A CSS variable resolver bound to that element.
7623
+ * @return The resolved label roles.
7624
+ */
7625
+ const resolveLabelRoles = (resolve) => ({
7626
+ label: resolveRole(CATALOG_POINTERS.label, resolve),
7627
+ labelInverse: resolveRole(CATALOG_POINTERS.labelInverse, resolve)
7628
+ });
7629
+ const roleContrast = (role, fill, fillLuminance) => {
7630
+ if (role.kind === "unreadable") return 0;
7631
+ return luminanceContrastRatio(fillLuminance, rgbLuminance(blendRgb(role.rgb, fill, role.alpha)));
7632
+ };
7633
+ /**
7634
+ * Picks the label color for text drawn on a fill: the role that contrasts more, or black/white when neither reaches AA.
7635
+ *
7636
+ * Both roles set to one color is a host's choice to keep that color, so it never falls back.
7637
+ *
7638
+ * @param fill - The fill the text sits on, as unrounded sRGB channels.
7639
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7640
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7641
+ * @return The color the label should paint with.
7642
+ */
7643
+ const pickLabelTextColorForFill = (fill, roles, defaultRole) => {
7644
+ if (!roles || (0, import_fast_deep_equal.default)(roles.label, roles.labelInverse)) return defaultRole;
7645
+ const fillLuminance = rgbLuminance(fill);
7646
+ const labelContrast = roleContrast(roles.label, fill, fillLuminance);
7647
+ const inverseContrast = roleContrast(roles.labelInverse, fill, fillLuminance);
7648
+ if (Math.max(labelContrast, inverseContrast) >= 4.55) return labelContrast > inverseContrast ? "label" : "label-inverse";
7649
+ return luminanceContrastRatio(fillLuminance, 0) > luminanceContrastRatio(fillLuminance, 1) ? "black" : "white";
7650
+ };
7651
+ /**
7652
+ * `pickLabelTextColorForFill` for a fill given as a color.
7653
+ *
7654
+ * @param fill - The fill, in any color syntax `normalizeColorToHex` reads.
7655
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7656
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7657
+ * @return The color the label should paint with.
7658
+ */
7659
+ const pickLabelTextColor = (fill, roles, defaultRole) => {
7660
+ const fillHex = normalizeColorToHex(fill);
7661
+ if (!isValidHexColor(fillHex)) return defaultRole;
7662
+ return pickLabelTextColorForFill(hexToRgb(fillHex), roles, defaultRole);
7663
+ };
7490
7664
  //#endregion
7491
7665
  //#region src/charts/heatmap-chart/heatmap-chart.module.scss
7492
7666
  var heatmap_chart_module_default = {
@@ -7496,9 +7670,11 @@ var heatmap_chart_module_default = {
7496
7670
  "heatmap-chart__cell--hidden": "a8ccharts-O3YMOW-heatmap-chart__cell--hidden",
7497
7671
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
7498
7672
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
7499
- "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
7500
7673
  "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
7501
7674
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
7675
+ "heatmap-chart__cell-value--black": "a8ccharts-O3YMOW-heatmap-chart__cell-value--black",
7676
+ "heatmap-chart__cell-value--inverse": "a8ccharts-O3YMOW-heatmap-chart__cell-value--inverse",
7677
+ "heatmap-chart__cell-value--white": "a8ccharts-O3YMOW-heatmap-chart__cell-value--white",
7502
7678
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
7503
7679
  "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
7504
7680
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
@@ -7520,30 +7696,44 @@ var heatmap_chart_module_default = {
7520
7696
  //#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
7521
7697
  const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
7522
7698
  /**
7523
- * Get the min and max values from heatmap data, ignoring null/NaN. Summary
7524
- * columns stay out: a roll-up on the scale would flatten every real cell.
7699
+ * Get the min and max values from heatmap data, ignoring null/NaN and the zeros
7700
+ * `isEmptyValue` paints as empty. Summary columns stay out: a roll-up on the
7701
+ * scale would flatten every real cell.
7525
7702
  * @param data - The heatmap columns
7526
7703
  * @return Tuple of [min, max] values
7527
7704
  */
7528
7705
  const getValueExtent = (data) => {
7529
7706
  let min = Infinity;
7530
7707
  let max = -Infinity;
7708
+ let hasZero = false;
7531
7709
  for (const column of data) {
7532
7710
  if (column.summary) continue;
7533
7711
  for (const cell of column.data) {
7534
7712
  if (!isPresent(cell.value)) continue;
7713
+ if (cell.value === 0) {
7714
+ hasZero = true;
7715
+ continue;
7716
+ }
7535
7717
  if (cell.value < min) min = cell.value;
7536
7718
  if (cell.value > max) max = cell.value;
7537
7719
  }
7538
7720
  }
7721
+ if (hasZero && min < 0) max = Math.max(max, 0);
7539
7722
  if (min === Infinity) return [0, 0];
7540
7723
  return [min, max];
7541
7724
  };
7542
7725
  /**
7726
+ * Whether a value paints as an empty cell: a zero in data with no negatives
7727
+ * counts nothing, so it must not look like the lowest step of activity.
7728
+ *
7729
+ * @param value - The cell value
7730
+ * @param extent - The extent from `getValueExtent`
7731
+ * @return True when the cell takes the empty-cell color
7732
+ */
7733
+ const isEmptyValue = (value, extent) => value === 0 && extent[0] >= 0;
7734
+ /**
7543
7735
  * Normalize a value to 0–1 within the extent. A flat extent (min === max)
7544
- * maps to 1 — every cell is equally the "highest" — except an all-zero
7545
- * extent, which maps to 0 so a no-activity grid renders at the scale's
7546
- * bottom instead of full intensity.
7736
+ * maps to 1, since every cell is equally the "highest".
7547
7737
  *
7548
7738
  * @param value - The value to normalize
7549
7739
  * @param extent - Tuple of [min, max] values for the normalization range
@@ -7551,9 +7741,40 @@ const getValueExtent = (data) => {
7551
7741
  */
7552
7742
  const getNormalizedValue = (value, extent) => {
7553
7743
  const [min, max] = extent;
7554
- if (min === max) return max === 0 ? 0 : 1;
7744
+ if (min === max) return 1;
7555
7745
  return Math.min(1, Math.max(0, (value - min) / (max - min)));
7556
7746
  };
7747
+ const STEPS = 200;
7748
+ const firstMeeting = (from, to, meets) => {
7749
+ for (let step = 0; step <= STEPS; step++) {
7750
+ const color = mixHexColors(from, to, step / STEPS);
7751
+ if (meets(color)) return color;
7752
+ }
7753
+ return null;
7754
+ };
7755
+ /**
7756
+ * The two ends of the heatmap fill scale: 3:1 against the background and the empty cell at the
7757
+ * low end, toward 9:1 against the background at the high end.
7758
+ *
7759
+ * @param primary - The resolved primary color, as six-digit hex.
7760
+ * @param background - The resolved chart background, as six-digit hex.
7761
+ * @param emptyCell - The resolved empty-cell color, as six-digit hex, when it is opaque.
7762
+ * @return The scale ends, as six-digit hex.
7763
+ */
7764
+ const getHeatmapScale = (primary, background, emptyCell) => {
7765
+ const primaryStandsOut = contrastRatio(primary, background) >= 3;
7766
+ const extreme = (primaryStandsOut ? relativeLuminance(primary) > relativeLuminance(background) : contrastRatio("#ffffff", background) > contrastRatio("#000000", background)) ? "#ffffff" : "#000000";
7767
+ const lowAgainst = (references) => {
7768
+ const meets = (color) => references.every((reference) => contrastRatio(color, reference) >= 3);
7769
+ return (primaryStandsOut ? firstMeeting(background, primary, meets) : null) ?? firstMeeting(primary, extreme, meets);
7770
+ };
7771
+ const high = firstMeeting(primary, extreme, (color) => contrastRatio(color, background) >= 9) ?? extreme;
7772
+ const againstEmptyCell = emptyCell ? lowAgainst([background, emptyCell]) : null;
7773
+ return {
7774
+ low: (againstEmptyCell && contrastRatio(againstEmptyCell, background) <= contrastRatio(high, background) ? againstEmptyCell : lowAgainst([background])) ?? extreme,
7775
+ high
7776
+ };
7777
+ };
7557
7778
  //#endregion
7558
7779
  //#region src/charts/heatmap-chart/private/heatmap-context.ts
7559
7780
  /** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
@@ -7564,7 +7785,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7564
7785
  const context = (0, react.useContext)(HeatmapContext);
7565
7786
  const { legend } = useGlobalChartsTheme();
7566
7787
  if (!context) return null;
7567
- const { primaryColorHex } = context;
7788
+ const { fillVars } = context;
7568
7789
  const labelStyle = legend.labelStyles;
7569
7790
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
7570
7791
  direction: "row",
@@ -7587,7 +7808,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7587
7808
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7588
7809
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
7589
7810
  style: {
7590
- "--a8c-charts-color-heatmap-primary": primaryColorHex,
7811
+ ...fillVars,
7591
7812
  "--a8c-charts-heatmap-cell-intensity": intensity
7592
7813
  }
7593
7814
  }, index);
@@ -7791,26 +8012,25 @@ const stepCalendarCell = (grid, blocks, from, key) => {
7791
8012
  };
7792
8013
  //#endregion
7793
8014
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
7794
- const CELL_MIX_FLOOR = .15;
7795
- const NO_ROW_LABELS = [];
7796
- const TOOLTIP_BOX_STYLES = {
7797
- light: {
7798
- ..._visx_tooltip.defaultStyles,
7799
- zIndex: 3
7800
- },
7801
- dark: { zIndex: 3 }
8015
+ const CELL_VALUE_MODIFIER = {
8016
+ label: void 0,
8017
+ "label-inverse": heatmap_chart_module_default["heatmap-chart__cell-value--inverse"],
8018
+ black: heatmap_chart_module_default["heatmap-chart__cell-value--black"],
8019
+ white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
7802
8020
  };
8021
+ const NO_ROW_LABELS = [];
8022
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
7803
8023
  const cellName = (info) => info.cellLabel || [
7804
8024
  info.groupLabel,
7805
8025
  info.columnLabel,
7806
8026
  info.rowLabel
7807
8027
  ].filter(Boolean).join(" ");
7808
- 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 }) => {
8028
+ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipStyle, children }) => {
7809
8029
  const chartId = useChartId(providedChartId);
7810
8030
  const tooltipBoxStyle = tooltipStyle ? {
7811
- ...TOOLTIP_BOX_STYLES[tooltipVariant],
8031
+ ...TOOLTIP_BOX_STYLE,
7812
8032
  ...tooltipStyle
7813
- } : TOOLTIP_BOX_STYLES[tooltipVariant];
8033
+ } : TOOLTIP_BOX_STYLE;
7814
8034
  const { getElementStyles, theme } = useGlobalChartsContext();
7815
8035
  const scopeElement = useChartScopeElement();
7816
8036
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -7819,19 +8039,44 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7819
8039
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
7820
8040
  const standaloneScopeClass = useStandaloneScopeClass();
7821
8041
  const containerRef = (0, react.useRef)(null);
8042
+ const [labelRoles, setLabelRoles] = (0, react.useState)(null);
7822
8043
  const getTooltipOrigin = (0, react.useCallback)(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
7823
8044
  const { color: primaryColorHex } = getElementStyles({
7824
8045
  index: 0,
7825
8046
  overrideColor: primaryColor
7826
8047
  });
7827
- const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
8048
+ const resolveAtScope = createCssVariableResolver(scopeElement);
8049
+ const chartBackgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, resolveAtScope) ?? "#ffffff";
8050
+ const emptyCellHex = resolveOpaqueHex(CATALOG_POINTERS.track, resolveAtScope);
7828
8051
  const primaryHex = normalizeColorToHex(primaryColorHex);
7829
- const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
8052
+ const scale = (0, react.useMemo)(() => isValidHexColor(primaryHex) ? getHeatmapScale(primaryHex, chartBackgroundHex, emptyCellHex) : null, [
8053
+ primaryHex,
8054
+ chartBackgroundHex,
8055
+ emptyCellHex
8056
+ ]);
8057
+ const fillVars = (0, react.useMemo)(() => ({
8058
+ "--a8c-charts-color-heatmap-primary": primaryColorHex,
8059
+ ...scale && {
8060
+ "--a8c-charts-color-heatmap-low": scale.low,
8061
+ "--a8c-charts-color-heatmap-high": scale.high
8062
+ }
8063
+ }), [primaryColorHex, scale]);
8064
+ const cellTextColor = (intensity) => scale ? pickLabelTextColorForFill(blendRgb(hexToRgb(scale.high), hexToRgb(scale.low), intensity), labelRoles, "label") : "label";
8065
+ useIsomorphicLayoutEffect(() => {
8066
+ if (!containerRef.current) return;
8067
+ const next = resolveLabelRoles(createCssVariableResolver(containerRef.current));
8068
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
8069
+ }, [
8070
+ data,
8071
+ className,
8072
+ primaryColorHex,
8073
+ chartBackgroundHex
8074
+ ]);
7830
8075
  const extent = (0, react.useMemo)(() => getValueExtent(data), [data]);
7831
8076
  const heatmapContext = (0, react.useMemo)(() => ({
7832
8077
  extent,
7833
- primaryColorHex
7834
- }), [extent, primaryColorHex]);
8078
+ fillVars
8079
+ }), [extent, fillVars]);
7835
8080
  const columns = data.length;
7836
8081
  const rows = Math.max(0, ...data.map((column) => column.data.length));
7837
8082
  const groupLayout = (0, react.useMemo)(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
@@ -7982,9 +8227,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7982
8227
  const firstDataRow = hasColumnLabels ? 2 : 1;
7983
8228
  const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
7984
8229
  const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
8230
+ const columnTracks = data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ");
7985
8231
  const gridStyle = {
7986
- "--a8c-charts-color-heatmap-primary": primaryColorHex,
7987
- gridTemplateColumns: `auto ${data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ")}`,
8232
+ ...fillVars,
8233
+ gridTemplateColumns: `auto ${columnTracks}`,
7988
8234
  gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
7989
8235
  };
7990
8236
  if (compact) {
@@ -8084,7 +8330,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8084
8330
  }, `cell-${columnIndex}-${rowIndex}`);
8085
8331
  const value = cell?.value ?? null;
8086
8332
  const present = isPresent(value);
8087
- const filled = present && !column.summary;
8333
+ const filled = present && !column.summary && !isEmptyValue(value, extent);
8088
8334
  const normalized = filled ? getNormalizedValue(value, extent) : 0;
8089
8335
  const info = buildTooltipData(columnIndex, rowIndex);
8090
8336
  const accessibleLabel = `${cellName(info)}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
@@ -8098,7 +8344,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8098
8344
  "data-row": rowIndex,
8099
8345
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
8100
8346
  [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
8101
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
8102
8347
  [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
8103
8348
  ...summaryGaps(columnIndex),
8104
8349
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
@@ -8110,7 +8355,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8110
8355
  onMouseMove: handleCellMouseMove,
8111
8356
  onMouseLeave: handleCellMouseLeave,
8112
8357
  children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8113
- className: heatmap_chart_module_default["heatmap-chart__cell-value"],
8358
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell-value"], filled && CELL_VALUE_MODIFIER[cellTextColor(normalized)]),
8114
8359
  children: (0, _automattic_number_formatters.formatNumberCompact)(value)
8115
8360
  })
8116
8361
  }, `cell-${columnIndex}-${rowIndex}`);
@@ -8134,7 +8379,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8134
8379
  }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
8135
8380
  top: tooltipTop,
8136
8381
  left: tooltipLeft,
8137
- className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
8138
8382
  style: tooltipBoxStyle,
8139
8383
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8140
8384
  className: standaloneScopeClass,
@@ -11726,7 +11970,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
11726
11970
  var pie_chart_module_default = {
11727
11971
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
11728
11972
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
11973
+ "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",
11729
11974
  "pie-chart__label-text--on-light": "a8ccharts-gnszbG-pie-chart__label-text--on-light",
11975
+ "pie-chart__label-text--white": "a8ccharts-gnszbG-pie-chart__label-text--white",
11730
11976
  "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
11731
11977
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
11732
11978
  };
@@ -11734,18 +11980,13 @@ var pie_chart_module_default = {
11734
11980
  //#region src/charts/pie-chart/pie-chart.tsx
11735
11981
  /**
11736
11982
  * Default tooltip renderer for pie charts.
11737
- * Renders a BaseTooltip with the hovered segment's data.
11983
+ * Renders the default `label: value` tooltip content for the hovered segment.
11738
11984
  *
11739
11985
  * @param {PieChartRenderTooltipParams} params - The tooltip parameters containing the hovered data point
11740
11986
  * @return {ReactNode} The rendered tooltip content
11741
11987
  */
11742
11988
  const renderDefaultPieTooltip = ({ tooltipData }) => {
11743
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BaseTooltip, {
11744
- data: tooltipData,
11745
- top: 0,
11746
- left: 0,
11747
- renderContainer: false
11748
- });
11989
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelValueContent, { data: tooltipData });
11749
11990
  };
11750
11991
  /**
11751
11992
  * Validates the pie chart data
@@ -11770,24 +12011,11 @@ const validateData$1 = (data) => {
11770
12011
  message: ""
11771
12012
  };
11772
12013
  };
11773
- /**
11774
- * Whether a pie slice's label should use the dark `label` role instead of the default `label-inverse` role.
11775
- *
11776
- * A label plate always wins: the text sits on the plate, not the slice fill, so it never flips.
11777
- * Otherwise the role that contrasts more with the resolved slice fill wins. `null` pointers (not
11778
- * yet resolved, or labels off) keep the default inverse role.
11779
- *
11780
- * @param fill - The slice's resolved fill color.
11781
- * @param pointers - The label pointers, resolved at the chart's own root element.
11782
- * @return Whether the label needs dark text.
11783
- */
11784
- const labelNeedsDarkText = (fill, pointers) => {
11785
- if (!pointers || pointers.hasPlate) return false;
11786
- const fillHex = normalizeColorToHex(fill);
11787
- if (!isValidHexColor(fillHex) || !isValidHexColor(pointers.labelHex)) return false;
11788
- if (pointers.isInverseSeeThrough) return true;
11789
- if (!isValidHexColor(pointers.labelInverseHex)) return false;
11790
- return contrastRatio(fillHex, pointers.labelHex) > contrastRatio(fillHex, pointers.labelInverseHex);
12014
+ const LABEL_TEXT_MODIFIER = {
12015
+ label: pie_chart_module_default["pie-chart__label-text--on-light"],
12016
+ "label-inverse": void 0,
12017
+ black: pie_chart_module_default["pie-chart__label-text--black"],
12018
+ white: pie_chart_module_default["pie-chart__label-text--white"]
11791
12019
  };
11792
12020
  /**
11793
12021
  * Renders a pie or donut chart using the provided data.
@@ -11804,7 +12032,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11804
12032
  const standaloneScopeClass = useStandaloneScopeClass();
11805
12033
  const containerRef = (0, react.useRef)(null);
11806
12034
  const rootRef = (0, react.useRef)(null);
11807
- const [labelPointers, setLabelPointers] = (0, react.useState)(null);
12035
+ const [labelRoles, setLabelRoles] = (0, react.useState)(null);
11808
12036
  const onMouseLeave = (0, react.useCallback)(() => {
11809
12037
  if (!withTooltips) return;
11810
12038
  hideTooltip();
@@ -11815,18 +12043,9 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11815
12043
  if (!showLabels || !rootRef.current) return;
11816
12044
  const resolve = createCssVariableResolver(rootRef.current);
11817
12045
  const rawLabelBackground = resolve(CATALOG_POINTERS.labelBackground);
11818
- const rawLabel = resolve(CATALOG_POINTERS.label);
11819
- const isLabelOpaque = rawLabel ? (0, _visx_vendor_d3_color.color)(rawLabel)?.opacity === 1 : false;
11820
- const rawLabelInverse = resolve(CATALOG_POINTERS.labelInverse);
11821
- const labelInverseColor = rawLabelInverse ? (0, _visx_vendor_d3_color.color)(rawLabelInverse) : null;
11822
12046
  const plateColor = rawLabelBackground ? (0, _visx_vendor_d3_color.color)(rawLabelBackground) : null;
11823
- const next = {
11824
- hasPlate: rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false,
11825
- labelHex: isLabelOpaque ? normalizeColorToHex(CATALOG_POINTERS.label, null, resolve) : "",
11826
- labelInverseHex: normalizeColorToHex(CATALOG_POINTERS.labelInverse, null, resolve),
11827
- isInverseSeeThrough: labelInverseColor ? labelInverseColor.opacity < 1 : false
11828
- };
11829
- setLabelPointers((previous) => previous && previous.hasPlate === next.hasPlate && previous.labelHex === next.labelHex && previous.labelInverseHex === next.labelInverseHex && previous.isInverseSeeThrough === next.isInverseSeeThrough ? previous : next);
12047
+ const next = (rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false) ? null : resolveLabelRoles(resolve);
12048
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
11830
12049
  }, [
11831
12050
  showLabels,
11832
12051
  className,
@@ -11997,7 +12216,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11997
12216
  ry: 4,
11998
12217
  pointerEvents: "none"
11999
12218
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
12000
- className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], { [pie_chart_module_default["pie-chart__label-text--on-light"]]: labelNeedsDarkText(fill, labelPointers) }),
12219
+ className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
12001
12220
  x: centroidX,
12002
12221
  y: centroidY,
12003
12222
  dy: ".33em",
@@ -12053,18 +12272,13 @@ var pie_semi_circle_chart_module_default = {
12053
12272
  //#region src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx
12054
12273
  /**
12055
12274
  * Default tooltip renderer for semi-circle pie charts.
12056
- * Renders a BaseTooltip with the hovered segment's data.
12275
+ * Renders the default `label: value` tooltip content for the hovered segment.
12057
12276
  *
12058
12277
  * @param {PieSemiCircleChartRenderTooltipParams} params - The tooltip parameters containing the hovered data point
12059
12278
  * @return {ReactNode} The rendered tooltip content
12060
12279
  */
12061
12280
  const renderDefaultPieSemiCircleTooltip = ({ tooltipData }) => {
12062
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BaseTooltip, {
12063
- data: tooltipData,
12064
- top: 0,
12065
- left: 0,
12066
- renderContainer: false
12067
- });
12281
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LabelValueContent, { data: tooltipData });
12068
12282
  };
12069
12283
  const PAD_ANGLE = .03;
12070
12284
  const DEFAULT_WIDTH$1 = 400;
@@ -12532,7 +12746,9 @@ exports.PieSemiCircleChartUnresponsive = PieSemiCircleChart;
12532
12746
  exports.Sparkline = Sparkline;
12533
12747
  exports.SparklineUnresponsive = SparklineUnresponsive;
12534
12748
  exports.ThemeProvider = GlobalChartsProvider;
12749
+ exports.TooltipBox = TooltipBox;
12535
12750
  exports.TrendIndicator = TrendIndicator;
12751
+ exports.XYChartTooltip = XYChartTooltip;
12536
12752
  exports.buildCalendarHeatmapData = buildCalendarHeatmapData;
12537
12753
  exports.buildMonthCalendarHeatmapData = buildMonthCalendarHeatmapData;
12538
12754
  exports.defaultTheme = defaultTheme;