@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.js CHANGED
@@ -5,17 +5,18 @@ import { __, _x, sprintf } from "@wordpress/i18n";
5
5
  import clsx from "clsx";
6
6
  import * as React from "react";
7
7
  import { Children, Fragment, createContext, createContext as createContext$1, createElement, forwardRef, forwardRef as forwardRef$1, isValidElement, memo, useCallback, useCallback as useCallback$1, useContext, useContext as useContext$1, useEffect, useEffect as useEffect$1, useId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useRef, useRef as useRef$1, useState, useState as useState$1 } from "react";
8
- import { color, hsl } from "@visx/vendor/d3-color";
9
8
  import { differenceInHours, differenceInYears, isValid, parse, parseISO } from "date-fns";
10
9
  import { tzOffset } from "@date-fns/tz";
11
10
  import { Text, getStringWidth } from "@visx/text";
12
11
  import deepmerge from "deepmerge";
12
+ import { color, hsl } from "@visx/vendor/d3-color";
13
13
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
- import { Tooltip, defaultStyles, useTooltip } from "@visx/tooltip";
14
+ import { useTooltip } from "@visx/tooltip";
15
15
  import { createScale, getTicks, scaleBand, scaleCanBeZeroed, scaleOrdinal, scaleTime } from "@visx/scale";
16
16
  import { Group } from "@visx/group";
17
17
  import { LegendItem, LegendLabel, LegendOrdinal, LegendShape } from "@visx/legend";
18
18
  import { IconButton, Stack, Text as Text$1 } from "@wordpress/ui";
19
+ import { ThemeProvider } from "@wordpress/theme";
19
20
  import { createPortal } from "react-dom";
20
21
  import { LinearGradient } from "@visx/gradient";
21
22
  import { curveCatmullRom, curveLinear, curveMonotoneX } from "@visx/curve";
@@ -560,7 +561,8 @@ const relativeLuminance = (hex) => {
560
561
  * Whether light text reads better than dark text on the given background, using the W3C
561
562
  * luminance threshold (0.179) that maximizes contrast against black vs white.
562
563
  *
563
- * @param backgroundHex - Hex background color
564
+ * @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.
565
+ * @param backgroundHex - Hex background color
564
566
  * @return true if light text should be used; false (dark text) for malformed colors
565
567
  */
566
568
  const prefersLightText = (backgroundHex) => {
@@ -832,6 +834,7 @@ const CATALOG_POINTERS = {
832
834
  annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
833
835
  surface: "var(--a8c-charts-color-surface, #fff)",
834
836
  surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
837
+ track: "var(--a8c-charts-color-track, #f0f0f0)",
835
838
  trendUp: "var(--a8c-charts-color-trend-up, #008030)",
836
839
  trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
837
840
  series: SERIES_PALETTE_POINTERS
@@ -879,14 +882,27 @@ const PROTANOPIA = [
879
882
  const DEGREES = Math.PI / 180;
880
883
  const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
881
884
  const fromLinear = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - .055;
882
- const hexToLinear = (hex) => {
885
+ /**
886
+ * The sRGB channels of a hex color.
887
+ *
888
+ * @param hex - A six-digit hex color.
889
+ * @return Channels from 0 to 255.
890
+ * @throws {Error} if the hex string is malformed
891
+ */
892
+ const hexToRgb = (hex) => {
883
893
  validateHexColor(hex);
884
- return [
894
+ const [r, g, b] = [
885
895
  1,
886
896
  3,
887
897
  5
888
- ].map((start) => toLinear(parseInt(hex.slice(start, start + 2), 16) / 255));
898
+ ].map((start) => parseInt(hex.slice(start, start + 2), 16));
899
+ return [
900
+ r,
901
+ g,
902
+ b
903
+ ];
889
904
  };
905
+ const hexToLinear = (hex) => hexToRgb(hex).map((channel) => toLinear(channel / 255));
890
906
  const linearToHex = (linear) => "#" + linear.map((channel) => Math.round(Math.min(1, Math.max(0, fromLinear(channel))) * 255).toString(16).padStart(2, "0")).join("");
891
907
  const simulate = (matrix, [r, g, b]) => [
892
908
  0,
@@ -1012,6 +1028,35 @@ const hexToOklch = (hex) => {
1012
1028
  };
1013
1029
  };
1014
1030
  /**
1031
+ * `foreground` over `background` at `weight`, left unrounded as CSS `color-mix` and alpha compositing leave it.
1032
+ *
1033
+ * A mix rounded to hex can land on the other side of a contrast threshold from the fill the browser paints.
1034
+ *
1035
+ * @param foreground - The color weighted by `weight`.
1036
+ * @param background - The color it is mixed over.
1037
+ * @param weight - Share of `foreground`, from 0 to 1.
1038
+ * @return The mixed channels.
1039
+ */
1040
+ const blendRgb = (foreground, background, weight) => {
1041
+ const share = Math.min(1, Math.max(0, weight));
1042
+ const [r, g, b] = foreground.map((channel, i) => background[i] + (channel - background[i]) * share);
1043
+ return [
1044
+ r,
1045
+ g,
1046
+ b
1047
+ ];
1048
+ };
1049
+ /**
1050
+ * WCAG relative luminance of unrounded sRGB channels.
1051
+ *
1052
+ * @param rgb - Channels from 0 to 255.
1053
+ * @return Relative luminance from 0 to 1.
1054
+ */
1055
+ const rgbLuminance = (rgb) => {
1056
+ const [r, g, b] = rgb.map((channel) => toLinear(channel / 255));
1057
+ return .2126 * r + .7152 * g + .0722 * b;
1058
+ };
1059
+ /**
1015
1060
  * WCAG contrast ratio between two relative luminances.
1016
1061
  *
1017
1062
  * @param first - First relative luminance.
@@ -1218,6 +1263,14 @@ const buildPaletteGenerator = (normalizedSeeds, background, labelColors) => {
1218
1263
  };
1219
1264
  };
1220
1265
  //#endregion
1266
+ //#region src/providers/chart-context/private/resolve-opaque-hex.ts
1267
+ const resolveOpaqueHex = (pointer, resolve) => {
1268
+ const raw = resolve(pointer);
1269
+ if (!raw || color(raw)?.opacity !== 1) return null;
1270
+ const hex = normalizeColorToHex(raw);
1271
+ return isValidHexColor(hex) ? hex : null;
1272
+ };
1273
+ //#endregion
1221
1274
  //#region src/providers/chart-context/themes.ts
1222
1275
  /**
1223
1276
  * Default theme configuration: the shape and spacing a consumer can override.
@@ -1266,12 +1319,6 @@ const defaultTheme = {
1266
1319
  };
1267
1320
  //#endregion
1268
1321
  //#region src/providers/chart-context/global-charts-provider.tsx
1269
- const resolveOpaqueHex = (pointer, element) => {
1270
- const raw = resolveCssVariable(pointer, element);
1271
- if (!raw || color(raw)?.opacity !== 1) return null;
1272
- const hex = normalizeColorToHex(pointer, element, resolveCssVariable);
1273
- return isValidHexColor(hex) ? hex : null;
1274
- };
1275
1322
  const PLACEHOLDER_LABEL_COLORS = [LABEL_FALLBACK, LABEL_INVERSE_FALLBACK];
1276
1323
  const GlobalChartsContext = createContext(null);
1277
1324
  const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
@@ -1299,8 +1346,9 @@ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
1299
1346
  const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
1300
1347
  if (isValidHexColor(normalizedColor)) resolvedColors.push(normalizedColor);
1301
1348
  }
1302
- const backgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, wrapperRef.current) ?? "#ffffff";
1303
- const labelColors = [CATALOG_POINTERS.label, CATALOG_POINTERS.labelInverse].map((pointer) => resolveOpaqueHex(pointer, wrapperRef.current)).filter((hex) => hex !== null);
1349
+ const resolveAtWrapper = createCssVariableResolver(wrapperRef.current);
1350
+ const backgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, resolveAtWrapper) ?? "#ffffff";
1351
+ const labelColors = [CATALOG_POINTERS.label, CATALOG_POINTERS.labelInverse].map((pointer) => resolveOpaqueHex(pointer, resolveAtWrapper)).filter((hex) => hex !== null);
1304
1352
  setColorCache({
1305
1353
  colors: resolvedColors,
1306
1354
  background: backgroundHex,
@@ -1532,15 +1580,13 @@ const useXYChartTheme = (data) => {
1532
1580
  const resolve = createCssVariableResolver(scopeElement);
1533
1581
  const resolveColor = (value) => value ? resolve(value) ?? value : value;
1534
1582
  const paletteColors = [...JSON.parse(seriesColorKey), ...CATALOG_POINTERS.series].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1535
- const resolvedLabelColor = resolveColor(CATALOG_POINTERS.labelAxis);
1536
- const htmlLabelColor = resolvedLabelColor ? normalizeColorToHex(resolvedLabelColor) || resolvedLabelColor : resolvedLabelColor;
1537
1583
  return buildChartTheme({
1538
1584
  ...theme,
1539
1585
  gridColor: "",
1540
1586
  gridColorDark: "",
1541
1587
  colors: paletteColors,
1542
1588
  backgroundColor: resolveColor(CATALOG_POINTERS.background),
1543
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1589
+ htmlLabel: { color: CATALOG_POINTERS.labelAxis },
1544
1590
  gridStyles: {
1545
1591
  ...theme.gridStyles,
1546
1592
  stroke: CATALOG_POINTERS.grid
@@ -1638,10 +1684,10 @@ const DEFAULT_MARGIN_BOTTOM = 20;
1638
1684
  */
1639
1685
  const DEFAULT_MARGIN_LEFT = 20;
1640
1686
  /**
1641
- * Bottom margin to use when the X-axis is rendered at the top.
1687
+ * Bottom margin when no X-axis sits below the chart, because it is on top or hidden.
1642
1688
  * We only need a small buffer below the chart in that case.
1643
1689
  */
1644
- const DEFAULT_BOTTOM_FOR_TOP_AXIS = 10;
1690
+ const DEFAULT_BOTTOM_WITHOUT_X_AXIS = 10;
1645
1691
  /**
1646
1692
  * Fallback font size used when we cannot derive a font size
1647
1693
  * from the theme or axis styles for X-axis tick labels.
@@ -1721,18 +1767,18 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1721
1767
  if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1722
1768
  else defaultMargin.left = yMarginValue;
1723
1769
  }
1724
- const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1725
- const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1726
- const computedXMargin = fontSize + tickLength;
1727
- if (xOrientation === "top") {
1728
- defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1729
- defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
1730
- } else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
1731
1770
  if (options.axis?.x?.display !== false) {
1771
+ const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1772
+ const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1773
+ const computedXMargin = fontSize + tickLength;
1774
+ if (xOrientation === "top") {
1775
+ defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1776
+ defaultMargin.bottom = DEFAULT_BOTTOM_WITHOUT_X_AXIS;
1777
+ } else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
1732
1778
  const { first, last } = getEdgeTickWidths(options.axis?.x?.tickValues ?? [], options.axis?.x?.tickFormat, tickLabelStyle);
1733
1779
  defaultMargin.left = Math.max(defaultMargin.left, Math.ceil(first / 2));
1734
1780
  defaultMargin.right = Math.max(defaultMargin.right, Math.ceil(last / 2));
1735
- }
1781
+ } else defaultMargin.bottom = DEFAULT_BOTTOM_WITHOUT_X_AXIS;
1736
1782
  return defaultMargin;
1737
1783
  }, [
1738
1784
  options,
@@ -1975,6 +2021,9 @@ function usePrefersReducedMotion() {
1975
2021
  return prefersReducedMotion;
1976
2022
  }
1977
2023
  //#endregion
2024
+ //#region src/hooks/use-isomorphic-layout-effect.ts
2025
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
2026
+ //#endregion
1978
2027
  //#region src/providers/chart-context/hooks/use-chart-registration.ts
1979
2028
  const useChartRegistration = ({ chartId, legendItems, chartType, isDataValid, metadata }) => {
1980
2029
  const { registerChart, unregisterChart } = useGlobalChartsContext();
@@ -2441,33 +2490,92 @@ function useChartLegendItems(data, options = {}, legendShape) {
2441
2490
  ]);
2442
2491
  }
2443
2492
  //#endregion
2444
- //#region src/components/tooltip/base-tooltip.module.scss
2445
- var base_tooltip_module_default = {
2446
- "surface": "a8ccharts--zY0xG-surface",
2447
- "tooltip": "a8ccharts--zY0xG-tooltip"
2493
+ //#region src/components/tooltip/private/label-value-content.tsx
2494
+ const LabelValueContent = ({ data }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
2495
+ data.label,
2496
+ ": ",
2497
+ data.valueDisplay || formatNumber(data.value)
2498
+ ] });
2499
+ //#endregion
2500
+ //#region src/components/tooltip/private/tooltip-theme.tsx
2501
+ const DEFAULT_SURFACE = "#1e1e1e";
2502
+ const TOOLTIP_SURFACE = `var(--a8c-charts-color-tooltip-surface, ${DEFAULT_SURFACE})`;
2503
+ const toSeed = (value) => {
2504
+ const parsed = value ? color(value) : null;
2505
+ return parsed && parsed.opacity === 1 ? parsed.formatHex() : DEFAULT_SURFACE;
2506
+ };
2507
+ /**
2508
+ * 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.
2509
+ *
2510
+ * @param props - Component props.
2511
+ * @param props.children - The tooltip box.
2512
+ * @return The themed box.
2513
+ */
2514
+ const TooltipTheme = ({ children }) => {
2515
+ const scopeElement = useChartScopeElement();
2516
+ const color = useMemo(() => ({ background: toSeed(resolveCssVariable(TOOLTIP_SURFACE, scopeElement)) }), [scopeElement]);
2517
+ if (!ThemeProvider) return /* @__PURE__ */ jsx(Fragment$1, { children });
2518
+ return /* @__PURE__ */ jsx(ThemeProvider, {
2519
+ color,
2520
+ children
2521
+ });
2448
2522
  };
2449
2523
  //#endregion
2524
+ //#region src/components/tooltip/tooltip-box.module.scss
2525
+ var tooltip_box_module_default = { "surface": "a8ccharts-ddpU1W-surface" };
2526
+ //#endregion
2527
+ //#region src/components/tooltip/tooltip-box.tsx
2528
+ /**
2529
+ * The chart tooltip box: the dark surface every chart tooltip draws. It does not position itself; place it with `style` or a wrapper.
2530
+ *
2531
+ * @param props - Div attributes, plus `unstyled`.
2532
+ * @param props.unstyled - Drop the surface, scope class and theme.
2533
+ * @param ref - Forwarded to the box element.
2534
+ * @return The tooltip box.
2535
+ */
2536
+ const TooltipBox = forwardRef(({ unstyled = false, role = "tooltip", className, children, ...rest }, ref) => {
2537
+ const box = /* @__PURE__ */ jsx("div", {
2538
+ ref,
2539
+ role,
2540
+ className: clsx("visx-tooltip", !unstyled && ["a8c-charts-tooltip-scope", tooltip_box_module_default.surface], className),
2541
+ ...rest,
2542
+ children
2543
+ });
2544
+ return unstyled ? box : /* @__PURE__ */ jsx(TooltipTheme, { children: box });
2545
+ });
2546
+ TooltipBox.displayName = "TooltipBox";
2547
+ //#endregion
2450
2548
  //#region src/components/tooltip/base-tooltip.tsx
2451
- const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
2452
- data?.label,
2453
- ": ",
2454
- data?.valueDisplay || formatNumber(data?.value)
2455
- ] });
2456
- const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipContent, children, className, style, renderContainer = true }) => {
2549
+ /**
2550
+ * A tooltip box with children or `label: value` content. Prefer `TooltipBox`.
2551
+ *
2552
+ * @param props - Tooltip props.
2553
+ * @param props.data - Data for the default content.
2554
+ * @param props.top - Deprecated vertical position.
2555
+ * @param props.left - Deprecated horizontal position.
2556
+ * @param props.component - Deprecated content component.
2557
+ * @param props.children - Tooltip content.
2558
+ * @param props.className - Class name passed to the content component.
2559
+ * @param props.style - Styles for the box.
2560
+ * @param props.renderContainer - Deprecated; render the content alone when false.
2561
+ * @return The tooltip.
2562
+ */
2563
+ const BaseTooltip = ({ data, top, left, component: Component = LabelValueContent, children, className, style, renderContainer = true }) => {
2457
2564
  const content = children || data && /* @__PURE__ */ jsx(Component, {
2458
2565
  data,
2459
2566
  className
2460
2567
  });
2461
- const standaloneScopeClass = useStandaloneScopeClass();
2462
2568
  if (!renderContainer) return content;
2463
- return /* @__PURE__ */ jsx("div", {
2464
- className: clsx(standaloneScopeClass, base_tooltip_module_default.tooltip),
2569
+ return /* @__PURE__ */ jsx(TooltipBox, {
2465
2570
  style: {
2466
- top,
2467
- left,
2571
+ ...top === void 0 && left === void 0 ? void 0 : {
2572
+ position: "absolute",
2573
+ top,
2574
+ left,
2575
+ transform: "translate(-50%, -100%)"
2576
+ },
2468
2577
  ...style
2469
2578
  },
2470
- role: "tooltip",
2471
2579
  children: content
2472
2580
  });
2473
2581
  };
@@ -2484,6 +2592,11 @@ const isClipping = (element) => {
2484
2592
  overflowY
2485
2593
  ].some((value) => value && value !== "visible");
2486
2594
  };
2595
+ const findLayoutParent = (node) => {
2596
+ let element = node.parentElement;
2597
+ while (element && getComputedStyle(element).display === "contents") element = element.parentElement;
2598
+ return element;
2599
+ };
2487
2600
  const findClippingAncestor = (wrapper) => {
2488
2601
  let element = wrapper.parentElement;
2489
2602
  while (element && element !== document.body) {
@@ -2544,7 +2657,7 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2544
2657
  };
2545
2658
  };
2546
2659
  /**
2547
- * visx's `Tooltip`, positioned like its `TooltipWithBounds` but kept inside the
2660
+ * A `TooltipBox`, positioned like visx's `TooltipWithBounds` but kept inside the
2548
2661
  * nearest clipping ancestor — or the viewport when there is none — rather than
2549
2662
  * inside its own parent. Rendered in-tree, a tooltip's parent is the chart
2550
2663
  * wrapper, which is often narrower than the box; measuring against the parent
@@ -2553,24 +2666,25 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2553
2666
  * Re-measures on every render, so a box whose content changes width between
2554
2667
  * two hovers is placed for its current size.
2555
2668
  *
2556
- * @param props - visx `Tooltip` props.
2669
+ * @param props - `TooltipBox` props plus placement.
2557
2670
  * @param props.left - Anchor x, in wrapper coordinates.
2558
2671
  * @param props.top - Anchor y, in wrapper coordinates.
2559
2672
  * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2560
2673
  * @param props.offsetTop - Gap between the anchor and the box, vertically.
2561
- * @param props.style - Box styles; visx's defaults unless `unstyled`.
2562
- * @param props.unstyled - Skip `style` and leave the box bare.
2674
+ * @param props.style - Inline overrides on the surface; ignored when `unstyled`.
2675
+ * @param props.unstyled - Drop the surface and `style`, leaving the box bare.
2676
+ * @param props.className - Extra classes beside the surface.
2563
2677
  * @param props.children - Box content.
2564
2678
  * @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2565
2679
  * @return The tooltip box.
2566
2680
  */
2567
- const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2681
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style, unstyled = false, className, children, placement = "auto", ...rest }) => {
2568
2682
  const nodeRef = useRef(null);
2569
2683
  const clipRef = useRef(null);
2570
2684
  const [position, setPosition] = useState(null);
2571
2685
  useLayoutEffect(() => {
2572
2686
  const node = nodeRef.current;
2573
- const wrapper = node?.parentElement;
2687
+ const wrapper = node && findLayoutParent(node);
2574
2688
  if (!node || !wrapper) return;
2575
2689
  if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2576
2690
  wrapper,
@@ -2618,8 +2732,11 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2618
2732
  ]);
2619
2733
  const x = position?.x ?? left + offsetLeft;
2620
2734
  const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2621
- return /* @__PURE__ */ jsxs(Tooltip, {
2735
+ return /* @__PURE__ */ jsxs(TooltipBox, {
2622
2736
  ref: nodeRef,
2737
+ role: "presentation",
2738
+ unstyled,
2739
+ className,
2623
2740
  style: {
2624
2741
  position: "absolute",
2625
2742
  left: 0,
@@ -2635,16 +2752,16 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2635
2752
  left: left - x - POINTER_HEIGHT,
2636
2753
  top: -6,
2637
2754
  width: 12,
2638
- height: POINTER_HEIGHT,
2755
+ height: 7,
2639
2756
  background: "inherit",
2640
- clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
2757
+ clipPath: `polygon(50% 0, 100% ${POINTER_HEIGHT}px, 100% 100%, 0 100%, 0 ${POINTER_HEIGHT}px)`,
2641
2758
  pointerEvents: "none"
2642
2759
  }
2643
2760
  }), children]
2644
2761
  });
2645
2762
  };
2646
2763
  //#endregion
2647
- //#region src/components/tooltip/xy-chart-tooltip.tsx
2764
+ //#region src/components/tooltip/private/xy-chart-tooltip-overlay.tsx
2648
2765
  const CROSSHAIR_STROKE_WIDTH = 1.5;
2649
2766
  const CROSSHAIR_PAINT_PROPERTIES = /* @__PURE__ */ new Set([
2650
2767
  "stroke",
@@ -2746,15 +2863,36 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2746
2863
  const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2747
2864
  const marginTop = margin?.top ?? 0;
2748
2865
  const marginLeft = margin?.left ?? 0;
2749
- const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : Tooltip;
2750
2866
  const boxStyle = {
2751
- ...defaultStyles,
2752
2867
  zIndex,
2753
- backgroundColor: theme?.backgroundColor ?? "white",
2754
- boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2755
- ...theme?.htmlLabel,
2756
2868
  ...style
2757
2869
  };
2870
+ const { offsetLeft = 10, offsetTop = 10, unstyled, className, ...boxProps } = tooltipProps;
2871
+ const anchorTop = tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop;
2872
+ const box = detectBounds || tooltipPlacement !== "auto" ? /* @__PURE__ */ jsx(BoundedTooltip, {
2873
+ left: tooltipLeft,
2874
+ top: anchorTop,
2875
+ offsetLeft,
2876
+ offsetTop,
2877
+ unstyled,
2878
+ className,
2879
+ style: boxStyle,
2880
+ ...boxProps,
2881
+ ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2882
+ children: tooltipContent
2883
+ }) : /* @__PURE__ */ jsx(TooltipBox, {
2884
+ role: "presentation",
2885
+ unstyled,
2886
+ className,
2887
+ style: {
2888
+ position: "absolute",
2889
+ left: (tooltipLeft ?? 0) + offsetLeft,
2890
+ top: (anchorTop ?? 0) + offsetTop,
2891
+ ...!unstyled && boxStyle
2892
+ },
2893
+ ...boxProps,
2894
+ children: tooltipContent
2895
+ });
2758
2896
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("g", {
2759
2897
  className: "visx-tooltip-overlay",
2760
2898
  pointerEvents: "none",
@@ -2781,15 +2919,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2781
2919
  }),
2782
2920
  glyphs
2783
2921
  ]
2784
- }), container && createPortal(/* @__PURE__ */ jsx(TooltipComponent, {
2785
- left: tooltipLeft,
2786
- top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
2787
- style: boxStyle,
2788
- applyPositionStyle: true,
2789
- ...tooltipProps,
2790
- ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2791
- children: tooltipContent
2792
- }), container)] });
2922
+ }), container && createPortal(box, container)] });
2793
2923
  };
2794
2924
  /**
2795
2925
  * In-tree replacement for `@visx/xychart`'s `Tooltip`.
@@ -2814,7 +2944,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2814
2944
  * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2815
2945
  * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
2816
2946
  */
2817
- const XyChartTooltip = (props) => {
2947
+ const XyChartTooltipOverlay = (props) => {
2818
2948
  const tooltipContext = useContext(TooltipContext);
2819
2949
  const [container, setContainer] = useState(null);
2820
2950
  const anchorRef = useCallback((node) => {
@@ -2827,8 +2957,8 @@ const XyChartTooltip = (props) => {
2827
2957
  })] });
2828
2958
  };
2829
2959
  //#endregion
2830
- //#region src/components/tooltip/accessible-tooltip.tsx
2831
- const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2960
+ //#region src/components/tooltip/xy-chart-tooltip.tsx
2961
+ const XYChartTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2832
2962
  const tooltipContext = useContext(TooltipContext);
2833
2963
  const scopeElement = useChartScopeElement();
2834
2964
  const crosshairStroke = useMemo(() => {
@@ -2934,7 +3064,7 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2934
3064
  keyboardFocusedClassName,
2935
3065
  standaloneScopeClass
2936
3066
  ]);
2937
- return /* @__PURE__ */ jsx(XyChartTooltip, {
3067
+ return /* @__PURE__ */ jsx(XyChartTooltipOverlay, {
2938
3068
  ...props,
2939
3069
  verticalCrosshairStyle: {
2940
3070
  ...crosshairStroke,
@@ -2947,6 +3077,8 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2947
3077
  renderTooltip: focusableRenderTooltip
2948
3078
  });
2949
3079
  };
3080
+ /** @deprecated Use `XYChartTooltip`. */
3081
+ const AccessibleTooltip = XYChartTooltip;
2950
3082
  const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false, visibleSeriesKey }) => {
2951
3083
  const getChartRoot = useCallback(() => chartRef.current?.closest("[role=\"grid\"]") ?? chartRef.current, [chartRef]);
2952
3084
  const focusWithoutScrollIfNeeded = useCallback((element) => {
@@ -3056,7 +3188,6 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
3056
3188
  };
3057
3189
  //#endregion
3058
3190
  //#region src/providers/chart-context/hooks/use-default-hidden-series.ts
3059
- const useIsomorphicLayoutEffect$1 = typeof window !== "undefined" ? useLayoutEffect : useEffect;
3060
3191
  /**
3061
3192
  * Seeds a chart's hidden series once per chart ID, the first time the provider
3062
3193
  * sees that ID.
@@ -3077,7 +3208,7 @@ const useDefaultHiddenSeries = (chartId, seriesLabels) => {
3077
3208
  labelsRef.current = seriesLabels;
3078
3209
  const hasSeriesLabels = seriesLabels !== void 0;
3079
3210
  const shouldUseDefaults = hasSeriesLabels && !hasSeededChart(chartId);
3080
- useIsomorphicLayoutEffect$1(() => {
3211
+ useIsomorphicLayoutEffect(() => {
3081
3212
  if (labelsRef.current === void 0) return;
3082
3213
  seedChartHiddenSeries(chartId, labelsRef.current);
3083
3214
  }, [
@@ -3702,7 +3833,6 @@ var with_responsive_module_default = {
3702
3833
  };
3703
3834
  //#endregion
3704
3835
  //#region src/charts/private/with-responsive/with-responsive.tsx
3705
- const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
3706
3836
  /**
3707
3837
  * A higher-order component that provides responsive dimensions
3708
3838
  * to the wrapped chart component using useParentSize from `@visx/responsive`.
@@ -3986,10 +4116,8 @@ var line_chart_module_default = {
3986
4116
  "line-chart__annotation-label-popover-content": "a8ccharts-inuQka-line-chart__annotation-label-popover-content",
3987
4117
  "line-chart__annotation-label-trigger-button": "a8ccharts-inuQka-line-chart__annotation-label-trigger-button",
3988
4118
  "line-chart__annotations-overlay": "a8ccharts-inuQka-line-chart__annotations-overlay",
3989
- "line-chart__tooltip": "a8ccharts-inuQka-line-chart__tooltip",
3990
4119
  "line-chart__tooltip-date": "a8ccharts-inuQka-line-chart__tooltip-date",
3991
4120
  "line-chart__tooltip-label": "a8ccharts-inuQka-line-chart__tooltip-label",
3992
- "line-chart__tooltip-row": "a8ccharts-inuQka-line-chart__tooltip-row",
3993
4121
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
3994
4122
  "rise": "a8ccharts-inuQka-rise"
3995
4123
  };
@@ -4516,11 +4644,10 @@ const TooltipDate = ({ date, displayResolution }) => {
4516
4644
  * one row per visible series (label + formatted value), sorted descending by
4517
4645
  * value. Reused by AreaChart, which has the same multi-series shape.
4518
4646
  *
4519
- * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4520
- * @param contentStyle - Explicit tooltip content color overrides.
4647
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4521
4648
  * @return Tooltip JSX, or `null` when no datum is hovered.
4522
4649
  */
4523
- const renderDefaultTooltip = (params, contentStyle) => {
4650
+ const renderDefaultTooltip = (params) => {
4524
4651
  const { tooltipData, bucketInfo } = params;
4525
4652
  const nearestDatum = tooltipData?.nearestDatum?.datum;
4526
4653
  if (!nearestDatum) return null;
@@ -4533,29 +4660,24 @@ const renderDefaultTooltip = (params, contentStyle) => {
4533
4660
  if (b.value === null) return -1;
4534
4661
  return b.value - a.value;
4535
4662
  });
4536
- return /* @__PURE__ */ jsxs("div", {
4537
- className: line_chart_module_default["line-chart__tooltip"],
4538
- style: contentStyle,
4539
- children: [/* @__PURE__ */ jsx("div", {
4540
- className: line_chart_module_default["line-chart__tooltip-date"],
4541
- children: /* @__PURE__ */ jsx(TooltipDate, {
4542
- date: nearestDatum.date,
4543
- displayResolution: bucketInfo?.displayResolution ?? "day"
4544
- })
4545
- }), tooltipPoints.map((point) => /* @__PURE__ */ jsxs(Stack, {
4546
- direction: "row",
4547
- align: "center",
4548
- justify: "space-between",
4549
- className: line_chart_module_default["line-chart__tooltip-row"],
4550
- children: [/* @__PURE__ */ jsxs("span", {
4551
- className: line_chart_module_default["line-chart__tooltip-label"],
4552
- children: [point.key, ":"]
4553
- }), /* @__PURE__ */ jsx("span", {
4554
- className: line_chart_module_default["line-chart__tooltip-value"],
4555
- children: formatReading(point.value)
4556
- })]
4557
- }, point.key))]
4558
- });
4663
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
4664
+ className: line_chart_module_default["line-chart__tooltip-date"],
4665
+ children: /* @__PURE__ */ jsx(TooltipDate, {
4666
+ date: nearestDatum.date,
4667
+ displayResolution: bucketInfo?.displayResolution ?? "day"
4668
+ })
4669
+ }), tooltipPoints.map((point) => /* @__PURE__ */ jsxs(Stack, {
4670
+ direction: "row",
4671
+ align: "center",
4672
+ justify: "space-between",
4673
+ children: [/* @__PURE__ */ jsxs("span", {
4674
+ className: line_chart_module_default["line-chart__tooltip-label"],
4675
+ children: [point.key, ":"]
4676
+ }), /* @__PURE__ */ jsx("span", {
4677
+ className: line_chart_module_default["line-chart__tooltip-value"],
4678
+ children: formatReading(point.value)
4679
+ })]
4680
+ }, point.key))] });
4559
4681
  };
4560
4682
  const validateData$4 = (data) => {
4561
4683
  if (!data?.length) return __("No data available", "jetpack-charts");
@@ -4815,29 +4937,10 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4815
4937
  xAccessor: (d) => d?.date,
4816
4938
  yAccessor: (d) => d?.value
4817
4939
  };
4818
- const resolvedTooltipStyle = useMemo(() => {
4819
- if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
4820
- if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
4821
- return {
4822
- backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
4823
- ...tooltipStyle
4824
- };
4825
- }, [renderTooltip, tooltipStyle]);
4826
- const tooltipRenderer = useMemo(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
4827
- ...params,
4828
- bucketInfo
4829
- }, {
4830
- color: tooltipStyle?.color,
4831
- background: tooltipStyle?.background,
4832
- backgroundColor: tooltipStyle?.backgroundColor
4833
- }) : renderTooltip({
4940
+ const tooltipRenderer = useMemo(() => (params) => renderTooltip({
4834
4941
  ...params,
4835
4942
  bucketInfo
4836
- }), [
4837
- renderTooltip,
4838
- bucketInfo,
4839
- tooltipStyle
4840
- ]);
4943
+ }), [renderTooltip, bucketInfo]);
4841
4944
  if (error) return /* @__PURE__ */ jsx("div", {
4842
4945
  className: clsx("line-chart", line_chart_module_default["line-chart"]),
4843
4946
  children: error
@@ -4981,11 +5084,11 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4981
5084
  ] }, seriesData?.label || index);
4982
5085
  })
4983
5086
  }),
4984
- withTooltips && /* @__PURE__ */ jsx(AccessibleTooltip, {
5087
+ withTooltips && /* @__PURE__ */ jsx(XYChartTooltip, {
4985
5088
  detectBounds: true,
4986
5089
  snapTooltipToDatumX: true,
4987
5090
  tooltipPlacement,
4988
- style: resolvedTooltipStyle,
5091
+ style: tooltipStyle,
4989
5092
  snapTooltipToDatumY: true,
4990
5093
  showSeriesGlyphs: true,
4991
5094
  renderTooltip: tooltipRenderer,
@@ -5457,7 +5560,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
5457
5560
  children: seriesWithVisibility.map(renderSeries)
5458
5561
  }), !allSeriesHidden && !stacked && seriesWithVisibility.map(renderSeries)]
5459
5562
  }),
5460
- withTooltips && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AccessibleTooltip, {
5563
+ withTooltips && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(XYChartTooltip, {
5461
5564
  detectBounds: true,
5462
5565
  snapTooltipToDatumX: true,
5463
5566
  snapTooltipToDatumY: !stacked,
@@ -6068,6 +6171,7 @@ const ComparisonBars = ({ comparisonEntries, primaryKeys, groupPadding, horizont
6068
6171
  widthFactor
6069
6172
  });
6070
6173
  rects.push(/* @__PURE__ */ jsx("rect", {
6174
+ className: "bar-chart__comparison-bar",
6071
6175
  x: rect.x,
6072
6176
  y: rect.y,
6073
6177
  width: rect.width,
@@ -6483,7 +6587,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6483
6587
  const patternId = getPatternId(chartId, index);
6484
6588
  return `
6485
6589
  .visx-bar[fill="url(#${patternId})"],
6486
- .bar-chart__comparison-bars rect[fill="url(#${patternId})"] {
6590
+ .bar-chart__comparison-bar[fill="url(#${patternId})"] {
6487
6591
  stroke: ${color};
6488
6592
  stroke-width: 1;
6489
6593
  }
@@ -6665,15 +6769,15 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
6665
6769
  }),
6666
6770
  !allSeriesHidden && /* @__PURE__ */ jsx(WholeNumberTicks, {
6667
6771
  ...wholeNumberTicksProps,
6668
- children: (valueTicks) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Axis, {
6772
+ children: (valueTicks) => /* @__PURE__ */ jsxs(Fragment$1, { children: [chartOptions.axis.x.display !== false && /* @__PURE__ */ jsx(Axis, {
6669
6773
  ...chartOptions.axis.x,
6670
6774
  ...horizontal && valueTicks ? { tickValues: valueTicks } : {}
6671
- }), /* @__PURE__ */ jsx(Axis, {
6775
+ }), chartOptions.axis.y.display !== false && /* @__PURE__ */ jsx(Axis, {
6672
6776
  ...chartOptions.axis.y,
6673
6777
  ...!horizontal && valueTicks ? { tickValues: valueTicks } : {}
6674
6778
  })] })
6675
6779
  }),
6676
- withTooltips && /* @__PURE__ */ jsx(AccessibleTooltip, {
6780
+ withTooltips && /* @__PURE__ */ jsx(XYChartTooltip, {
6677
6781
  tooltipPlacement,
6678
6782
  tooltipAnchorTop,
6679
6783
  style: tooltipStyle,
@@ -6893,8 +6997,7 @@ var conversion_funnel_chart_module_default = {
6893
6997
  "step-rate": "a8ccharts-mGEVca-step-rate",
6894
6998
  "stretch": "a8ccharts-mGEVca-stretch",
6895
6999
  "tooltip-content": "a8ccharts-mGEVca-tooltip-content",
6896
- "tooltip-title": "a8ccharts-mGEVca-tooltip-title",
6897
- "tooltip-wrapper": "a8ccharts-mGEVca-tooltip-wrapper"
7000
+ "tooltip-title": "a8ccharts-mGEVca-tooltip-title"
6898
7001
  };
6899
7002
  //#endregion
6900
7003
  //#region src/charts/conversion-funnel-chart/private/use-funnel-selection.ts
@@ -6955,9 +7058,10 @@ const useFunnelSelection = (hideTooltip) => {
6955
7058
  * @param props.renderStepRate - Custom render function for step rates
6956
7059
  * @param props.renderMainMetric - Custom render function for the entire main metric section
6957
7060
  * @param props.renderTooltip - Custom render function for tooltip content
7061
+ * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
6958
7062
  * @return JSX element representing the conversion funnel chart
6959
7063
  */
6960
- const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
7064
+ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
6961
7065
  const chartId = useChartId(providedChartId);
6962
7066
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
6963
7067
  const chartRef = useRef(null);
@@ -7192,15 +7296,18 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7192
7296
  step: tooltipData,
7193
7297
  index: steps.findIndex((s) => s.id === tooltipData.id),
7194
7298
  top: tooltipTop,
7195
- left: tooltipLeft,
7196
- className: conversion_funnel_chart_module_default["tooltip-wrapper"]
7299
+ left: tooltipLeft
7197
7300
  }) : renderDefaultTooltip(tooltipData);
7198
7301
  if (!tooltipContent) return null;
7199
7302
  return /* @__PURE__ */ jsx(BoundedTooltip, {
7200
7303
  top: tooltipTop,
7201
7304
  left: tooltipLeft,
7202
- className: clsx(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
7203
- children: tooltipContent
7305
+ className: standaloneScopeClass,
7306
+ style: tooltipStyle,
7307
+ children: /* @__PURE__ */ jsx("div", {
7308
+ role: "tooltip",
7309
+ children: tooltipContent
7310
+ })
7204
7311
  });
7205
7312
  })()]
7206
7313
  });
@@ -7485,6 +7592,73 @@ let GoogleDataTableColumnRoleType = /* @__PURE__ */ function(GoogleDataTableColu
7485
7592
  GoogleDataTableColumnRoleType["domain"] = "domain";
7486
7593
  return GoogleDataTableColumnRoleType;
7487
7594
  }({});
7595
+ const resolveRole = (pointer, resolve) => {
7596
+ const raw = resolve(pointer);
7597
+ const parsed = raw ? color(raw) : null;
7598
+ if (!parsed) return {
7599
+ kind: "unreadable",
7600
+ raw
7601
+ };
7602
+ const { r, g, b } = parsed.opacity > 0 ? parsed.rgb() : {
7603
+ r: 0,
7604
+ g: 0,
7605
+ b: 0
7606
+ };
7607
+ return {
7608
+ kind: "color",
7609
+ rgb: [
7610
+ r,
7611
+ g,
7612
+ b
7613
+ ],
7614
+ alpha: parsed.opacity
7615
+ };
7616
+ };
7617
+ /**
7618
+ * Reads both label roles at the element the label text inherits from, so JS and CSS agree on what paints.
7619
+ *
7620
+ * @param resolve - A CSS variable resolver bound to that element.
7621
+ * @return The resolved label roles.
7622
+ */
7623
+ const resolveLabelRoles = (resolve) => ({
7624
+ label: resolveRole(CATALOG_POINTERS.label, resolve),
7625
+ labelInverse: resolveRole(CATALOG_POINTERS.labelInverse, resolve)
7626
+ });
7627
+ const roleContrast = (role, fill, fillLuminance) => {
7628
+ if (role.kind === "unreadable") return 0;
7629
+ return luminanceContrastRatio(fillLuminance, rgbLuminance(blendRgb(role.rgb, fill, role.alpha)));
7630
+ };
7631
+ /**
7632
+ * Picks the label color for text drawn on a fill: the role that contrasts more, or black/white when neither reaches AA.
7633
+ *
7634
+ * Both roles set to one color is a host's choice to keep that color, so it never falls back.
7635
+ *
7636
+ * @param fill - The fill the text sits on, as unrounded sRGB channels.
7637
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7638
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7639
+ * @return The color the label should paint with.
7640
+ */
7641
+ const pickLabelTextColorForFill = (fill, roles, defaultRole) => {
7642
+ if (!roles || (0, import_fast_deep_equal.default)(roles.label, roles.labelInverse)) return defaultRole;
7643
+ const fillLuminance = rgbLuminance(fill);
7644
+ const labelContrast = roleContrast(roles.label, fill, fillLuminance);
7645
+ const inverseContrast = roleContrast(roles.labelInverse, fill, fillLuminance);
7646
+ if (Math.max(labelContrast, inverseContrast) >= 4.55) return labelContrast > inverseContrast ? "label" : "label-inverse";
7647
+ return luminanceContrastRatio(fillLuminance, 0) > luminanceContrastRatio(fillLuminance, 1) ? "black" : "white";
7648
+ };
7649
+ /**
7650
+ * `pickLabelTextColorForFill` for a fill given as a color.
7651
+ *
7652
+ * @param fill - The fill, in any color syntax `normalizeColorToHex` reads.
7653
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7654
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7655
+ * @return The color the label should paint with.
7656
+ */
7657
+ const pickLabelTextColor = (fill, roles, defaultRole) => {
7658
+ const fillHex = normalizeColorToHex(fill);
7659
+ if (!isValidHexColor(fillHex)) return defaultRole;
7660
+ return pickLabelTextColorForFill(hexToRgb(fillHex), roles, defaultRole);
7661
+ };
7488
7662
  //#endregion
7489
7663
  //#region src/charts/heatmap-chart/heatmap-chart.module.scss
7490
7664
  var heatmap_chart_module_default = {
@@ -7494,9 +7668,11 @@ var heatmap_chart_module_default = {
7494
7668
  "heatmap-chart__cell--hidden": "a8ccharts-O3YMOW-heatmap-chart__cell--hidden",
7495
7669
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
7496
7670
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
7497
- "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
7498
7671
  "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
7499
7672
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
7673
+ "heatmap-chart__cell-value--black": "a8ccharts-O3YMOW-heatmap-chart__cell-value--black",
7674
+ "heatmap-chart__cell-value--inverse": "a8ccharts-O3YMOW-heatmap-chart__cell-value--inverse",
7675
+ "heatmap-chart__cell-value--white": "a8ccharts-O3YMOW-heatmap-chart__cell-value--white",
7500
7676
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
7501
7677
  "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
7502
7678
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
@@ -7518,30 +7694,44 @@ var heatmap_chart_module_default = {
7518
7694
  //#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
7519
7695
  const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
7520
7696
  /**
7521
- * Get the min and max values from heatmap data, ignoring null/NaN. Summary
7522
- * columns stay out: a roll-up on the scale would flatten every real cell.
7697
+ * Get the min and max values from heatmap data, ignoring null/NaN and the zeros
7698
+ * `isEmptyValue` paints as empty. Summary columns stay out: a roll-up on the
7699
+ * scale would flatten every real cell.
7523
7700
  * @param data - The heatmap columns
7524
7701
  * @return Tuple of [min, max] values
7525
7702
  */
7526
7703
  const getValueExtent = (data) => {
7527
7704
  let min = Infinity;
7528
7705
  let max = -Infinity;
7706
+ let hasZero = false;
7529
7707
  for (const column of data) {
7530
7708
  if (column.summary) continue;
7531
7709
  for (const cell of column.data) {
7532
7710
  if (!isPresent(cell.value)) continue;
7711
+ if (cell.value === 0) {
7712
+ hasZero = true;
7713
+ continue;
7714
+ }
7533
7715
  if (cell.value < min) min = cell.value;
7534
7716
  if (cell.value > max) max = cell.value;
7535
7717
  }
7536
7718
  }
7719
+ if (hasZero && min < 0) max = Math.max(max, 0);
7537
7720
  if (min === Infinity) return [0, 0];
7538
7721
  return [min, max];
7539
7722
  };
7540
7723
  /**
7724
+ * Whether a value paints as an empty cell: a zero in data with no negatives
7725
+ * counts nothing, so it must not look like the lowest step of activity.
7726
+ *
7727
+ * @param value - The cell value
7728
+ * @param extent - The extent from `getValueExtent`
7729
+ * @return True when the cell takes the empty-cell color
7730
+ */
7731
+ const isEmptyValue = (value, extent) => value === 0 && extent[0] >= 0;
7732
+ /**
7541
7733
  * Normalize a value to 0–1 within the extent. A flat extent (min === max)
7542
- * maps to 1 — every cell is equally the "highest" — except an all-zero
7543
- * extent, which maps to 0 so a no-activity grid renders at the scale's
7544
- * bottom instead of full intensity.
7734
+ * maps to 1, since every cell is equally the "highest".
7545
7735
  *
7546
7736
  * @param value - The value to normalize
7547
7737
  * @param extent - Tuple of [min, max] values for the normalization range
@@ -7549,9 +7739,40 @@ const getValueExtent = (data) => {
7549
7739
  */
7550
7740
  const getNormalizedValue = (value, extent) => {
7551
7741
  const [min, max] = extent;
7552
- if (min === max) return max === 0 ? 0 : 1;
7742
+ if (min === max) return 1;
7553
7743
  return Math.min(1, Math.max(0, (value - min) / (max - min)));
7554
7744
  };
7745
+ const STEPS = 200;
7746
+ const firstMeeting = (from, to, meets) => {
7747
+ for (let step = 0; step <= STEPS; step++) {
7748
+ const color = mixHexColors(from, to, step / STEPS);
7749
+ if (meets(color)) return color;
7750
+ }
7751
+ return null;
7752
+ };
7753
+ /**
7754
+ * The two ends of the heatmap fill scale: 3:1 against the background and the empty cell at the
7755
+ * low end, toward 9:1 against the background at the high end.
7756
+ *
7757
+ * @param primary - The resolved primary color, as six-digit hex.
7758
+ * @param background - The resolved chart background, as six-digit hex.
7759
+ * @param emptyCell - The resolved empty-cell color, as six-digit hex, when it is opaque.
7760
+ * @return The scale ends, as six-digit hex.
7761
+ */
7762
+ const getHeatmapScale = (primary, background, emptyCell) => {
7763
+ const primaryStandsOut = contrastRatio(primary, background) >= 3;
7764
+ const extreme = (primaryStandsOut ? relativeLuminance(primary) > relativeLuminance(background) : contrastRatio("#ffffff", background) > contrastRatio("#000000", background)) ? "#ffffff" : "#000000";
7765
+ const lowAgainst = (references) => {
7766
+ const meets = (color) => references.every((reference) => contrastRatio(color, reference) >= 3);
7767
+ return (primaryStandsOut ? firstMeeting(background, primary, meets) : null) ?? firstMeeting(primary, extreme, meets);
7768
+ };
7769
+ const high = firstMeeting(primary, extreme, (color) => contrastRatio(color, background) >= 9) ?? extreme;
7770
+ const againstEmptyCell = emptyCell ? lowAgainst([background, emptyCell]) : null;
7771
+ return {
7772
+ low: (againstEmptyCell && contrastRatio(againstEmptyCell, background) <= contrastRatio(high, background) ? againstEmptyCell : lowAgainst([background])) ?? extreme,
7773
+ high
7774
+ };
7775
+ };
7555
7776
  //#endregion
7556
7777
  //#region src/charts/heatmap-chart/private/heatmap-context.ts
7557
7778
  /** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
@@ -7562,7 +7783,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7562
7783
  const context = useContext(HeatmapContext);
7563
7784
  const { legend } = useGlobalChartsTheme();
7564
7785
  if (!context) return null;
7565
- const { primaryColorHex } = context;
7786
+ const { fillVars } = context;
7566
7787
  const labelStyle = legend.labelStyles;
7567
7788
  return /* @__PURE__ */ jsxs(Stack, {
7568
7789
  direction: "row",
@@ -7585,7 +7806,7 @@ const HeatmapLegend = ({ steps = 5, variant = "swatches", lessLabel, moreLabel }
7585
7806
  return /* @__PURE__ */ jsx("span", {
7586
7807
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
7587
7808
  style: {
7588
- "--a8c-charts-color-heatmap-primary": primaryColorHex,
7809
+ ...fillVars,
7589
7810
  "--a8c-charts-heatmap-cell-intensity": intensity
7590
7811
  }
7591
7812
  }, index);
@@ -7789,26 +8010,25 @@ const stepCalendarCell = (grid, blocks, from, key) => {
7789
8010
  };
7790
8011
  //#endregion
7791
8012
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
7792
- const CELL_MIX_FLOOR = .15;
7793
- const NO_ROW_LABELS = [];
7794
- const TOOLTIP_BOX_STYLES = {
7795
- light: {
7796
- ...defaultStyles,
7797
- zIndex: 3
7798
- },
7799
- dark: { zIndex: 3 }
8013
+ const CELL_VALUE_MODIFIER = {
8014
+ label: void 0,
8015
+ "label-inverse": heatmap_chart_module_default["heatmap-chart__cell-value--inverse"],
8016
+ black: heatmap_chart_module_default["heatmap-chart__cell-value--black"],
8017
+ white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
7800
8018
  };
8019
+ const NO_ROW_LABELS = [];
8020
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
7801
8021
  const cellName = (info) => info.cellLabel || [
7802
8022
  info.groupLabel,
7803
8023
  info.columnLabel,
7804
8024
  info.rowLabel
7805
8025
  ].filter(Boolean).join(" ");
7806
- 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 }) => {
8026
+ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, columnGroups, keyboardNavigation = "grid", ariaLabel, primaryColor, gap = "md", withTooltips = false, renderTooltip, tooltipStyle, children }) => {
7807
8027
  const chartId = useChartId(providedChartId);
7808
8028
  const tooltipBoxStyle = tooltipStyle ? {
7809
- ...TOOLTIP_BOX_STYLES[tooltipVariant],
8029
+ ...TOOLTIP_BOX_STYLE,
7810
8030
  ...tooltipStyle
7811
- } : TOOLTIP_BOX_STYLES[tooltipVariant];
8031
+ } : TOOLTIP_BOX_STYLE;
7812
8032
  const { getElementStyles, theme } = useGlobalChartsContext();
7813
8033
  const scopeElement = useChartScopeElement();
7814
8034
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -7817,19 +8037,44 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7817
8037
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = useTooltip();
7818
8038
  const standaloneScopeClass = useStandaloneScopeClass();
7819
8039
  const containerRef = useRef(null);
8040
+ const [labelRoles, setLabelRoles] = useState(null);
7820
8041
  const getTooltipOrigin = useCallback(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
7821
8042
  const { color: primaryColorHex } = getElementStyles({
7822
8043
  index: 0,
7823
8044
  overrideColor: primaryColor
7824
8045
  });
7825
- const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
8046
+ const resolveAtScope = createCssVariableResolver(scopeElement);
8047
+ const chartBackgroundHex = resolveOpaqueHex(CATALOG_POINTERS.background, resolveAtScope) ?? "#ffffff";
8048
+ const emptyCellHex = resolveOpaqueHex(CATALOG_POINTERS.track, resolveAtScope);
7826
8049
  const primaryHex = normalizeColorToHex(primaryColorHex);
7827
- const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
8050
+ const scale = useMemo(() => isValidHexColor(primaryHex) ? getHeatmapScale(primaryHex, chartBackgroundHex, emptyCellHex) : null, [
8051
+ primaryHex,
8052
+ chartBackgroundHex,
8053
+ emptyCellHex
8054
+ ]);
8055
+ const fillVars = useMemo(() => ({
8056
+ "--a8c-charts-color-heatmap-primary": primaryColorHex,
8057
+ ...scale && {
8058
+ "--a8c-charts-color-heatmap-low": scale.low,
8059
+ "--a8c-charts-color-heatmap-high": scale.high
8060
+ }
8061
+ }), [primaryColorHex, scale]);
8062
+ const cellTextColor = (intensity) => scale ? pickLabelTextColorForFill(blendRgb(hexToRgb(scale.high), hexToRgb(scale.low), intensity), labelRoles, "label") : "label";
8063
+ useIsomorphicLayoutEffect(() => {
8064
+ if (!containerRef.current) return;
8065
+ const next = resolveLabelRoles(createCssVariableResolver(containerRef.current));
8066
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
8067
+ }, [
8068
+ data,
8069
+ className,
8070
+ primaryColorHex,
8071
+ chartBackgroundHex
8072
+ ]);
7828
8073
  const extent = useMemo(() => getValueExtent(data), [data]);
7829
8074
  const heatmapContext = useMemo(() => ({
7830
8075
  extent,
7831
- primaryColorHex
7832
- }), [extent, primaryColorHex]);
8076
+ fillVars
8077
+ }), [extent, fillVars]);
7833
8078
  const columns = data.length;
7834
8079
  const rows = Math.max(0, ...data.map((column) => column.data.length));
7835
8080
  const groupLayout = useMemo(() => resolveColumnGroups(columnGroups, columns, 2), [columnGroups, columns]);
@@ -7980,9 +8225,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7980
8225
  const firstDataRow = hasColumnLabels ? 2 : 1;
7981
8226
  const dataTrack = (column) => column.summary ? "minmax(auto, max-content)" : columnTrack;
7982
8227
  const gapTrack = compact ? `minmax(${groupGap}px, 1fr)` : `${groupGap}px`;
8228
+ const columnTracks = data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ");
7983
8229
  const gridStyle = {
7984
- "--a8c-charts-color-heatmap-primary": primaryColorHex,
7985
- gridTemplateColumns: `auto ${data.map((column, columnIndex) => groupLayout.columns[columnIndex].gapBefore ? `${gapTrack} ${dataTrack(column)}` : dataTrack(column)).join(" ")}`,
8230
+ ...fillVars,
8231
+ gridTemplateColumns: `auto ${columnTracks}`,
7986
8232
  gridTemplateRows: `${hasColumnLabels ? "auto " : ""}repeat(${rows}, ${rowTrack})${hasGroups ? " auto" : ""}`
7987
8233
  };
7988
8234
  if (compact) {
@@ -8082,7 +8328,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8082
8328
  }, `cell-${columnIndex}-${rowIndex}`);
8083
8329
  const value = cell?.value ?? null;
8084
8330
  const present = isPresent(value);
8085
- const filled = present && !column.summary;
8331
+ const filled = present && !column.summary && !isEmptyValue(value, extent);
8086
8332
  const normalized = filled ? getNormalizedValue(value, extent) : 0;
8087
8333
  const info = buildTooltipData(columnIndex, rowIndex);
8088
8334
  const accessibleLabel = `${cellName(info)}: ${info.value === null ? __("No data", "jetpack-charts") : formatNumber(info.value)}`;
@@ -8096,7 +8342,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8096
8342
  "data-row": rowIndex,
8097
8343
  className: clsx(heatmap_chart_module_default["heatmap-chart__cell"], {
8098
8344
  [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
8099
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
8100
8345
  [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
8101
8346
  ...summaryGaps(columnIndex),
8102
8347
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
@@ -8108,7 +8353,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8108
8353
  onMouseMove: handleCellMouseMove,
8109
8354
  onMouseLeave: handleCellMouseLeave,
8110
8355
  children: (drawValues || column.summary) && present && /* @__PURE__ */ jsx("span", {
8111
- className: heatmap_chart_module_default["heatmap-chart__cell-value"],
8356
+ className: clsx(heatmap_chart_module_default["heatmap-chart__cell-value"], filled && CELL_VALUE_MODIFIER[cellTextColor(normalized)]),
8112
8357
  children: formatNumberCompact(value)
8113
8358
  })
8114
8359
  }, `cell-${columnIndex}-${rowIndex}`);
@@ -8132,7 +8377,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8132
8377
  }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
8133
8378
  top: tooltipTop,
8134
8379
  left: tooltipLeft,
8135
- className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
8136
8380
  style: tooltipBoxStyle,
8137
8381
  children: /* @__PURE__ */ jsx("div", {
8138
8382
  className: standaloneScopeClass,
@@ -11359,7 +11603,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
11359
11603
  var pie_chart_module_default = {
11360
11604
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
11361
11605
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
11606
+ "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",
11362
11607
  "pie-chart__label-text--on-light": "a8ccharts-gnszbG-pie-chart__label-text--on-light",
11608
+ "pie-chart__label-text--white": "a8ccharts-gnszbG-pie-chart__label-text--white",
11363
11609
  "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
11364
11610
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
11365
11611
  };
@@ -11367,18 +11613,13 @@ var pie_chart_module_default = {
11367
11613
  //#region src/charts/pie-chart/pie-chart.tsx
11368
11614
  /**
11369
11615
  * Default tooltip renderer for pie charts.
11370
- * Renders a BaseTooltip with the hovered segment's data.
11616
+ * Renders the default `label: value` tooltip content for the hovered segment.
11371
11617
  *
11372
11618
  * @param {PieChartRenderTooltipParams} params - The tooltip parameters containing the hovered data point
11373
11619
  * @return {ReactNode} The rendered tooltip content
11374
11620
  */
11375
11621
  const renderDefaultPieTooltip = ({ tooltipData }) => {
11376
- return /* @__PURE__ */ jsx(BaseTooltip, {
11377
- data: tooltipData,
11378
- top: 0,
11379
- left: 0,
11380
- renderContainer: false
11381
- });
11622
+ return /* @__PURE__ */ jsx(LabelValueContent, { data: tooltipData });
11382
11623
  };
11383
11624
  /**
11384
11625
  * Validates the pie chart data
@@ -11403,24 +11644,11 @@ const validateData$1 = (data) => {
11403
11644
  message: ""
11404
11645
  };
11405
11646
  };
11406
- /**
11407
- * Whether a pie slice's label should use the dark `label` role instead of the default `label-inverse` role.
11408
- *
11409
- * A label plate always wins: the text sits on the plate, not the slice fill, so it never flips.
11410
- * Otherwise the role that contrasts more with the resolved slice fill wins. `null` pointers (not
11411
- * yet resolved, or labels off) keep the default inverse role.
11412
- *
11413
- * @param fill - The slice's resolved fill color.
11414
- * @param pointers - The label pointers, resolved at the chart's own root element.
11415
- * @return Whether the label needs dark text.
11416
- */
11417
- const labelNeedsDarkText = (fill, pointers) => {
11418
- if (!pointers || pointers.hasPlate) return false;
11419
- const fillHex = normalizeColorToHex(fill);
11420
- if (!isValidHexColor(fillHex) || !isValidHexColor(pointers.labelHex)) return false;
11421
- if (pointers.isInverseSeeThrough) return true;
11422
- if (!isValidHexColor(pointers.labelInverseHex)) return false;
11423
- return contrastRatio(fillHex, pointers.labelHex) > contrastRatio(fillHex, pointers.labelInverseHex);
11647
+ const LABEL_TEXT_MODIFIER = {
11648
+ label: pie_chart_module_default["pie-chart__label-text--on-light"],
11649
+ "label-inverse": void 0,
11650
+ black: pie_chart_module_default["pie-chart__label-text--black"],
11651
+ white: pie_chart_module_default["pie-chart__label-text--white"]
11424
11652
  };
11425
11653
  /**
11426
11654
  * Renders a pie or donut chart using the provided data.
@@ -11437,7 +11665,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11437
11665
  const standaloneScopeClass = useStandaloneScopeClass();
11438
11666
  const containerRef = useRef(null);
11439
11667
  const rootRef = useRef(null);
11440
- const [labelPointers, setLabelPointers] = useState(null);
11668
+ const [labelRoles, setLabelRoles] = useState(null);
11441
11669
  const onMouseLeave = useCallback(() => {
11442
11670
  if (!withTooltips) return;
11443
11671
  hideTooltip();
@@ -11448,18 +11676,9 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11448
11676
  if (!showLabels || !rootRef.current) return;
11449
11677
  const resolve = createCssVariableResolver(rootRef.current);
11450
11678
  const rawLabelBackground = resolve(CATALOG_POINTERS.labelBackground);
11451
- const rawLabel = resolve(CATALOG_POINTERS.label);
11452
- const isLabelOpaque = rawLabel ? color(rawLabel)?.opacity === 1 : false;
11453
- const rawLabelInverse = resolve(CATALOG_POINTERS.labelInverse);
11454
- const labelInverseColor = rawLabelInverse ? color(rawLabelInverse) : null;
11455
11679
  const plateColor = rawLabelBackground ? color(rawLabelBackground) : null;
11456
- const next = {
11457
- hasPlate: rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false,
11458
- labelHex: isLabelOpaque ? normalizeColorToHex(CATALOG_POINTERS.label, null, resolve) : "",
11459
- labelInverseHex: normalizeColorToHex(CATALOG_POINTERS.labelInverse, null, resolve),
11460
- isInverseSeeThrough: labelInverseColor ? labelInverseColor.opacity < 1 : false
11461
- };
11462
- setLabelPointers((previous) => previous && previous.hasPlate === next.hasPlate && previous.labelHex === next.labelHex && previous.labelInverseHex === next.labelInverseHex && previous.isInverseSeeThrough === next.isInverseSeeThrough ? previous : next);
11680
+ const next = (rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false) ? null : resolveLabelRoles(resolve);
11681
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
11463
11682
  }, [
11464
11683
  showLabels,
11465
11684
  className,
@@ -11630,7 +11849,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11630
11849
  ry: 4,
11631
11850
  pointerEvents: "none"
11632
11851
  }), /* @__PURE__ */ jsx("text", {
11633
- className: clsx(pie_chart_module_default["pie-chart__label-text"], { [pie_chart_module_default["pie-chart__label-text--on-light"]]: labelNeedsDarkText(fill, labelPointers) }),
11852
+ className: clsx(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
11634
11853
  x: centroidX,
11635
11854
  y: centroidY,
11636
11855
  dy: ".33em",
@@ -11686,18 +11905,13 @@ var pie_semi_circle_chart_module_default = {
11686
11905
  //#region src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx
11687
11906
  /**
11688
11907
  * Default tooltip renderer for semi-circle pie charts.
11689
- * Renders a BaseTooltip with the hovered segment's data.
11908
+ * Renders the default `label: value` tooltip content for the hovered segment.
11690
11909
  *
11691
11910
  * @param {PieSemiCircleChartRenderTooltipParams} params - The tooltip parameters containing the hovered data point
11692
11911
  * @return {ReactNode} The rendered tooltip content
11693
11912
  */
11694
11913
  const renderDefaultPieSemiCircleTooltip = ({ tooltipData }) => {
11695
- return /* @__PURE__ */ jsx(BaseTooltip, {
11696
- data: tooltipData,
11697
- top: 0,
11698
- left: 0,
11699
- renderContainer: false
11700
- });
11914
+ return /* @__PURE__ */ jsx(LabelValueContent, { data: tooltipData });
11701
11915
  };
11702
11916
  const PAD_ANGLE = .03;
11703
11917
  const DEFAULT_WIDTH$1 = 400;
@@ -12137,6 +12351,6 @@ function TrendIndicator({ direction, value, className, style, showIcon = true })
12137
12351
  });
12138
12352
  }
12139
12353
  //#endregion
12140
- export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, GoogleDataTableColumnRoleType, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, buildMonthCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getBucketInfo, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, prefersLightText, relativeLuminance, resolveCssVariable, useCalendarHeatmapData, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, useMonthCalendarHeatmapData, validateHexColor };
12354
+ export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, GoogleDataTableColumnRoleType, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TooltipBox, TrendIndicator, XYChartTooltip, buildCalendarHeatmapData, buildMonthCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getBucketInfo, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, prefersLightText, relativeLuminance, resolveCssVariable, useCalendarHeatmapData, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, useMonthCalendarHeatmapData, validateHexColor };
12141
12355
 
12142
12356
  //# sourceMappingURL=index.js.map