@automattic/charts 4.5.0 → 4.7.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 (62) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/TOKENS.md +12 -10
  3. package/dist/index.cjs +248 -146
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +33 -20
  6. package/dist/index.d.cts +22 -11
  7. package/dist/index.d.ts +22 -11
  8. package/dist/index.js +249 -147
  9. package/dist/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +0 -10
  12. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +4 -2
  13. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +11 -1
  14. package/src/charts/conversion-funnel-chart/types.ts +3 -0
  15. package/src/charts/geo-chart/geo-chart.tsx +4 -0
  16. package/src/charts/geo-chart/index.ts +7 -1
  17. package/src/charts/geo-chart/test/geo-chart.test.tsx +8 -0
  18. package/src/charts/geo-chart/types.ts +12 -0
  19. package/src/charts/heatmap-chart/heatmap-chart.module.scss +10 -3
  20. package/src/charts/heatmap-chart/heatmap-chart.tsx +54 -37
  21. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +129 -25
  22. package/src/charts/heatmap-chart/types.ts +2 -6
  23. package/src/charts/line-chart/crosshair-style-types.fixture.ts +23 -0
  24. package/src/charts/line-chart/line-chart.module.scss +0 -5
  25. package/src/charts/line-chart/line-chart.tsx +8 -40
  26. package/src/charts/line-chart/test/line-chart.test.tsx +19 -47
  27. package/src/charts/pie-chart/pie-chart.module.scss +9 -0
  28. package/src/charts/pie-chart/pie-chart.tsx +22 -76
  29. package/src/charts/pie-chart/test/pie-chart.test.tsx +21 -8
  30. package/src/charts/private/chart-composition/chart-html.tsx +1 -1
  31. package/src/charts/private/chart-composition/chart-svg.tsx +1 -1
  32. package/src/charts/private/label-text-color.ts +116 -0
  33. package/src/charts/private/radial-wipe-animation/radial-wipe-animation.tsx +2 -0
  34. package/src/charts/private/svg-empty-state/svg-empty-state.tsx +1 -1
  35. package/src/charts/private/test/label-text-color.test.ts +191 -0
  36. package/src/charts/private/with-responsive/test/with-responsive.test.tsx +1 -0
  37. package/src/charts/private/with-responsive/with-responsive.tsx +2 -5
  38. package/src/components/tooltip/accessible-tooltip.tsx +1 -1
  39. package/src/components/tooltip/base-tooltip.module.scss +12 -8
  40. package/src/components/tooltip/base-tooltip.tsx +11 -9
  41. package/src/components/tooltip/private/bounded-tooltip.tsx +27 -8
  42. package/src/components/tooltip/private/tooltip-theme.tsx +37 -0
  43. package/src/components/tooltip/test/bounded-tooltip.test.tsx +82 -1
  44. package/src/components/tooltip/test/tool-tip.test.tsx +5 -0
  45. package/src/components/tooltip/test/tooltip-theme.test.tsx +61 -0
  46. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +41 -28
  47. package/src/components/tooltip/xy-chart-tooltip.tsx +29 -29
  48. package/src/components/trend-indicator/trend-indicator.tsx +1 -0
  49. package/src/hooks/index.ts +1 -0
  50. package/src/hooks/test/use-xychart-theme.test.tsx +3 -3
  51. package/src/hooks/use-isomorphic-layout-effect.ts +5 -0
  52. package/src/hooks/use-xychart-theme.ts +4 -12
  53. package/src/index.ts +7 -1
  54. package/src/providers/chart-context/hooks/use-default-hidden-series.ts +2 -4
  55. package/src/providers/chart-context/private/perceptual-color.ts +45 -4
  56. package/src/providers/chart-context/test/perceptual-color.test.ts +31 -0
  57. package/src/styles/chart-scope-class.ts +5 -0
  58. package/src/styles/chart-scope.scss +9 -6
  59. package/src/styles/test/chart-scope.test.ts +3 -3
  60. package/src/utils/color-utils.ts +2 -1
  61. package/src/visx/types.ts +1 -1
  62. package/src/charts/line-chart/test/crosshair-style-types.test.ts +0 -31
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ 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) => {
@@ -881,14 +883,27 @@ const PROTANOPIA = [
881
883
  const DEGREES = Math.PI / 180;
882
884
  const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
883
885
  const fromLinear = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - .055;
884
- const hexToLinear = (hex) => {
886
+ /**
887
+ * The sRGB channels of a hex color.
888
+ *
889
+ * @param hex - A six-digit hex color.
890
+ * @return Channels from 0 to 255.
891
+ * @throws {Error} if the hex string is malformed
892
+ */
893
+ const hexToRgb = (hex) => {
885
894
  validateHexColor(hex);
886
- return [
895
+ const [r, g, b] = [
887
896
  1,
888
897
  3,
889
898
  5
890
- ].map((start) => toLinear(parseInt(hex.slice(start, start + 2), 16) / 255));
899
+ ].map((start) => parseInt(hex.slice(start, start + 2), 16));
900
+ return [
901
+ r,
902
+ g,
903
+ b
904
+ ];
891
905
  };
906
+ const hexToLinear = (hex) => hexToRgb(hex).map((channel) => toLinear(channel / 255));
892
907
  const linearToHex = (linear) => "#" + linear.map((channel) => Math.round(Math.min(1, Math.max(0, fromLinear(channel))) * 255).toString(16).padStart(2, "0")).join("");
893
908
  const simulate = (matrix, [r, g, b]) => [
894
909
  0,
@@ -1014,6 +1029,35 @@ const hexToOklch = (hex) => {
1014
1029
  };
1015
1030
  };
1016
1031
  /**
1032
+ * `foreground` over `background` at `weight`, left unrounded as CSS `color-mix` and alpha compositing leave it.
1033
+ *
1034
+ * A mix rounded to hex can land on the other side of a contrast threshold from the fill the browser paints.
1035
+ *
1036
+ * @param foreground - The color weighted by `weight`.
1037
+ * @param background - The color it is mixed over.
1038
+ * @param weight - Share of `foreground`, from 0 to 1.
1039
+ * @return The mixed channels.
1040
+ */
1041
+ const blendRgb = (foreground, background, weight) => {
1042
+ const share = Math.min(1, Math.max(0, weight));
1043
+ const [r, g, b] = foreground.map((channel, i) => background[i] + (channel - background[i]) * share);
1044
+ return [
1045
+ r,
1046
+ g,
1047
+ b
1048
+ ];
1049
+ };
1050
+ /**
1051
+ * WCAG relative luminance of unrounded sRGB channels.
1052
+ *
1053
+ * @param rgb - Channels from 0 to 255.
1054
+ * @return Relative luminance from 0 to 1.
1055
+ */
1056
+ const rgbLuminance = (rgb) => {
1057
+ const [r, g, b] = rgb.map((channel) => toLinear(channel / 255));
1058
+ return .2126 * r + .7152 * g + .0722 * b;
1059
+ };
1060
+ /**
1017
1061
  * WCAG contrast ratio between two relative luminances.
1018
1062
  *
1019
1063
  * @param first - First relative luminance.
@@ -1021,15 +1065,6 @@ const hexToOklch = (hex) => {
1021
1065
  * @return Ratio from 1 to 21.
1022
1066
  */
1023
1067
  const luminanceContrastRatio = (first, second) => (Math.max(first, second) + .05) / (Math.min(first, second) + .05);
1024
- /**
1025
- * WCAG contrast ratio between two hex colors.
1026
- *
1027
- * @param first - First hex color.
1028
- * @param second - Second hex color.
1029
- * @return Ratio from 1 to 21.
1030
- * @throws {Error} if either hex string is malformed
1031
- */
1032
- const contrastRatio = (first, second) => luminanceContrastRatio(relativeLuminance(first), relativeLuminance(second));
1033
1068
  /** WCAG 1.4.3 text contrast, which a label drawn on a fill needs from at least one label color. */
1034
1069
  const MIN_LABEL_CONTRAST = 4.5;
1035
1070
  /** Degrees of hue one unit of OKLCH chroma is worth when ranking candidates by hue. */
@@ -1534,15 +1569,13 @@ const useXYChartTheme = (data) => {
1534
1569
  const resolve = createCssVariableResolver(scopeElement);
1535
1570
  const resolveColor = (value) => value ? resolve(value) ?? value : value;
1536
1571
  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
1572
  return (0, _visx_xychart.buildChartTheme)({
1540
1573
  ...theme,
1541
1574
  gridColor: "",
1542
1575
  gridColorDark: "",
1543
1576
  colors: paletteColors,
1544
1577
  backgroundColor: resolveColor(CATALOG_POINTERS.background),
1545
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1578
+ htmlLabel: { color: CATALOG_POINTERS.labelAxis },
1546
1579
  gridStyles: {
1547
1580
  ...theme.gridStyles,
1548
1581
  stroke: CATALOG_POINTERS.grid
@@ -1977,6 +2010,9 @@ function usePrefersReducedMotion() {
1977
2010
  return prefersReducedMotion;
1978
2011
  }
1979
2012
  //#endregion
2013
+ //#region src/hooks/use-isomorphic-layout-effect.ts
2014
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
2015
+ //#endregion
1980
2016
  //#region src/providers/chart-context/hooks/use-chart-registration.ts
1981
2017
  const useChartRegistration = ({ chartId, legendItems, chartType, isDataValid, metadata }) => {
1982
2018
  const { registerChart, unregisterChart } = useGlobalChartsContext();
@@ -2449,6 +2485,30 @@ var base_tooltip_module_default = {
2449
2485
  "tooltip": "a8ccharts--zY0xG-tooltip"
2450
2486
  };
2451
2487
  //#endregion
2488
+ //#region src/components/tooltip/private/tooltip-theme.tsx
2489
+ const DEFAULT_SURFACE = "#1e1e1e";
2490
+ const TOOLTIP_SURFACE = `var(--a8c-charts-color-tooltip-surface, ${DEFAULT_SURFACE})`;
2491
+ const toSeed = (value) => {
2492
+ const parsed = value ? (0, _visx_vendor_d3_color.color)(value) : null;
2493
+ return parsed && parsed.opacity === 1 ? parsed.formatHex() : DEFAULT_SURFACE;
2494
+ };
2495
+ /**
2496
+ * 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.
2497
+ *
2498
+ * @param props - Component props.
2499
+ * @param props.children - The tooltip box.
2500
+ * @return The themed box.
2501
+ */
2502
+ const TooltipTheme = ({ children }) => {
2503
+ const scopeElement = useChartScopeElement();
2504
+ const color = (0, react.useMemo)(() => ({ background: toSeed(resolveCssVariable(TOOLTIP_SURFACE, scopeElement)) }), [scopeElement]);
2505
+ if (!_wordpress_theme.ThemeProvider) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
2506
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_theme.ThemeProvider, {
2507
+ color,
2508
+ children
2509
+ });
2510
+ };
2511
+ //#endregion
2452
2512
  //#region src/components/tooltip/base-tooltip.tsx
2453
2513
  const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2454
2514
  data?.label,
@@ -2460,10 +2520,9 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2460
2520
  data,
2461
2521
  className
2462
2522
  });
2463
- const standaloneScopeClass = useStandaloneScopeClass();
2464
2523
  if (!renderContainer) return content;
2465
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2466
- className: (0, clsx.default)(standaloneScopeClass, base_tooltip_module_default.tooltip),
2524
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTheme, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2525
+ className: (0, clsx.default)(CHART_SCOPE_CLASS, base_tooltip_module_default.tooltip),
2467
2526
  style: {
2468
2527
  top,
2469
2528
  left,
@@ -2471,7 +2530,7 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2471
2530
  },
2472
2531
  role: "tooltip",
2473
2532
  children: content
2474
- });
2533
+ }) });
2475
2534
  };
2476
2535
  //#endregion
2477
2536
  //#region src/components/tooltip/private/bounded-tooltip.tsx
@@ -2486,6 +2545,11 @@ const isClipping = (element) => {
2486
2545
  overflowY
2487
2546
  ].some((value) => value && value !== "visible");
2488
2547
  };
2548
+ const findLayoutParent = (node) => {
2549
+ let element = node.parentElement;
2550
+ while (element && getComputedStyle(element).display === "contents") element = element.parentElement;
2551
+ return element;
2552
+ };
2489
2553
  const findClippingAncestor = (wrapper) => {
2490
2554
  let element = wrapper.parentElement;
2491
2555
  while (element && element !== document.body) {
@@ -2560,19 +2624,20 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2560
2624
  * @param props.top - Anchor y, in wrapper coordinates.
2561
2625
  * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2562
2626
  * @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.
2627
+ * @param props.style - Inline overrides on the surface; ignored when `unstyled`.
2628
+ * @param props.unstyled - Drop the surface and `style`, leaving the box bare.
2629
+ * @param props.className - Extra classes beside the surface.
2565
2630
  * @param props.children - Box content.
2566
2631
  * @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2567
2632
  * @return The tooltip box.
2568
2633
  */
2569
- const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2634
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style, unstyled = false, className, children, placement = "auto", ...rest }) => {
2570
2635
  const nodeRef = (0, react.useRef)(null);
2571
2636
  const clipRef = (0, react.useRef)(null);
2572
2637
  const [position, setPosition] = (0, react.useState)(null);
2573
2638
  (0, react.useLayoutEffect)(() => {
2574
2639
  const node = nodeRef.current;
2575
- const wrapper = node?.parentElement;
2640
+ const wrapper = node && findLayoutParent(node);
2576
2641
  if (!node || !wrapper) return;
2577
2642
  if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2578
2643
  wrapper,
@@ -2620,8 +2685,9 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2620
2685
  ]);
2621
2686
  const x = position?.x ?? left + offsetLeft;
2622
2687
  const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2623
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_tooltip.Tooltip, {
2688
+ const box = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_tooltip.Tooltip, {
2624
2689
  ref: nodeRef,
2690
+ className: (0, clsx.default)(!unstyled && ["a8c-charts-tooltip-scope", base_tooltip_module_default.surface], className),
2625
2691
  style: {
2626
2692
  position: "absolute",
2627
2693
  left: 0,
@@ -2637,13 +2703,14 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2637
2703
  left: left - x - POINTER_HEIGHT,
2638
2704
  top: -6,
2639
2705
  width: 12,
2640
- height: POINTER_HEIGHT,
2706
+ height: 7,
2641
2707
  background: "inherit",
2642
- clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
2708
+ clipPath: `polygon(50% 0, 100% ${POINTER_HEIGHT}px, 100% 100%, 0 100%, 0 ${POINTER_HEIGHT}px)`,
2643
2709
  pointerEvents: "none"
2644
2710
  }
2645
2711
  }), children]
2646
2712
  });
2713
+ return unstyled ? box : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTheme, { children: box });
2647
2714
  };
2648
2715
  //#endregion
2649
2716
  //#region src/components/tooltip/xy-chart-tooltip.tsx
@@ -2750,13 +2817,20 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2750
2817
  const marginLeft = margin?.left ?? 0;
2751
2818
  const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : _visx_tooltip.Tooltip;
2752
2819
  const boxStyle = {
2753
- ..._visx_tooltip.defaultStyles,
2754
2820
  zIndex,
2755
- backgroundColor: theme?.backgroundColor ?? "white",
2756
- boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2757
- ...theme?.htmlLabel,
2758
2821
  ...style
2759
2822
  };
2823
+ const themesOwnBox = TooltipComponent === _visx_tooltip.Tooltip && !tooltipProps.unstyled;
2824
+ const box = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2825
+ left: tooltipLeft,
2826
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
2827
+ style: boxStyle,
2828
+ applyPositionStyle: true,
2829
+ ...tooltipProps,
2830
+ className: (0, clsx.default)(themesOwnBox && ["a8c-charts-scope", base_tooltip_module_default.surface], tooltipProps.className),
2831
+ ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2832
+ children: tooltipContent
2833
+ });
2760
2834
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2761
2835
  className: "visx-tooltip-overlay",
2762
2836
  pointerEvents: "none",
@@ -2783,15 +2857,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2783
2857
  }),
2784
2858
  glyphs
2785
2859
  ]
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)] });
2860
+ }), container && (0, react_dom.createPortal)(themesOwnBox ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTheme, { children: box }) : box, container)] });
2795
2861
  };
2796
2862
  /**
2797
2863
  * In-tree replacement for `@visx/xychart`'s `Tooltip`.
@@ -3058,7 +3124,6 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
3058
3124
  };
3059
3125
  //#endregion
3060
3126
  //#region src/providers/chart-context/hooks/use-default-hidden-series.ts
3061
- const useIsomorphicLayoutEffect$1 = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
3062
3127
  /**
3063
3128
  * Seeds a chart's hidden series once per chart ID, the first time the provider
3064
3129
  * sees that ID.
@@ -3079,7 +3144,7 @@ const useDefaultHiddenSeries = (chartId, seriesLabels) => {
3079
3144
  labelsRef.current = seriesLabels;
3080
3145
  const hasSeriesLabels = seriesLabels !== void 0;
3081
3146
  const shouldUseDefaults = hasSeriesLabels && !hasSeededChart(chartId);
3082
- useIsomorphicLayoutEffect$1(() => {
3147
+ useIsomorphicLayoutEffect(() => {
3083
3148
  if (labelsRef.current === void 0) return;
3084
3149
  seedChartHiddenSeries(chartId, labelsRef.current);
3085
3150
  }, [
@@ -3704,7 +3769,6 @@ var with_responsive_module_default = {
3704
3769
  };
3705
3770
  //#endregion
3706
3771
  //#region src/charts/private/with-responsive/with-responsive.tsx
3707
- const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
3708
3772
  /**
3709
3773
  * A higher-order component that provides responsive dimensions
3710
3774
  * to the wrapped chart component using useParentSize from `@visx/responsive`.
@@ -3988,10 +4052,8 @@ var line_chart_module_default = {
3988
4052
  "line-chart__annotation-label-popover-content": "a8ccharts-inuQka-line-chart__annotation-label-popover-content",
3989
4053
  "line-chart__annotation-label-trigger-button": "a8ccharts-inuQka-line-chart__annotation-label-trigger-button",
3990
4054
  "line-chart__annotations-overlay": "a8ccharts-inuQka-line-chart__annotations-overlay",
3991
- "line-chart__tooltip": "a8ccharts-inuQka-line-chart__tooltip",
3992
4055
  "line-chart__tooltip-date": "a8ccharts-inuQka-line-chart__tooltip-date",
3993
4056
  "line-chart__tooltip-label": "a8ccharts-inuQka-line-chart__tooltip-label",
3994
- "line-chart__tooltip-row": "a8ccharts-inuQka-line-chart__tooltip-row",
3995
4057
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
3996
4058
  "rise": "a8ccharts-inuQka-rise"
3997
4059
  };
@@ -4518,11 +4580,10 @@ const TooltipDate = ({ date, displayResolution }) => {
4518
4580
  * one row per visible series (label + formatted value), sorted descending by
4519
4581
  * value. Reused by AreaChart, which has the same multi-series shape.
4520
4582
  *
4521
- * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4522
- * @param contentStyle - Explicit tooltip content color overrides.
4583
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4523
4584
  * @return Tooltip JSX, or `null` when no datum is hovered.
4524
4585
  */
4525
- const renderDefaultTooltip = (params, contentStyle) => {
4586
+ const renderDefaultTooltip = (params) => {
4526
4587
  const { tooltipData, bucketInfo } = params;
4527
4588
  const nearestDatum = tooltipData?.nearestDatum?.datum;
4528
4589
  if (!nearestDatum) return null;
@@ -4535,29 +4596,24 @@ const renderDefaultTooltip = (params, contentStyle) => {
4535
4596
  if (b.value === null) return -1;
4536
4597
  return b.value - a.value;
4537
4598
  });
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
- });
4599
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4600
+ className: line_chart_module_default["line-chart__tooltip-date"],
4601
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
4602
+ date: nearestDatum.date,
4603
+ displayResolution: bucketInfo?.displayResolution ?? "day"
4604
+ })
4605
+ }), tooltipPoints.map((point) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
4606
+ direction: "row",
4607
+ align: "center",
4608
+ justify: "space-between",
4609
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4610
+ className: line_chart_module_default["line-chart__tooltip-label"],
4611
+ children: [point.key, ":"]
4612
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4613
+ className: line_chart_module_default["line-chart__tooltip-value"],
4614
+ children: formatReading(point.value)
4615
+ })]
4616
+ }, point.key))] });
4561
4617
  };
4562
4618
  const validateData$4 = (data) => {
4563
4619
  if (!data?.length) return (0, _wordpress_i18n.__)("No data available", "jetpack-charts");
@@ -4817,29 +4873,10 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4817
4873
  xAccessor: (d) => d?.date,
4818
4874
  yAccessor: (d) => d?.value
4819
4875
  };
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({
4876
+ const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip({
4836
4877
  ...params,
4837
4878
  bucketInfo
4838
- }), [
4839
- renderTooltip,
4840
- bucketInfo,
4841
- tooltipStyle
4842
- ]);
4879
+ }), [renderTooltip, bucketInfo]);
4843
4880
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4844
4881
  className: (0, clsx.default)("line-chart", line_chart_module_default["line-chart"]),
4845
4882
  children: error
@@ -4987,7 +5024,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4987
5024
  detectBounds: true,
4988
5025
  snapTooltipToDatumX: true,
4989
5026
  tooltipPlacement,
4990
- style: resolvedTooltipStyle,
5027
+ style: tooltipStyle,
4991
5028
  snapTooltipToDatumY: true,
4992
5029
  showSeriesGlyphs: true,
4993
5030
  renderTooltip: tooltipRenderer,
@@ -6895,8 +6932,7 @@ var conversion_funnel_chart_module_default = {
6895
6932
  "step-rate": "a8ccharts-mGEVca-step-rate",
6896
6933
  "stretch": "a8ccharts-mGEVca-stretch",
6897
6934
  "tooltip-content": "a8ccharts-mGEVca-tooltip-content",
6898
- "tooltip-title": "a8ccharts-mGEVca-tooltip-title",
6899
- "tooltip-wrapper": "a8ccharts-mGEVca-tooltip-wrapper"
6935
+ "tooltip-title": "a8ccharts-mGEVca-tooltip-title"
6900
6936
  };
6901
6937
  //#endregion
6902
6938
  //#region src/charts/conversion-funnel-chart/private/use-funnel-selection.ts
@@ -6957,9 +6993,10 @@ const useFunnelSelection = (hideTooltip) => {
6957
6993
  * @param props.renderStepRate - Custom render function for step rates
6958
6994
  * @param props.renderMainMetric - Custom render function for the entire main metric section
6959
6995
  * @param props.renderTooltip - Custom render function for tooltip content
6996
+ * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
6960
6997
  * @return JSX element representing the conversion funnel chart
6961
6998
  */
6962
- const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
6999
+ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
6963
7000
  const chartId = useChartId(providedChartId);
6964
7001
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
6965
7002
  const chartRef = (0, react.useRef)(null);
@@ -7194,14 +7231,14 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7194
7231
  step: tooltipData,
7195
7232
  index: steps.findIndex((s) => s.id === tooltipData.id),
7196
7233
  top: tooltipTop,
7197
- left: tooltipLeft,
7198
- className: conversion_funnel_chart_module_default["tooltip-wrapper"]
7234
+ left: tooltipLeft
7199
7235
  }) : renderDefaultTooltip(tooltipData);
7200
7236
  if (!tooltipContent) return null;
7201
7237
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
7202
7238
  top: tooltipTop,
7203
7239
  left: tooltipLeft,
7204
- className: (0, clsx.default)(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
7240
+ className: standaloneScopeClass,
7241
+ style: tooltipStyle,
7205
7242
  children: tooltipContent
7206
7243
  });
7207
7244
  })()]
@@ -7345,13 +7382,14 @@ function normalizeGeoChartError(eventArgs) {
7345
7382
  * @param props.height - Height of the chart in pixels
7346
7383
  * @param props.region - Region to display ('world', 'US', or ISO 3166-1 alpha-2 code)
7347
7384
  * @param props.resolution - Resolution level ('countries', 'provinces', or 'metros')
7385
+ * @param props.displayMode - Plot rows as shaded areas ('regions') or markers ('markers')
7348
7386
  * @param props.domain - Country code whose viewpoint sets the disputed borders
7349
7387
  * @param props.onError - Optional callback for Google Charts errors
7350
7388
  * @param props.className - Additional CSS class name for the chart container
7351
7389
  * @param props.renderPlaceholder - Optional render function for the loading placeholder
7352
7390
  * @return A React component displaying an interactive map with data visualization
7353
7391
  */
7354
- const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", domain, onError, renderPlaceholder }) => {
7392
+ const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", displayMode = "regions", domain, onError, renderPlaceholder }) => {
7355
7393
  const { getElementStyles } = useGlobalChartsContext();
7356
7394
  const scopeElement = useChartScopeElement();
7357
7395
  const containerRef = (0, react.useRef)(null);
@@ -7415,6 +7453,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7415
7453
  const options = (0, react.useMemo)(() => ({
7416
7454
  ...region !== "world" && { region },
7417
7455
  ...resolution !== "countries" && { resolution },
7456
+ ...displayMode !== "regions" && { displayMode },
7418
7457
  ...domain && { domain },
7419
7458
  colorAxis: { colors: [lightColorHex, fullColorHex] },
7420
7459
  backgroundColor: backgroundColorHex,
@@ -7429,6 +7468,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
7429
7468
  }), [
7430
7469
  region,
7431
7470
  resolution,
7471
+ displayMode,
7432
7472
  domain,
7433
7473
  lightColorHex,
7434
7474
  fullColorHex,
@@ -7484,6 +7524,73 @@ let GoogleDataTableColumnRoleType = /* @__PURE__ */ function(GoogleDataTableColu
7484
7524
  GoogleDataTableColumnRoleType["domain"] = "domain";
7485
7525
  return GoogleDataTableColumnRoleType;
7486
7526
  }({});
7527
+ const resolveRole = (pointer, resolve) => {
7528
+ const raw = resolve(pointer);
7529
+ const parsed = raw ? (0, _visx_vendor_d3_color.color)(raw) : null;
7530
+ if (!parsed) return {
7531
+ kind: "unreadable",
7532
+ raw
7533
+ };
7534
+ const { r, g, b } = parsed.opacity > 0 ? parsed.rgb() : {
7535
+ r: 0,
7536
+ g: 0,
7537
+ b: 0
7538
+ };
7539
+ return {
7540
+ kind: "color",
7541
+ rgb: [
7542
+ r,
7543
+ g,
7544
+ b
7545
+ ],
7546
+ alpha: parsed.opacity
7547
+ };
7548
+ };
7549
+ /**
7550
+ * Reads both label roles at the element the label text inherits from, so JS and CSS agree on what paints.
7551
+ *
7552
+ * @param resolve - A CSS variable resolver bound to that element.
7553
+ * @return The resolved label roles.
7554
+ */
7555
+ const resolveLabelRoles = (resolve) => ({
7556
+ label: resolveRole(CATALOG_POINTERS.label, resolve),
7557
+ labelInverse: resolveRole(CATALOG_POINTERS.labelInverse, resolve)
7558
+ });
7559
+ const roleContrast = (role, fill, fillLuminance) => {
7560
+ if (role.kind === "unreadable") return 0;
7561
+ return luminanceContrastRatio(fillLuminance, rgbLuminance(blendRgb(role.rgb, fill, role.alpha)));
7562
+ };
7563
+ /**
7564
+ * Picks the label color for text drawn on a fill: the role that contrasts more, or black/white when neither reaches AA.
7565
+ *
7566
+ * Both roles set to one color is a host's choice to keep that color, so it never falls back.
7567
+ *
7568
+ * @param fill - The fill the text sits on, as unrounded sRGB channels.
7569
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7570
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7571
+ * @return The color the label should paint with.
7572
+ */
7573
+ const pickLabelTextColorForFill = (fill, roles, defaultRole) => {
7574
+ if (!roles || (0, import_fast_deep_equal.default)(roles.label, roles.labelInverse)) return defaultRole;
7575
+ const fillLuminance = rgbLuminance(fill);
7576
+ const labelContrast = roleContrast(roles.label, fill, fillLuminance);
7577
+ const inverseContrast = roleContrast(roles.labelInverse, fill, fillLuminance);
7578
+ if (Math.max(labelContrast, inverseContrast) >= 4.55) return labelContrast > inverseContrast ? "label" : "label-inverse";
7579
+ return luminanceContrastRatio(fillLuminance, 0) > luminanceContrastRatio(fillLuminance, 1) ? "black" : "white";
7580
+ };
7581
+ /**
7582
+ * `pickLabelTextColorForFill` for a fill given as a color.
7583
+ *
7584
+ * @param fill - The fill, in any color syntax `normalizeColorToHex` reads.
7585
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7586
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7587
+ * @return The color the label should paint with.
7588
+ */
7589
+ const pickLabelTextColor = (fill, roles, defaultRole) => {
7590
+ const fillHex = normalizeColorToHex(fill);
7591
+ if (!isValidHexColor(fillHex)) return defaultRole;
7592
+ return pickLabelTextColorForFill(hexToRgb(fillHex), roles, defaultRole);
7593
+ };
7487
7594
  //#endregion
7488
7595
  //#region src/charts/heatmap-chart/heatmap-chart.module.scss
7489
7596
  var heatmap_chart_module_default = {
@@ -7493,9 +7600,11 @@ var heatmap_chart_module_default = {
7493
7600
  "heatmap-chart__cell--hidden": "a8ccharts-O3YMOW-heatmap-chart__cell--hidden",
7494
7601
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
7495
7602
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
7496
- "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
7497
7603
  "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
7498
7604
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
7605
+ "heatmap-chart__cell-value--black": "a8ccharts-O3YMOW-heatmap-chart__cell-value--black",
7606
+ "heatmap-chart__cell-value--inverse": "a8ccharts-O3YMOW-heatmap-chart__cell-value--inverse",
7607
+ "heatmap-chart__cell-value--white": "a8ccharts-O3YMOW-heatmap-chart__cell-value--white",
7499
7608
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
7500
7609
  "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
7501
7610
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
@@ -7789,25 +7898,25 @@ const stepCalendarCell = (grid, blocks, from, key) => {
7789
7898
  //#endregion
7790
7899
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
7791
7900
  const CELL_MIX_FLOOR = .15;
7792
- const NO_ROW_LABELS = [];
7793
- const TOOLTIP_BOX_STYLES = {
7794
- light: {
7795
- ..._visx_tooltip.defaultStyles,
7796
- zIndex: 3
7797
- },
7798
- dark: { zIndex: 3 }
7901
+ const CELL_VALUE_MODIFIER = {
7902
+ label: void 0,
7903
+ "label-inverse": heatmap_chart_module_default["heatmap-chart__cell-value--inverse"],
7904
+ black: heatmap_chart_module_default["heatmap-chart__cell-value--black"],
7905
+ white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
7799
7906
  };
7907
+ const NO_ROW_LABELS = [];
7908
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
7800
7909
  const cellName = (info) => info.cellLabel || [
7801
7910
  info.groupLabel,
7802
7911
  info.columnLabel,
7803
7912
  info.rowLabel
7804
7913
  ].filter(Boolean).join(" ");
7805
- 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 }) => {
7914
+ 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 }) => {
7806
7915
  const chartId = useChartId(providedChartId);
7807
7916
  const tooltipBoxStyle = tooltipStyle ? {
7808
- ...TOOLTIP_BOX_STYLES[tooltipVariant],
7917
+ ...TOOLTIP_BOX_STYLE,
7809
7918
  ...tooltipStyle
7810
- } : TOOLTIP_BOX_STYLES[tooltipVariant];
7919
+ } : TOOLTIP_BOX_STYLE;
7811
7920
  const { getElementStyles, theme } = useGlobalChartsContext();
7812
7921
  const scopeElement = useChartScopeElement();
7813
7922
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -7816,6 +7925,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7816
7925
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
7817
7926
  const standaloneScopeClass = useStandaloneScopeClass();
7818
7927
  const containerRef = (0, react.useRef)(null);
7928
+ const [labelRoles, setLabelRoles] = (0, react.useState)(null);
7819
7929
  const getTooltipOrigin = (0, react.useCallback)(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
7820
7930
  const { color: primaryColorHex } = getElementStyles({
7821
7931
  index: 0,
@@ -7823,7 +7933,21 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7823
7933
  });
7824
7934
  const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
7825
7935
  const primaryHex = normalizeColorToHex(primaryColorHex);
7826
- const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
7936
+ const cellMix = isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) ? {
7937
+ primary: hexToRgb(primaryHex),
7938
+ background: hexToRgb(chartBackgroundHex)
7939
+ } : null;
7940
+ const cellTextColor = (intensity) => cellMix ? pickLabelTextColorForFill(blendRgb(cellMix.primary, cellMix.background, CELL_MIX_FLOOR + .85 * intensity), labelRoles, "label") : "label";
7941
+ useIsomorphicLayoutEffect(() => {
7942
+ if (!containerRef.current) return;
7943
+ const next = resolveLabelRoles(createCssVariableResolver(containerRef.current));
7944
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
7945
+ }, [
7946
+ data,
7947
+ className,
7948
+ primaryColorHex,
7949
+ chartBackgroundHex
7950
+ ]);
7827
7951
  const extent = (0, react.useMemo)(() => getValueExtent(data), [data]);
7828
7952
  const heatmapContext = (0, react.useMemo)(() => ({
7829
7953
  extent,
@@ -8095,7 +8219,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8095
8219
  "data-row": rowIndex,
8096
8220
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
8097
8221
  [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
8098
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
8099
8222
  [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
8100
8223
  ...summaryGaps(columnIndex),
8101
8224
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
@@ -8107,7 +8230,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8107
8230
  onMouseMove: handleCellMouseMove,
8108
8231
  onMouseLeave: handleCellMouseLeave,
8109
8232
  children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8110
- className: heatmap_chart_module_default["heatmap-chart__cell-value"],
8233
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell-value"], filled && CELL_VALUE_MODIFIER[cellTextColor(normalized)]),
8111
8234
  children: (0, _automattic_number_formatters.formatNumberCompact)(value)
8112
8235
  })
8113
8236
  }, `cell-${columnIndex}-${rowIndex}`);
@@ -8131,7 +8254,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8131
8254
  }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
8132
8255
  top: tooltipTop,
8133
8256
  left: tooltipLeft,
8134
- className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
8135
8257
  style: tooltipBoxStyle,
8136
8258
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8137
8259
  className: standaloneScopeClass,
@@ -11723,7 +11845,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
11723
11845
  var pie_chart_module_default = {
11724
11846
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
11725
11847
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
11848
+ "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",
11726
11849
  "pie-chart__label-text--on-light": "a8ccharts-gnszbG-pie-chart__label-text--on-light",
11850
+ "pie-chart__label-text--white": "a8ccharts-gnszbG-pie-chart__label-text--white",
11727
11851
  "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
11728
11852
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
11729
11853
  };
@@ -11767,24 +11891,11 @@ const validateData$1 = (data) => {
11767
11891
  message: ""
11768
11892
  };
11769
11893
  };
11770
- /**
11771
- * Whether a pie slice's label should use the dark `label` role instead of the default `label-inverse` role.
11772
- *
11773
- * A label plate always wins: the text sits on the plate, not the slice fill, so it never flips.
11774
- * Otherwise the role that contrasts more with the resolved slice fill wins. `null` pointers (not
11775
- * yet resolved, or labels off) keep the default inverse role.
11776
- *
11777
- * @param fill - The slice's resolved fill color.
11778
- * @param pointers - The label pointers, resolved at the chart's own root element.
11779
- * @return Whether the label needs dark text.
11780
- */
11781
- const labelNeedsDarkText = (fill, pointers) => {
11782
- if (!pointers || pointers.hasPlate) return false;
11783
- const fillHex = normalizeColorToHex(fill);
11784
- if (!isValidHexColor(fillHex) || !isValidHexColor(pointers.labelHex)) return false;
11785
- if (pointers.isInverseSeeThrough) return true;
11786
- if (!isValidHexColor(pointers.labelInverseHex)) return false;
11787
- return contrastRatio(fillHex, pointers.labelHex) > contrastRatio(fillHex, pointers.labelInverseHex);
11894
+ const LABEL_TEXT_MODIFIER = {
11895
+ label: pie_chart_module_default["pie-chart__label-text--on-light"],
11896
+ "label-inverse": void 0,
11897
+ black: pie_chart_module_default["pie-chart__label-text--black"],
11898
+ white: pie_chart_module_default["pie-chart__label-text--white"]
11788
11899
  };
11789
11900
  /**
11790
11901
  * Renders a pie or donut chart using the provided data.
@@ -11801,7 +11912,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11801
11912
  const standaloneScopeClass = useStandaloneScopeClass();
11802
11913
  const containerRef = (0, react.useRef)(null);
11803
11914
  const rootRef = (0, react.useRef)(null);
11804
- const [labelPointers, setLabelPointers] = (0, react.useState)(null);
11915
+ const [labelRoles, setLabelRoles] = (0, react.useState)(null);
11805
11916
  const onMouseLeave = (0, react.useCallback)(() => {
11806
11917
  if (!withTooltips) return;
11807
11918
  hideTooltip();
@@ -11812,18 +11923,9 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11812
11923
  if (!showLabels || !rootRef.current) return;
11813
11924
  const resolve = createCssVariableResolver(rootRef.current);
11814
11925
  const rawLabelBackground = resolve(CATALOG_POINTERS.labelBackground);
11815
- const rawLabel = resolve(CATALOG_POINTERS.label);
11816
- const isLabelOpaque = rawLabel ? (0, _visx_vendor_d3_color.color)(rawLabel)?.opacity === 1 : false;
11817
- const rawLabelInverse = resolve(CATALOG_POINTERS.labelInverse);
11818
- const labelInverseColor = rawLabelInverse ? (0, _visx_vendor_d3_color.color)(rawLabelInverse) : null;
11819
11926
  const plateColor = rawLabelBackground ? (0, _visx_vendor_d3_color.color)(rawLabelBackground) : null;
11820
- const next = {
11821
- hasPlate: rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false,
11822
- labelHex: isLabelOpaque ? normalizeColorToHex(CATALOG_POINTERS.label, null, resolve) : "",
11823
- labelInverseHex: normalizeColorToHex(CATALOG_POINTERS.labelInverse, null, resolve),
11824
- isInverseSeeThrough: labelInverseColor ? labelInverseColor.opacity < 1 : false
11825
- };
11826
- setLabelPointers((previous) => previous && previous.hasPlate === next.hasPlate && previous.labelHex === next.labelHex && previous.labelInverseHex === next.labelInverseHex && previous.isInverseSeeThrough === next.isInverseSeeThrough ? previous : next);
11927
+ const next = (rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false) ? null : resolveLabelRoles(resolve);
11928
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
11827
11929
  }, [
11828
11930
  showLabels,
11829
11931
  className,
@@ -11994,7 +12096,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11994
12096
  ry: 4,
11995
12097
  pointerEvents: "none"
11996
12098
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
11997
- 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) }),
12099
+ className: (0, clsx.default)(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
11998
12100
  x: centroidX,
11999
12101
  y: centroidY,
12000
12102
  dy: ".33em",