@automattic/charts 4.6.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 (57) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/TOKENS.md +12 -10
  3. package/dist/index.cjs +244 -145
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +33 -20
  6. package/dist/index.d.cts +10 -10
  7. package/dist/index.d.ts +10 -10
  8. package/dist/index.js +245 -146
  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/heatmap-chart/heatmap-chart.module.scss +10 -3
  16. package/src/charts/heatmap-chart/heatmap-chart.tsx +54 -37
  17. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +129 -25
  18. package/src/charts/heatmap-chart/types.ts +2 -6
  19. package/src/charts/line-chart/crosshair-style-types.fixture.ts +23 -0
  20. package/src/charts/line-chart/line-chart.module.scss +0 -5
  21. package/src/charts/line-chart/line-chart.tsx +8 -40
  22. package/src/charts/line-chart/test/line-chart.test.tsx +19 -47
  23. package/src/charts/pie-chart/pie-chart.module.scss +9 -0
  24. package/src/charts/pie-chart/pie-chart.tsx +22 -76
  25. package/src/charts/pie-chart/test/pie-chart.test.tsx +21 -8
  26. package/src/charts/private/chart-composition/chart-html.tsx +1 -1
  27. package/src/charts/private/chart-composition/chart-svg.tsx +1 -1
  28. package/src/charts/private/label-text-color.ts +116 -0
  29. package/src/charts/private/radial-wipe-animation/radial-wipe-animation.tsx +2 -0
  30. package/src/charts/private/svg-empty-state/svg-empty-state.tsx +1 -1
  31. package/src/charts/private/test/label-text-color.test.ts +191 -0
  32. package/src/charts/private/with-responsive/test/with-responsive.test.tsx +1 -0
  33. package/src/charts/private/with-responsive/with-responsive.tsx +2 -5
  34. package/src/components/tooltip/accessible-tooltip.tsx +1 -1
  35. package/src/components/tooltip/base-tooltip.module.scss +12 -8
  36. package/src/components/tooltip/base-tooltip.tsx +11 -9
  37. package/src/components/tooltip/private/bounded-tooltip.tsx +27 -8
  38. package/src/components/tooltip/private/tooltip-theme.tsx +37 -0
  39. package/src/components/tooltip/test/bounded-tooltip.test.tsx +82 -1
  40. package/src/components/tooltip/test/tool-tip.test.tsx +5 -0
  41. package/src/components/tooltip/test/tooltip-theme.test.tsx +61 -0
  42. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +41 -28
  43. package/src/components/tooltip/xy-chart-tooltip.tsx +29 -29
  44. package/src/components/trend-indicator/trend-indicator.tsx +1 -0
  45. package/src/hooks/index.ts +1 -0
  46. package/src/hooks/test/use-xychart-theme.test.tsx +3 -3
  47. package/src/hooks/use-isomorphic-layout-effect.ts +5 -0
  48. package/src/hooks/use-xychart-theme.ts +4 -12
  49. package/src/providers/chart-context/hooks/use-default-hidden-series.ts +2 -4
  50. package/src/providers/chart-context/private/perceptual-color.ts +45 -4
  51. package/src/providers/chart-context/test/perceptual-color.test.ts +31 -0
  52. package/src/styles/chart-scope-class.ts +5 -0
  53. package/src/styles/chart-scope.scss +9 -6
  54. package/src/styles/test/chart-scope.test.ts +3 -3
  55. package/src/utils/color-utils.ts +2 -1
  56. package/src/visx/types.ts +1 -1
  57. package/src/charts/line-chart/test/crosshair-style-types.test.ts +0 -31
package/dist/index.js CHANGED
@@ -11,11 +11,12 @@ import { tzOffset } from "@date-fns/tz";
11
11
  import { Text, getStringWidth } from "@visx/text";
12
12
  import deepmerge from "deepmerge";
13
13
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
- import { Tooltip, defaultStyles, useTooltip } from "@visx/tooltip";
14
+ import { Tooltip, 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) => {
@@ -879,14 +881,27 @@ const PROTANOPIA = [
879
881
  const DEGREES = Math.PI / 180;
880
882
  const toLinear = (channel) => channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
881
883
  const fromLinear = (channel) => channel <= .0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - .055;
882
- const hexToLinear = (hex) => {
884
+ /**
885
+ * The sRGB channels of a hex color.
886
+ *
887
+ * @param hex - A six-digit hex color.
888
+ * @return Channels from 0 to 255.
889
+ * @throws {Error} if the hex string is malformed
890
+ */
891
+ const hexToRgb = (hex) => {
883
892
  validateHexColor(hex);
884
- return [
893
+ const [r, g, b] = [
885
894
  1,
886
895
  3,
887
896
  5
888
- ].map((start) => toLinear(parseInt(hex.slice(start, start + 2), 16) / 255));
897
+ ].map((start) => parseInt(hex.slice(start, start + 2), 16));
898
+ return [
899
+ r,
900
+ g,
901
+ b
902
+ ];
889
903
  };
904
+ const hexToLinear = (hex) => hexToRgb(hex).map((channel) => toLinear(channel / 255));
890
905
  const linearToHex = (linear) => "#" + linear.map((channel) => Math.round(Math.min(1, Math.max(0, fromLinear(channel))) * 255).toString(16).padStart(2, "0")).join("");
891
906
  const simulate = (matrix, [r, g, b]) => [
892
907
  0,
@@ -1012,6 +1027,35 @@ const hexToOklch = (hex) => {
1012
1027
  };
1013
1028
  };
1014
1029
  /**
1030
+ * `foreground` over `background` at `weight`, left unrounded as CSS `color-mix` and alpha compositing leave it.
1031
+ *
1032
+ * A mix rounded to hex can land on the other side of a contrast threshold from the fill the browser paints.
1033
+ *
1034
+ * @param foreground - The color weighted by `weight`.
1035
+ * @param background - The color it is mixed over.
1036
+ * @param weight - Share of `foreground`, from 0 to 1.
1037
+ * @return The mixed channels.
1038
+ */
1039
+ const blendRgb = (foreground, background, weight) => {
1040
+ const share = Math.min(1, Math.max(0, weight));
1041
+ const [r, g, b] = foreground.map((channel, i) => background[i] + (channel - background[i]) * share);
1042
+ return [
1043
+ r,
1044
+ g,
1045
+ b
1046
+ ];
1047
+ };
1048
+ /**
1049
+ * WCAG relative luminance of unrounded sRGB channels.
1050
+ *
1051
+ * @param rgb - Channels from 0 to 255.
1052
+ * @return Relative luminance from 0 to 1.
1053
+ */
1054
+ const rgbLuminance = (rgb) => {
1055
+ const [r, g, b] = rgb.map((channel) => toLinear(channel / 255));
1056
+ return .2126 * r + .7152 * g + .0722 * b;
1057
+ };
1058
+ /**
1015
1059
  * WCAG contrast ratio between two relative luminances.
1016
1060
  *
1017
1061
  * @param first - First relative luminance.
@@ -1019,15 +1063,6 @@ const hexToOklch = (hex) => {
1019
1063
  * @return Ratio from 1 to 21.
1020
1064
  */
1021
1065
  const luminanceContrastRatio = (first, second) => (Math.max(first, second) + .05) / (Math.min(first, second) + .05);
1022
- /**
1023
- * WCAG contrast ratio between two hex colors.
1024
- *
1025
- * @param first - First hex color.
1026
- * @param second - Second hex color.
1027
- * @return Ratio from 1 to 21.
1028
- * @throws {Error} if either hex string is malformed
1029
- */
1030
- const contrastRatio = (first, second) => luminanceContrastRatio(relativeLuminance(first), relativeLuminance(second));
1031
1066
  /** WCAG 1.4.3 text contrast, which a label drawn on a fill needs from at least one label color. */
1032
1067
  const MIN_LABEL_CONTRAST = 4.5;
1033
1068
  /** Degrees of hue one unit of OKLCH chroma is worth when ranking candidates by hue. */
@@ -1532,15 +1567,13 @@ const useXYChartTheme = (data) => {
1532
1567
  const resolve = createCssVariableResolver(scopeElement);
1533
1568
  const resolveColor = (value) => value ? resolve(value) ?? value : value;
1534
1569
  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
1570
  return buildChartTheme({
1538
1571
  ...theme,
1539
1572
  gridColor: "",
1540
1573
  gridColorDark: "",
1541
1574
  colors: paletteColors,
1542
1575
  backgroundColor: resolveColor(CATALOG_POINTERS.background),
1543
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1576
+ htmlLabel: { color: CATALOG_POINTERS.labelAxis },
1544
1577
  gridStyles: {
1545
1578
  ...theme.gridStyles,
1546
1579
  stroke: CATALOG_POINTERS.grid
@@ -1975,6 +2008,9 @@ function usePrefersReducedMotion() {
1975
2008
  return prefersReducedMotion;
1976
2009
  }
1977
2010
  //#endregion
2011
+ //#region src/hooks/use-isomorphic-layout-effect.ts
2012
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
2013
+ //#endregion
1978
2014
  //#region src/providers/chart-context/hooks/use-chart-registration.ts
1979
2015
  const useChartRegistration = ({ chartId, legendItems, chartType, isDataValid, metadata }) => {
1980
2016
  const { registerChart, unregisterChart } = useGlobalChartsContext();
@@ -2447,6 +2483,30 @@ var base_tooltip_module_default = {
2447
2483
  "tooltip": "a8ccharts--zY0xG-tooltip"
2448
2484
  };
2449
2485
  //#endregion
2486
+ //#region src/components/tooltip/private/tooltip-theme.tsx
2487
+ const DEFAULT_SURFACE = "#1e1e1e";
2488
+ const TOOLTIP_SURFACE = `var(--a8c-charts-color-tooltip-surface, ${DEFAULT_SURFACE})`;
2489
+ const toSeed = (value) => {
2490
+ const parsed = value ? color(value) : null;
2491
+ return parsed && parsed.opacity === 1 ? parsed.formatHex() : DEFAULT_SURFACE;
2492
+ };
2493
+ /**
2494
+ * 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.
2495
+ *
2496
+ * @param props - Component props.
2497
+ * @param props.children - The tooltip box.
2498
+ * @return The themed box.
2499
+ */
2500
+ const TooltipTheme = ({ children }) => {
2501
+ const scopeElement = useChartScopeElement();
2502
+ const color = useMemo(() => ({ background: toSeed(resolveCssVariable(TOOLTIP_SURFACE, scopeElement)) }), [scopeElement]);
2503
+ if (!ThemeProvider) return /* @__PURE__ */ jsx(Fragment$1, { children });
2504
+ return /* @__PURE__ */ jsx(ThemeProvider, {
2505
+ color,
2506
+ children
2507
+ });
2508
+ };
2509
+ //#endregion
2450
2510
  //#region src/components/tooltip/base-tooltip.tsx
2451
2511
  const DefaultTooltipContent = ({ data }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
2452
2512
  data?.label,
@@ -2458,10 +2518,9 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2458
2518
  data,
2459
2519
  className
2460
2520
  });
2461
- const standaloneScopeClass = useStandaloneScopeClass();
2462
2521
  if (!renderContainer) return content;
2463
- return /* @__PURE__ */ jsx("div", {
2464
- className: clsx(standaloneScopeClass, base_tooltip_module_default.tooltip),
2522
+ return /* @__PURE__ */ jsx(TooltipTheme, { children: /* @__PURE__ */ jsx("div", {
2523
+ className: clsx(CHART_SCOPE_CLASS, base_tooltip_module_default.tooltip),
2465
2524
  style: {
2466
2525
  top,
2467
2526
  left,
@@ -2469,7 +2528,7 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2469
2528
  },
2470
2529
  role: "tooltip",
2471
2530
  children: content
2472
- });
2531
+ }) });
2473
2532
  };
2474
2533
  //#endregion
2475
2534
  //#region src/components/tooltip/private/bounded-tooltip.tsx
@@ -2484,6 +2543,11 @@ const isClipping = (element) => {
2484
2543
  overflowY
2485
2544
  ].some((value) => value && value !== "visible");
2486
2545
  };
2546
+ const findLayoutParent = (node) => {
2547
+ let element = node.parentElement;
2548
+ while (element && getComputedStyle(element).display === "contents") element = element.parentElement;
2549
+ return element;
2550
+ };
2487
2551
  const findClippingAncestor = (wrapper) => {
2488
2552
  let element = wrapper.parentElement;
2489
2553
  while (element && element !== document.body) {
@@ -2558,19 +2622,20 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bo
2558
2622
  * @param props.top - Anchor y, in wrapper coordinates.
2559
2623
  * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2560
2624
  * @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.
2625
+ * @param props.style - Inline overrides on the surface; ignored when `unstyled`.
2626
+ * @param props.unstyled - Drop the surface and `style`, leaving the box bare.
2627
+ * @param props.className - Extra classes beside the surface.
2563
2628
  * @param props.children - Box content.
2564
2629
  * @param props.placement - Below-axis, beside without vertical flipping, or automatic flipping.
2565
2630
  * @return The tooltip box.
2566
2631
  */
2567
- const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2632
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style, unstyled = false, className, children, placement = "auto", ...rest }) => {
2568
2633
  const nodeRef = useRef(null);
2569
2634
  const clipRef = useRef(null);
2570
2635
  const [position, setPosition] = useState(null);
2571
2636
  useLayoutEffect(() => {
2572
2637
  const node = nodeRef.current;
2573
- const wrapper = node?.parentElement;
2638
+ const wrapper = node && findLayoutParent(node);
2574
2639
  if (!node || !wrapper) return;
2575
2640
  if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2576
2641
  wrapper,
@@ -2618,8 +2683,9 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2618
2683
  ]);
2619
2684
  const x = position?.x ?? left + offsetLeft;
2620
2685
  const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2621
- return /* @__PURE__ */ jsxs(Tooltip, {
2686
+ const box = /* @__PURE__ */ jsxs(Tooltip, {
2622
2687
  ref: nodeRef,
2688
+ className: clsx(!unstyled && ["a8c-charts-tooltip-scope", base_tooltip_module_default.surface], className),
2623
2689
  style: {
2624
2690
  position: "absolute",
2625
2691
  left: 0,
@@ -2635,13 +2701,14 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2635
2701
  left: left - x - POINTER_HEIGHT,
2636
2702
  top: -6,
2637
2703
  width: 12,
2638
- height: POINTER_HEIGHT,
2704
+ height: 7,
2639
2705
  background: "inherit",
2640
- clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
2706
+ clipPath: `polygon(50% 0, 100% ${POINTER_HEIGHT}px, 100% 100%, 0 100%, 0 ${POINTER_HEIGHT}px)`,
2641
2707
  pointerEvents: "none"
2642
2708
  }
2643
2709
  }), children]
2644
2710
  });
2711
+ return unstyled ? box : /* @__PURE__ */ jsx(TooltipTheme, { children: box });
2645
2712
  };
2646
2713
  //#endregion
2647
2714
  //#region src/components/tooltip/xy-chart-tooltip.tsx
@@ -2748,13 +2815,20 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2748
2815
  const marginLeft = margin?.left ?? 0;
2749
2816
  const TooltipComponent = detectBounds || tooltipPlacement !== "auto" ? BoundedTooltip : Tooltip;
2750
2817
  const boxStyle = {
2751
- ...defaultStyles,
2752
2818
  zIndex,
2753
- backgroundColor: theme?.backgroundColor ?? "white",
2754
- boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2755
- ...theme?.htmlLabel,
2756
2819
  ...style
2757
2820
  };
2821
+ const themesOwnBox = TooltipComponent === Tooltip && !tooltipProps.unstyled;
2822
+ const box = /* @__PURE__ */ jsx(TooltipComponent, {
2823
+ left: tooltipLeft,
2824
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipAnchorTop ?? tooltipTop,
2825
+ style: boxStyle,
2826
+ applyPositionStyle: true,
2827
+ ...tooltipProps,
2828
+ className: clsx(themesOwnBox && ["a8c-charts-scope", base_tooltip_module_default.surface], tooltipProps.className),
2829
+ ...tooltipPlacement !== "auto" && { placement: tooltipPlacement },
2830
+ children: tooltipContent
2831
+ });
2758
2832
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("g", {
2759
2833
  className: "visx-tooltip-overlay",
2760
2834
  pointerEvents: "none",
@@ -2781,15 +2855,7 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2781
2855
  }),
2782
2856
  glyphs
2783
2857
  ]
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)] });
2858
+ }), container && createPortal(themesOwnBox ? /* @__PURE__ */ jsx(TooltipTheme, { children: box }) : box, container)] });
2793
2859
  };
2794
2860
  /**
2795
2861
  * In-tree replacement for `@visx/xychart`'s `Tooltip`.
@@ -3056,7 +3122,6 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
3056
3122
  };
3057
3123
  //#endregion
3058
3124
  //#region src/providers/chart-context/hooks/use-default-hidden-series.ts
3059
- const useIsomorphicLayoutEffect$1 = typeof window !== "undefined" ? useLayoutEffect : useEffect;
3060
3125
  /**
3061
3126
  * Seeds a chart's hidden series once per chart ID, the first time the provider
3062
3127
  * sees that ID.
@@ -3077,7 +3142,7 @@ const useDefaultHiddenSeries = (chartId, seriesLabels) => {
3077
3142
  labelsRef.current = seriesLabels;
3078
3143
  const hasSeriesLabels = seriesLabels !== void 0;
3079
3144
  const shouldUseDefaults = hasSeriesLabels && !hasSeededChart(chartId);
3080
- useIsomorphicLayoutEffect$1(() => {
3145
+ useIsomorphicLayoutEffect(() => {
3081
3146
  if (labelsRef.current === void 0) return;
3082
3147
  seedChartHiddenSeries(chartId, labelsRef.current);
3083
3148
  }, [
@@ -3702,7 +3767,6 @@ var with_responsive_module_default = {
3702
3767
  };
3703
3768
  //#endregion
3704
3769
  //#region src/charts/private/with-responsive/with-responsive.tsx
3705
- const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
3706
3770
  /**
3707
3771
  * A higher-order component that provides responsive dimensions
3708
3772
  * to the wrapped chart component using useParentSize from `@visx/responsive`.
@@ -3986,10 +4050,8 @@ var line_chart_module_default = {
3986
4050
  "line-chart__annotation-label-popover-content": "a8ccharts-inuQka-line-chart__annotation-label-popover-content",
3987
4051
  "line-chart__annotation-label-trigger-button": "a8ccharts-inuQka-line-chart__annotation-label-trigger-button",
3988
4052
  "line-chart__annotations-overlay": "a8ccharts-inuQka-line-chart__annotations-overlay",
3989
- "line-chart__tooltip": "a8ccharts-inuQka-line-chart__tooltip",
3990
4053
  "line-chart__tooltip-date": "a8ccharts-inuQka-line-chart__tooltip-date",
3991
4054
  "line-chart__tooltip-label": "a8ccharts-inuQka-line-chart__tooltip-label",
3992
- "line-chart__tooltip-row": "a8ccharts-inuQka-line-chart__tooltip-row",
3993
4055
  "line-chart--animated": "a8ccharts-inuQka-line-chart--animated",
3994
4056
  "rise": "a8ccharts-inuQka-rise"
3995
4057
  };
@@ -4516,11 +4578,10 @@ const TooltipDate = ({ date, displayResolution }) => {
4516
4578
  * one row per visible series (label + formatted value), sorted descending by
4517
4579
  * value. Reused by AreaChart, which has the same multi-series shape.
4518
4580
  *
4519
- * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4520
- * @param contentStyle - Explicit tooltip content color overrides.
4581
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4521
4582
  * @return Tooltip JSX, or `null` when no datum is hovered.
4522
4583
  */
4523
- const renderDefaultTooltip = (params, contentStyle) => {
4584
+ const renderDefaultTooltip = (params) => {
4524
4585
  const { tooltipData, bucketInfo } = params;
4525
4586
  const nearestDatum = tooltipData?.nearestDatum?.datum;
4526
4587
  if (!nearestDatum) return null;
@@ -4533,29 +4594,24 @@ const renderDefaultTooltip = (params, contentStyle) => {
4533
4594
  if (b.value === null) return -1;
4534
4595
  return b.value - a.value;
4535
4596
  });
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
- });
4597
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
4598
+ className: line_chart_module_default["line-chart__tooltip-date"],
4599
+ children: /* @__PURE__ */ jsx(TooltipDate, {
4600
+ date: nearestDatum.date,
4601
+ displayResolution: bucketInfo?.displayResolution ?? "day"
4602
+ })
4603
+ }), tooltipPoints.map((point) => /* @__PURE__ */ jsxs(Stack, {
4604
+ direction: "row",
4605
+ align: "center",
4606
+ justify: "space-between",
4607
+ children: [/* @__PURE__ */ jsxs("span", {
4608
+ className: line_chart_module_default["line-chart__tooltip-label"],
4609
+ children: [point.key, ":"]
4610
+ }), /* @__PURE__ */ jsx("span", {
4611
+ className: line_chart_module_default["line-chart__tooltip-value"],
4612
+ children: formatReading(point.value)
4613
+ })]
4614
+ }, point.key))] });
4559
4615
  };
4560
4616
  const validateData$4 = (data) => {
4561
4617
  if (!data?.length) return __("No data available", "jetpack-charts");
@@ -4815,29 +4871,10 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4815
4871
  xAccessor: (d) => d?.date,
4816
4872
  yAccessor: (d) => d?.value
4817
4873
  };
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({
4874
+ const tooltipRenderer = useMemo(() => (params) => renderTooltip({
4834
4875
  ...params,
4835
4876
  bucketInfo
4836
- }), [
4837
- renderTooltip,
4838
- bucketInfo,
4839
- tooltipStyle
4840
- ]);
4877
+ }), [renderTooltip, bucketInfo]);
4841
4878
  if (error) return /* @__PURE__ */ jsx("div", {
4842
4879
  className: clsx("line-chart", line_chart_module_default["line-chart"]),
4843
4880
  children: error
@@ -4985,7 +5022,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4985
5022
  detectBounds: true,
4986
5023
  snapTooltipToDatumX: true,
4987
5024
  tooltipPlacement,
4988
- style: resolvedTooltipStyle,
5025
+ style: tooltipStyle,
4989
5026
  snapTooltipToDatumY: true,
4990
5027
  showSeriesGlyphs: true,
4991
5028
  renderTooltip: tooltipRenderer,
@@ -6893,8 +6930,7 @@ var conversion_funnel_chart_module_default = {
6893
6930
  "step-rate": "a8ccharts-mGEVca-step-rate",
6894
6931
  "stretch": "a8ccharts-mGEVca-stretch",
6895
6932
  "tooltip-content": "a8ccharts-mGEVca-tooltip-content",
6896
- "tooltip-title": "a8ccharts-mGEVca-tooltip-title",
6897
- "tooltip-wrapper": "a8ccharts-mGEVca-tooltip-wrapper"
6933
+ "tooltip-title": "a8ccharts-mGEVca-tooltip-title"
6898
6934
  };
6899
6935
  //#endregion
6900
6936
  //#region src/charts/conversion-funnel-chart/private/use-funnel-selection.ts
@@ -6955,9 +6991,10 @@ const useFunnelSelection = (hideTooltip) => {
6955
6991
  * @param props.renderStepRate - Custom render function for step rates
6956
6992
  * @param props.renderMainMetric - Custom render function for the entire main metric section
6957
6993
  * @param props.renderTooltip - Custom render function for tooltip content
6994
+ * @param props.tooltipStyle - Inline styles merged over the tooltip box defaults
6958
6995
  * @return JSX element representing the conversion funnel chart
6959
6996
  */
6960
- const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
6997
+ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip, tooltipStyle }) => {
6961
6998
  const chartId = useChartId(providedChartId);
6962
6999
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
6963
7000
  const chartRef = useRef(null);
@@ -7192,14 +7229,14 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
7192
7229
  step: tooltipData,
7193
7230
  index: steps.findIndex((s) => s.id === tooltipData.id),
7194
7231
  top: tooltipTop,
7195
- left: tooltipLeft,
7196
- className: conversion_funnel_chart_module_default["tooltip-wrapper"]
7232
+ left: tooltipLeft
7197
7233
  }) : renderDefaultTooltip(tooltipData);
7198
7234
  if (!tooltipContent) return null;
7199
7235
  return /* @__PURE__ */ jsx(BoundedTooltip, {
7200
7236
  top: tooltipTop,
7201
7237
  left: tooltipLeft,
7202
- className: clsx(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
7238
+ className: standaloneScopeClass,
7239
+ style: tooltipStyle,
7203
7240
  children: tooltipContent
7204
7241
  });
7205
7242
  })()]
@@ -7485,6 +7522,73 @@ let GoogleDataTableColumnRoleType = /* @__PURE__ */ function(GoogleDataTableColu
7485
7522
  GoogleDataTableColumnRoleType["domain"] = "domain";
7486
7523
  return GoogleDataTableColumnRoleType;
7487
7524
  }({});
7525
+ const resolveRole = (pointer, resolve) => {
7526
+ const raw = resolve(pointer);
7527
+ const parsed = raw ? color(raw) : null;
7528
+ if (!parsed) return {
7529
+ kind: "unreadable",
7530
+ raw
7531
+ };
7532
+ const { r, g, b } = parsed.opacity > 0 ? parsed.rgb() : {
7533
+ r: 0,
7534
+ g: 0,
7535
+ b: 0
7536
+ };
7537
+ return {
7538
+ kind: "color",
7539
+ rgb: [
7540
+ r,
7541
+ g,
7542
+ b
7543
+ ],
7544
+ alpha: parsed.opacity
7545
+ };
7546
+ };
7547
+ /**
7548
+ * Reads both label roles at the element the label text inherits from, so JS and CSS agree on what paints.
7549
+ *
7550
+ * @param resolve - A CSS variable resolver bound to that element.
7551
+ * @return The resolved label roles.
7552
+ */
7553
+ const resolveLabelRoles = (resolve) => ({
7554
+ label: resolveRole(CATALOG_POINTERS.label, resolve),
7555
+ labelInverse: resolveRole(CATALOG_POINTERS.labelInverse, resolve)
7556
+ });
7557
+ const roleContrast = (role, fill, fillLuminance) => {
7558
+ if (role.kind === "unreadable") return 0;
7559
+ return luminanceContrastRatio(fillLuminance, rgbLuminance(blendRgb(role.rgb, fill, role.alpha)));
7560
+ };
7561
+ /**
7562
+ * Picks the label color for text drawn on a fill: the role that contrasts more, or black/white when neither reaches AA.
7563
+ *
7564
+ * Both roles set to one color is a host's choice to keep that color, so it never falls back.
7565
+ *
7566
+ * @param fill - The fill the text sits on, as unrounded sRGB channels.
7567
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7568
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7569
+ * @return The color the label should paint with.
7570
+ */
7571
+ const pickLabelTextColorForFill = (fill, roles, defaultRole) => {
7572
+ if (!roles || (0, import_fast_deep_equal.default)(roles.label, roles.labelInverse)) return defaultRole;
7573
+ const fillLuminance = rgbLuminance(fill);
7574
+ const labelContrast = roleContrast(roles.label, fill, fillLuminance);
7575
+ const inverseContrast = roleContrast(roles.labelInverse, fill, fillLuminance);
7576
+ if (Math.max(labelContrast, inverseContrast) >= 4.55) return labelContrast > inverseContrast ? "label" : "label-inverse";
7577
+ return luminanceContrastRatio(fillLuminance, 0) > luminanceContrastRatio(fillLuminance, 1) ? "black" : "white";
7578
+ };
7579
+ /**
7580
+ * `pickLabelTextColorForFill` for a fill given as a color.
7581
+ *
7582
+ * @param fill - The fill, in any color syntax `normalizeColorToHex` reads.
7583
+ * @param roles - The label roles resolved at the chart's element, or null before they are read.
7584
+ * @param defaultRole - The role the chart's stylesheet paints when nothing can be decided.
7585
+ * @return The color the label should paint with.
7586
+ */
7587
+ const pickLabelTextColor = (fill, roles, defaultRole) => {
7588
+ const fillHex = normalizeColorToHex(fill);
7589
+ if (!isValidHexColor(fillHex)) return defaultRole;
7590
+ return pickLabelTextColorForFill(hexToRgb(fillHex), roles, defaultRole);
7591
+ };
7488
7592
  //#endregion
7489
7593
  //#region src/charts/heatmap-chart/heatmap-chart.module.scss
7490
7594
  var heatmap_chart_module_default = {
@@ -7494,9 +7598,11 @@ var heatmap_chart_module_default = {
7494
7598
  "heatmap-chart__cell--hidden": "a8ccharts-O3YMOW-heatmap-chart__cell--hidden",
7495
7599
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
7496
7600
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
7497
- "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
7498
7601
  "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
7499
7602
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
7603
+ "heatmap-chart__cell-value--black": "a8ccharts-O3YMOW-heatmap-chart__cell-value--black",
7604
+ "heatmap-chart__cell-value--inverse": "a8ccharts-O3YMOW-heatmap-chart__cell-value--inverse",
7605
+ "heatmap-chart__cell-value--white": "a8ccharts-O3YMOW-heatmap-chart__cell-value--white",
7500
7606
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
7501
7607
  "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
7502
7608
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
@@ -7790,25 +7896,25 @@ const stepCalendarCell = (grid, blocks, from, key) => {
7790
7896
  //#endregion
7791
7897
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
7792
7898
  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 }
7899
+ const CELL_VALUE_MODIFIER = {
7900
+ label: void 0,
7901
+ "label-inverse": heatmap_chart_module_default["heatmap-chart__cell-value--inverse"],
7902
+ black: heatmap_chart_module_default["heatmap-chart__cell-value--black"],
7903
+ white: heatmap_chart_module_default["heatmap-chart__cell-value--white"]
7800
7904
  };
7905
+ const NO_ROW_LABELS = [];
7906
+ const TOOLTIP_BOX_STYLE = { zIndex: 3 };
7801
7907
  const cellName = (info) => info.cellLabel || [
7802
7908
  info.groupLabel,
7803
7909
  info.columnLabel,
7804
7910
  info.rowLabel
7805
7911
  ].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 }) => {
7912
+ 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
7913
  const chartId = useChartId(providedChartId);
7808
7914
  const tooltipBoxStyle = tooltipStyle ? {
7809
- ...TOOLTIP_BOX_STYLES[tooltipVariant],
7915
+ ...TOOLTIP_BOX_STYLE,
7810
7916
  ...tooltipStyle
7811
- } : TOOLTIP_BOX_STYLES[tooltipVariant];
7917
+ } : TOOLTIP_BOX_STYLE;
7812
7918
  const { getElementStyles, theme } = useGlobalChartsContext();
7813
7919
  const scopeElement = useChartScopeElement();
7814
7920
  const { heatmapChart: heatmapChartSettings } = theme;
@@ -7817,6 +7923,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7817
7923
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = useTooltip();
7818
7924
  const standaloneScopeClass = useStandaloneScopeClass();
7819
7925
  const containerRef = useRef(null);
7926
+ const [labelRoles, setLabelRoles] = useState(null);
7820
7927
  const getTooltipOrigin = useCallback(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
7821
7928
  const { color: primaryColorHex } = getElementStyles({
7822
7929
  index: 0,
@@ -7824,7 +7931,21 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
7824
7931
  });
7825
7932
  const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
7826
7933
  const primaryHex = normalizeColorToHex(primaryColorHex);
7827
- const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
7934
+ const cellMix = isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) ? {
7935
+ primary: hexToRgb(primaryHex),
7936
+ background: hexToRgb(chartBackgroundHex)
7937
+ } : null;
7938
+ const cellTextColor = (intensity) => cellMix ? pickLabelTextColorForFill(blendRgb(cellMix.primary, cellMix.background, CELL_MIX_FLOOR + .85 * intensity), labelRoles, "label") : "label";
7939
+ useIsomorphicLayoutEffect(() => {
7940
+ if (!containerRef.current) return;
7941
+ const next = resolveLabelRoles(createCssVariableResolver(containerRef.current));
7942
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
7943
+ }, [
7944
+ data,
7945
+ className,
7946
+ primaryColorHex,
7947
+ chartBackgroundHex
7948
+ ]);
7828
7949
  const extent = useMemo(() => getValueExtent(data), [data]);
7829
7950
  const heatmapContext = useMemo(() => ({
7830
7951
  extent,
@@ -8096,7 +8217,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8096
8217
  "data-row": rowIndex,
8097
8218
  className: clsx(heatmap_chart_module_default["heatmap-chart__cell"], {
8098
8219
  [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
8099
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
8100
8220
  [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
8101
8221
  ...summaryGaps(columnIndex),
8102
8222
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selected?.column === columnIndex && selected?.row === rowIndex
@@ -8108,7 +8228,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8108
8228
  onMouseMove: handleCellMouseMove,
8109
8229
  onMouseLeave: handleCellMouseLeave,
8110
8230
  children: (drawValues || column.summary) && present && /* @__PURE__ */ jsx("span", {
8111
- className: heatmap_chart_module_default["heatmap-chart__cell-value"],
8231
+ className: clsx(heatmap_chart_module_default["heatmap-chart__cell-value"], filled && CELL_VALUE_MODIFIER[cellTextColor(normalized)]),
8112
8232
  children: formatNumberCompact(value)
8113
8233
  })
8114
8234
  }, `cell-${columnIndex}-${rowIndex}`);
@@ -8132,7 +8252,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
8132
8252
  }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
8133
8253
  top: tooltipTop,
8134
8254
  left: tooltipLeft,
8135
- className: tooltipVariant === "dark" ? base_tooltip_module_default.surface : void 0,
8136
8255
  style: tooltipBoxStyle,
8137
8256
  children: /* @__PURE__ */ jsx("div", {
8138
8257
  className: standaloneScopeClass,
@@ -11359,7 +11478,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
11359
11478
  var pie_chart_module_default = {
11360
11479
  "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
11361
11480
  "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
11481
+ "pie-chart__label-text--black": "a8ccharts-gnszbG-pie-chart__label-text--black",
11362
11482
  "pie-chart__label-text--on-light": "a8ccharts-gnszbG-pie-chart__label-text--on-light",
11483
+ "pie-chart__label-text--white": "a8ccharts-gnszbG-pie-chart__label-text--white",
11363
11484
  "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
11364
11485
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
11365
11486
  };
@@ -11403,24 +11524,11 @@ const validateData$1 = (data) => {
11403
11524
  message: ""
11404
11525
  };
11405
11526
  };
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);
11527
+ const LABEL_TEXT_MODIFIER = {
11528
+ label: pie_chart_module_default["pie-chart__label-text--on-light"],
11529
+ "label-inverse": void 0,
11530
+ black: pie_chart_module_default["pie-chart__label-text--black"],
11531
+ white: pie_chart_module_default["pie-chart__label-text--white"]
11424
11532
  };
11425
11533
  /**
11426
11534
  * Renders a pie or donut chart using the provided data.
@@ -11437,7 +11545,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11437
11545
  const standaloneScopeClass = useStandaloneScopeClass();
11438
11546
  const containerRef = useRef(null);
11439
11547
  const rootRef = useRef(null);
11440
- const [labelPointers, setLabelPointers] = useState(null);
11548
+ const [labelRoles, setLabelRoles] = useState(null);
11441
11549
  const onMouseLeave = useCallback(() => {
11442
11550
  if (!withTooltips) return;
11443
11551
  hideTooltip();
@@ -11448,18 +11556,9 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11448
11556
  if (!showLabels || !rootRef.current) return;
11449
11557
  const resolve = createCssVariableResolver(rootRef.current);
11450
11558
  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
11559
  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);
11560
+ const next = (rawLabelBackground ? !plateColor || plateColor.opacity > 0 : false) ? null : resolveLabelRoles(resolve);
11561
+ setLabelRoles((previous) => (0, import_fast_deep_equal.default)(previous, next) ? previous : next);
11463
11562
  }, [
11464
11563
  showLabels,
11465
11564
  className,
@@ -11630,7 +11729,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
11630
11729
  ry: 4,
11631
11730
  pointerEvents: "none"
11632
11731
  }), /* @__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) }),
11732
+ className: clsx(pie_chart_module_default["pie-chart__label-text"], LABEL_TEXT_MODIFIER[pickLabelTextColor(fill, labelRoles, "label-inverse")]),
11634
11733
  x: centroidX,
11635
11734
  y: centroidY,
11636
11735
  dy: ".33em",