@automattic/charts 3.2.0 → 4.0.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 (109) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/TOKENS.md +53 -58
  3. package/dist/index.cjs +1012 -542
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +72 -29
  6. package/dist/index.d.cts +151 -179
  7. package/dist/index.d.ts +151 -179
  8. package/dist/index.js +1013 -544
  9. package/dist/index.js.map +1 -1
  10. package/package.json +6 -6
  11. package/src/charts/area-chart/area-chart.tsx +39 -25
  12. package/src/charts/area-chart/test/area-chart.test.tsx +4 -33
  13. package/src/charts/area-chart/test/tick-placement.test.tsx +55 -0
  14. package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +86 -0
  15. package/src/charts/area-chart/test/tooltip-resolution.test.tsx +69 -0
  16. package/src/charts/area-chart/types.ts +4 -7
  17. package/src/charts/bar-chart/bar-chart.tsx +2 -1
  18. package/src/charts/bar-chart/private/test/use-bar-chart-options-formatting.test.tsx +69 -0
  19. package/src/charts/bar-chart/private/use-bar-chart-options.ts +27 -15
  20. package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -0
  21. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +33 -23
  22. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +138 -182
  23. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +46 -0
  24. package/src/charts/geo-chart/geo-chart.module.scss +1 -0
  25. package/src/charts/geo-chart/geo-chart.tsx +5 -10
  26. package/src/charts/geo-chart/test/geo-chart.test.tsx +2 -2
  27. package/src/charts/heatmap-chart/heatmap-chart.module.scss +21 -16
  28. package/src/charts/heatmap-chart/heatmap-chart.tsx +45 -28
  29. package/src/charts/heatmap-chart/private/build-calendar-data.ts +3 -0
  30. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +11 -4
  31. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +51 -12
  32. package/src/charts/heatmap-chart/types.ts +2 -2
  33. package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
  34. package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +3 -5
  35. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +31 -19
  36. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +27 -17
  37. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +14 -0
  38. package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -9
  39. package/src/charts/line-chart/line-chart.module.scss +10 -18
  40. package/src/charts/line-chart/line-chart.tsx +93 -24
  41. package/src/charts/line-chart/private/line-chart-annotation.tsx +18 -19
  42. package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +74 -0
  43. package/src/charts/line-chart/test/line-chart.test.tsx +27 -3
  44. package/src/charts/line-chart/test/tick-placement.test.tsx +84 -0
  45. package/src/charts/line-chart/test/tooltip-resolution.test.tsx +140 -0
  46. package/src/charts/line-chart/types.ts +4 -1
  47. package/src/charts/pie-chart/pie-chart.module.scss +13 -1
  48. package/src/charts/pie-chart/pie-chart.tsx +30 -40
  49. package/src/charts/pie-chart/test/pie-chart.test.tsx +56 -0
  50. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +8 -3
  51. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +23 -37
  52. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +50 -0
  53. package/src/charts/private/chart-layout/chart-layout.module.scss +3 -2
  54. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
  55. package/src/charts/private/test/axis-formatting.test.tsx +85 -0
  56. package/src/charts/private/test/time-axis-formatting.test.ts +109 -0
  57. package/src/charts/private/test/time-axis-options.test.ts +267 -0
  58. package/src/charts/private/test/time-axis-tick-invariants.test.ts +153 -0
  59. package/src/charts/private/test/time-axis-tick-values.test.ts +280 -0
  60. package/src/charts/private/test/time-axis.test.ts +9 -69
  61. package/src/charts/private/time-axis-options.ts +106 -0
  62. package/src/charts/private/time-axis.ts +318 -136
  63. package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
  64. package/src/charts/private/xy-plot/xy-plot.module.scss +8 -0
  65. package/src/charts/sparkline/test/sparkline.test.tsx +29 -56
  66. package/src/charts/sparkline/types.ts +1 -1
  67. package/src/components/legend/private/base-legend.module.scss +9 -5
  68. package/src/components/tooltip/accessible-tooltip.tsx +13 -15
  69. package/src/components/tooltip/base-tooltip.module.scss +3 -3
  70. package/src/components/tooltip/private/bounded-tooltip.tsx +172 -0
  71. package/src/components/tooltip/test/accessible-tooltip.test.tsx +2 -3
  72. package/src/components/tooltip/test/bounded-tooltip.test.tsx +129 -0
  73. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +279 -0
  74. package/src/components/tooltip/xy-chart-tooltip.tsx +288 -0
  75. package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
  76. package/src/hooks/test/use-xychart-theme.test.tsx +16 -3
  77. package/src/hooks/use-xychart-theme.ts +25 -9
  78. package/src/index.ts +4 -0
  79. package/src/providers/chart-context/global-charts-provider.tsx +37 -38
  80. package/src/providers/chart-context/hooks/use-chart-formatting.ts +22 -0
  81. package/src/providers/chart-context/index.ts +1 -0
  82. package/src/providers/chart-context/private/catalog-pointers.ts +42 -0
  83. package/src/providers/chart-context/private/series-palette.ts +3 -13
  84. package/src/providers/chart-context/test/{sparse-theme.test.ts → catalog-pointers.test.ts} +22 -10
  85. package/src/providers/chart-context/test/chart-context.test.tsx +177 -792
  86. package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +7 -25
  87. package/src/providers/chart-context/themes.ts +7 -52
  88. package/src/providers/chart-context/types.ts +3 -0
  89. package/src/styles/chart-scope.scss +39 -62
  90. package/src/styles/test/chart-scope.test.ts +19 -47
  91. package/src/test-utils/series-fixtures.ts +31 -0
  92. package/src/types.ts +79 -166
  93. package/src/utils/bucket-info.ts +87 -0
  94. package/src/utils/color-utils.ts +0 -27
  95. package/src/utils/date-formatting.ts +174 -0
  96. package/src/utils/index.ts +3 -0
  97. package/src/utils/test/bucket-info.test.ts +185 -0
  98. package/src/utils/test/color-utils.test.ts +1 -48
  99. package/src/utils/test/date-formatting.test.ts +89 -0
  100. package/src/utils/test/get-styles.test.ts +10 -7
  101. package/src/utils/test/merge-themes.test.ts +18 -46
  102. package/src/visx/types.ts +29 -2
  103. package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +0 -28
  104. package/src/providers/chart-context/private/theme-override-vars.ts +0 -131
  105. package/src/providers/chart-context/private/with-catalog-pointers.ts +0 -93
  106. package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +0 -29
  107. package/src/providers/chart-context/test/theme-override-vars.test.ts +0 -183
  108. package/src/providers/chart-context/test/with-catalog-pointers.test.ts +0 -104
  109. package/src/styles/test/wpds-fallbacks.test.ts +0 -131
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getStringWidth as getStringWidth$1 } from "./visx/text/index.js";
2
2
  import { formatNumber, formatNumberCompact } from "@automattic/number-formatters";
3
- import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarSeries, DataContext, Grid, Tooltip, TooltipContext, XYChart, buildChartTheme } from "@visx/xychart";
3
+ import { AnimatedAreaSeries, AnimatedAreaStack, AreaSeries, Axis, BarGroup, BarSeries, DataContext, Grid, TooltipContext, XYChart, buildChartTheme } from "@visx/xychart";
4
4
  import { __, _x, sprintf } from "@wordpress/i18n";
5
5
  import clsx from "clsx";
6
6
  import * as React from "react";
@@ -10,11 +10,12 @@ import { addDays, differenceInCalendarWeeks, differenceInHours, differenceInYear
10
10
  import { Text, getStringWidth } from "@visx/text";
11
11
  import deepmerge from "deepmerge";
12
12
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
- import { useTooltip, useTooltipInPortal } from "@visx/tooltip";
13
+ import { Tooltip, defaultStyles, useTooltip } from "@visx/tooltip";
14
14
  import { createScale, getTicks, scaleBand, scaleOrdinal, scaleTime } from "@visx/scale";
15
15
  import { Group } from "@visx/group";
16
16
  import { LegendItem, LegendLabel, LegendOrdinal, LegendShape } from "@visx/legend";
17
17
  import { IconButton, Stack, Text as Text$1 } from "@wordpress/ui";
18
+ import { createPortal } from "react-dom";
18
19
  import { LinearGradient } from "@visx/gradient";
19
20
  import { curveCatmullRom, curveLinear, curveMonotoneX } from "@visx/curve";
20
21
  import { useParentSize } from "@visx/responsive";
@@ -172,6 +173,39 @@ const parseAsLocalDate = (dateString) => {
172
173
  return /* @__PURE__ */ new Date(NaN);
173
174
  };
174
175
  //#endregion
176
+ //#region src/utils/bucket-info.ts
177
+ const getInstant = (point) => {
178
+ if (!point) return;
179
+ if ("date" in point && point.date) return point.date.getTime();
180
+ if ("dateString" in point && point.dateString) return parseAsLocalDate(point.dateString).getTime();
181
+ };
182
+ const getPointSpacingInHours = (data) => {
183
+ return data.reduce((spacing, datom) => {
184
+ const instants = [...new Set(datom.data.map((point) => getInstant(point)))].filter((time) => time !== void 0 && Number.isFinite(time)).sort((a, b) => a - b);
185
+ return instants.reduce((seriesSpacing, time, index) => index === 0 ? seriesSpacing : Math.min(seriesSpacing, Math.abs(differenceInHours(time, instants[index - 1]))), spacing);
186
+ }, Number.POSITIVE_INFINITY);
187
+ };
188
+ const SUB_DAILY_SPACING_HOURS = 23;
189
+ const MONTHLY_SPACING_HOURS = 672;
190
+ const getBucketResolution = (data, tickResolution) => {
191
+ if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
192
+ const spacingInHours = getPointSpacingInHours(data);
193
+ if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
194
+ if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
195
+ return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
196
+ };
197
+ /**
198
+ * How this data was classified, for consumers that render their own labels.
199
+ *
200
+ * @param data - The series the chart draws, in any order, dated by `date` or `dateString`.
201
+ * @param tickResolution - Caller-declared bucket resolution, when known.
202
+ * @return The declared or inferred bucket, and the resolution formats key on.
203
+ */
204
+ const getBucketInfo = (data, tickResolution) => ({
205
+ bucket: tickResolution ?? getBucketResolution(data),
206
+ displayResolution: getBucketResolution(data, tickResolution)
207
+ });
208
+ //#endregion
175
209
  //#region src/utils/format-metric-value.ts
176
210
  /**
177
211
  * Format a numeric metric value based on type, precision and scale.
@@ -384,20 +418,6 @@ const parseHslString = (hslString) => {
384
418
  ];
385
419
  };
386
420
  /**
387
- * Parse an RGB string like 'rgb(255, 0, 0)' into a hex color.
388
- *
389
- * @deprecated Use normalizeColorToHex() instead, which handles all color formats including rgb() and rgba().
390
- * @param rgbString - RGB color string (not RGBA)
391
- * @return hex color string or null if invalid
392
- */
393
- const parseRgbString = (rgbString) => {
394
- const lower = rgbString.toLowerCase().trim();
395
- if (!lower.startsWith("rgb(") || lower.startsWith("rgba(")) return null;
396
- const parsed = color(lower);
397
- if (!parsed) return null;
398
- return parsed.formatHex();
399
- };
400
- /**
401
421
  * Normalize any CSS color value to a hex color string.
402
422
  * Handles hex, HSL, HSLA, RGB, RGBA, named CSS colors, and CSS variables.
403
423
  *
@@ -625,6 +645,112 @@ const resolveFontSize = (val) => {
625
645
  }
626
646
  };
627
647
  //#endregion
648
+ //#region src/utils/date-formatting.ts
649
+ const formatters = /* @__PURE__ */ new Map();
650
+ const getFormatter$1 = (options, { locale, timeZone }) => {
651
+ const key = `${locale ?? ""}|${timeZone ?? ""}|${JSON.stringify(options)}`;
652
+ let formatter = formatters.get(key);
653
+ if (!formatter) {
654
+ formatter = new Intl.DateTimeFormat(locale, {
655
+ ...options,
656
+ timeZone
657
+ });
658
+ formatters.set(key, formatter);
659
+ }
660
+ return formatter;
661
+ };
662
+ const warned = /* @__PURE__ */ new Set();
663
+ const warnOnce = (key, message) => {
664
+ if (warned.has(key) || process.env.NODE_ENV === "production") return;
665
+ warned.add(key);
666
+ console.warn(`[Charts] ${message}`);
667
+ };
668
+ const isUsable = (formatting) => {
669
+ try {
670
+ Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
671
+ return true;
672
+ } catch {
673
+ return false;
674
+ }
675
+ };
676
+ const toBcp47 = (locale) => locale?.replace(/_/g, "-");
677
+ /**
678
+ * Repairs a host locale for `Intl`, dropping either half it still cannot use.
679
+ *
680
+ * Every formatter below is built during render, so an unusable string would throw
681
+ * where React unmounts the whole host tree rather than one chart. WordPress hands
682
+ * out the likeliest one either way: `timezone_string` is `''` on a site set to a
683
+ * raw UTC offset, and has no repair.
684
+ *
685
+ * @param formatting - Locale and time zone as the host supplied them.
686
+ * @return The usable halves, each `undefined` where the host's value was not.
687
+ */
688
+ const sanitizeFormatting = (formatting) => {
689
+ const repaired = {
690
+ ...formatting,
691
+ locale: toBcp47(formatting.locale)
692
+ };
693
+ if (isUsable(repaired)) return repaired;
694
+ const locale = isUsable({ locale: repaired.locale }) ? repaired.locale : void 0;
695
+ const timeZone = isUsable({ timeZone: repaired.timeZone }) ? repaired.timeZone : void 0;
696
+ if (locale === void 0 && formatting.locale !== void 0) warnOnce(`locale:${formatting.locale}`, `locale ${JSON.stringify(formatting.locale)} is not a BCP-47 tag Intl accepts, so dates render in the browser's locale.`);
697
+ if (timeZone === void 0 && formatting.timeZone !== void 0) warnOnce(`timeZone:${formatting.timeZone}`, `timeZone ${JSON.stringify(formatting.timeZone)} is not a zone Intl accepts, so dates render in the browser's zone. Pass an IANA name or a UTC offset such as "+05:30".`);
698
+ return {
699
+ locale,
700
+ timeZone
701
+ };
702
+ };
703
+ /**
704
+ * A date formatter bound to the host's locale and time zone.
705
+ *
706
+ * @param options - Date-part options, as for `Intl.DateTimeFormat`.
707
+ * @param formatting - Host locale and time zone; either half absent leaves the runtime's own.
708
+ * @return Formatter taking a timestamp or a `Date`.
709
+ */
710
+ const createDateFormatter = (options, formatting) => {
711
+ return (value) => {
712
+ if (!Number.isFinite(Number(value))) return new Date(Number(value)).toLocaleDateString();
713
+ return getFormatter$1(options, formatting).format(value);
714
+ };
715
+ };
716
+ const CLOCK_OPTIONS = {
717
+ month: "numeric",
718
+ hour: "numeric",
719
+ hourCycle: "h23"
720
+ };
721
+ const NO_FIELDS = {
722
+ month: NaN,
723
+ hour: NaN
724
+ };
725
+ /**
726
+ * A clock reading calendar fields in the host's time zone.
727
+ *
728
+ * `Date`'s own getters read the browser's zone, so a chart viewed from another
729
+ * country anchors its ticks to the wrong midnights and the wrong January
730
+ * without this.
731
+ *
732
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
733
+ * @return Reader taking a `Date`.
734
+ */
735
+ const createZonedClock = (timeZone) => {
736
+ if (!timeZone) return (date) => ({
737
+ month: date.getMonth() + 1,
738
+ hour: date.getHours()
739
+ });
740
+ return (date) => {
741
+ if (!Number.isFinite(Number(date))) return NO_FIELDS;
742
+ const parts = getFormatter$1(CLOCK_OPTIONS, {
743
+ locale: "en-US",
744
+ timeZone
745
+ }).formatToParts(date);
746
+ const read = (type) => Number(parts.find((part) => part.type === type)?.value);
747
+ return {
748
+ month: read("month"),
749
+ hour: read("hour")
750
+ };
751
+ };
752
+ };
753
+ //#endregion
628
754
  //#region src/providers/chart-scope/chart-scope-context.ts
629
755
  /**
630
756
  * The nearest chart element, published so JS token resolution reads the same CSS cascade that paints the chart's own colours.
@@ -772,150 +898,29 @@ const SERIES_SLOT_1_FALLBACK = "#3858e9";
772
898
  /** The catalog pointer for every slot, in slot order. */
773
899
  const SERIES_PALETTE_POINTERS = Array.from({ length: 5 }, (_, index) => index === 0 ? `var(${seriesRole(1)}, ${SERIES_SLOT_1_FALLBACK})` : `var(${seriesRole(index + 1)})`);
774
900
  //#endregion
775
- //#region src/providers/chart-context/private/theme-override-vars.ts
776
- const ROLE_FOR_FIELD = [
777
- ["--a8c-charts-color-background", (theme) => theme.backgroundColor],
778
- ["--a8c-charts-color-grid", (theme) => theme.gridStyles?.stroke],
779
- ["--a8c-charts-color-axis", (theme) => theme.xAxisLineStyles?.stroke],
780
- ["--a8c-charts-color-tick", (theme) => theme.xTickLineStyles?.stroke],
781
- ["--a8c-charts-color-label-axis", (theme) => theme.svgLabelSmall?.fill],
782
- ["--a8c-charts-color-label-background", (theme) => theme.labelBackgroundColor],
783
- ["--a8c-charts-color-label-on-fill", (theme) => theme.labelTextColor],
784
- ...Array.from({ length: 5 }, (_, index) => [seriesRole(index + 1), (theme) => theme.colors?.[index]])
785
- ];
786
- let warnedAboutExtraColors = false;
787
- /**
788
- * Warns once when a consumer passes more colors than there are slots to publish them into.
789
- *
790
- * @param count - How many entries `theme.colors` holds.
791
- */
792
- function warnOnceAboutExtraColors(count) {
793
- if (warnedAboutExtraColors || process.env.NODE_ENV === "production") return;
794
- warnedAboutExtraColors = true;
795
- console.warn(`[Charts] theme.colors holds ${count} colors and the palette has 5 slots, so entries past the 5th are ignored. Set a per-series color with \`options.stroke\` instead.`);
796
- }
797
- /**
798
- * The variable a `theme` prop override is published as, one layer outside the role itself.
799
- *
800
- * `chart-scope.scss` declares each of these roles as `var(<role>-theme, <catalog default>)`, so an override that is invalid at computed-value time — a fallback-less `var()` naming a token the host never set — only invalidates this variable, and the role still resolves its mapped `--wpds-*` token. Published as the role directly, such a value took the role and every bare `var(<role>)` read site down with it.
801
- *
802
- * @param role - The catalog role being overridden.
803
- * @return The custom property carrying the consumer's value.
804
- */
805
- const themeLayerVar = (role) => `${role}-theme`;
806
- ROLE_FOR_FIELD.map(([role]) => role);
807
- /**
808
- * Whether a value reads the role it is being published for, which would make the catalog entry depend on itself.
809
- *
810
- * The dependency runs `<role>: var(<role>-theme, …)`, so a value naming `<role>` closes a cycle through the catalog entry. CSS marks every custom property in a cycle invalid at computed-value time — the role's own fallback is *not* used, so the token resolves to nothing and every chart loses that color. Verified in Chrome: `--role-theme: var(--role, blue); --role: var(--role-theme, green)` leaves `--role` empty, not `green`.
811
- *
812
- * Three things this has to get right, none of which a plain substring test gets:
813
- *
814
- * `var()` permits whitespace after the opening paren, so testing for `var(<role>` misses `var( --a8c-charts-color-grid, … )`.
815
- *
816
- * Several roles are prefixes of others (`--a8c-charts-color-label` of `--a8c-charts-color-label-secondary`), and every role is a prefix of its own theme layer. A prefix match would read a legitimate cross-role pointer as a self-reference and silently drop the override, so the role must be followed by something that cannot continue an identifier — `-` included. A value naming `<role>-theme` is therefore not treated as a self-reference: that cycle is confined to the theme layer, which leaves the role free to fall back to the catalog default.
817
- *
818
- * `resolveCssVariable` also accepts a bare custom-property name with no `var()` wrapper (`--a8c-charts-color-grid`), so that is legal `theme` input too. It forms no cycle — custom properties accept arbitrary token streams — so it survives as a literal string and drops silently at the use site instead of falling back to the catalog default.
819
- *
820
- * @param value - The consumer's value for the field.
821
- * @param role - The catalog role this value would override.
822
- * @return True when the value reads `role`, so it must not be published.
823
- */
824
- const readsOwnRole = (value, role) => value.trim() === role || new RegExp(`var\\(\\s*${role.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(value);
825
- /**
826
- * Maps a sparse consumer theme onto the theme-layer variables its overridden catalog roles read, so CSS-painted and JS-resolved colors read one source.
827
- *
828
- * A value that reads the role it would override is left unpublished — that is the default theme's own pointer surviving `mergeThemes`, and publishing it would invalidate the role. The role is still reported in `roles`: `withCatalogPointers` restores its theme field to the catalog pointer either way, so CSS and visx agree on the catalog default rather than visx painting a literal CSS never sees.
829
- *
830
- * `theme.colors` publishes through the same mechanism, one slot per entry, so a CSS declaration of `--a8c-charts-color-series-N` still beats it and a short array leaves the later slots unset rather than blank.
831
- *
832
- * @param theme - The consumer's `theme` prop, before merging with the default theme.
833
- * @return The wrapper's custom properties, and the roles the consumer overrode.
834
- */
835
- const themeOverrideVars = (theme) => {
836
- if (!theme) return {
837
- vars: {},
838
- roles: []
839
- };
840
- if (Array.isArray(theme.colors) && theme.colors.length > 5) warnOnceAboutExtraColors(theme.colors.length);
841
- const vars = {};
842
- const roles = [];
843
- for (const [role, read] of ROLE_FOR_FIELD) {
844
- const value = read(theme);
845
- if (typeof value !== "string" || value === "") continue;
846
- roles.push(role);
847
- if (!readsOwnRole(value, role)) vars[themeLayerVar(role)] = value;
848
- }
849
- return {
850
- vars,
851
- roles
852
- };
853
- };
854
- //#endregion
855
901
  //#region src/providers/chart-context/themes.ts
856
902
  /**
857
- * Default theme configuration
903
+ * Default theme configuration: the shape and spacing a consumer can override.
904
+ *
905
+ * For a color, set the matching `--a8c-charts-color-*` role in CSS instead; the `var()`
906
+ * chains that deliver those live in `private/catalog-pointers.ts`.
858
907
  */
859
908
  const defaultTheme = {
860
- backgroundColor: "var(--a8c-charts-color-background, #fff)",
861
- labelBackgroundColor: "var(--a8c-charts-color-label-background, transparent)",
862
- labelTextColor: "var(--a8c-charts-color-label-on-fill, #FFFFFF)",
863
- colors: [...SERIES_PALETTE_POINTERS],
864
- gridStyles: {
865
- stroke: "var(--a8c-charts-color-grid, #dbdbdb)",
866
- strokeWidth: 1
867
- },
909
+ gridStyles: { strokeWidth: 1 },
868
910
  tickLength: 4,
869
- gridColor: "",
870
- gridColorDark: "",
871
- xTickLineStyles: {
872
- stroke: "var(--a8c-charts-color-tick, #dbdbdb)",
873
- strokeWidth: 1
874
- },
875
- xAxisLineStyles: {
876
- stroke: "var(--a8c-charts-color-axis, #dbdbdb)",
877
- strokeWidth: 1
878
- },
911
+ xTickLineStyles: { strokeWidth: 1 },
912
+ xAxisLineStyles: { strokeWidth: 1 },
879
913
  legend: {
880
- labelStyles: { color: "var(--a8c-charts-color-label, #1e1e1e)" },
914
+ labelStyles: {},
881
915
  containerStyles: {},
882
916
  shapeStyles: []
883
917
  },
884
918
  seriesLineStyles: [],
885
919
  glyphs: [],
886
- svgLabelSmall: {
887
- fill: "var(--a8c-charts-color-label-axis, #1e1e1e)",
888
- fontFamily: "inherit"
889
- },
920
+ svgLabelSmall: { fontFamily: "inherit" },
890
921
  svgLabelBig: { fontFamily: "inherit" },
891
- annotationStyles: {
892
- label: {
893
- anchorLineStroke: "var(--a8c-charts-color-annotation, #1e1e1e)",
894
- backgroundFill: "var(--a8c-charts-color-surface, #fff)"
895
- },
896
- connector: { stroke: "var(--a8c-charts-color-annotation, #1e1e1e)" },
897
- circleSubject: {
898
- stroke: "transparent",
899
- fill: "var(--a8c-charts-color-annotation, #1e1e1e)",
900
- radius: 5
901
- }
902
- },
903
- geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)" },
904
- leaderboardChart: {
905
- rowGap: 12,
906
- columnGap: 4,
907
- labelSpacing: "xs",
908
- deltaColors: [
909
- "var(--a8c-charts-color-trend-down, #cc1818)",
910
- "var(--a8c-charts-color-trend-neutral, #707070)",
911
- "var(--a8c-charts-color-trend-up, #008030)"
912
- ]
913
- },
914
- conversionFunnelChart: {
915
- backgroundColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
916
- positiveChangeColor: "var(--a8c-charts-color-trend-up, #008030)",
917
- negativeChangeColor: "var(--a8c-charts-color-trend-down, #cc1818)"
918
- },
922
+ annotationStyles: {},
923
+ leaderboardChart: { labelSpacing: "xs" },
919
924
  lineChart: { lineStyles: { comparison: {
920
925
  strokeDasharray: "4 4",
921
926
  strokeLinecap: "square"
@@ -939,70 +944,9 @@ const defaultTheme = {
939
944
  }
940
945
  };
941
946
  //#endregion
942
- //#region src/providers/chart-context/private/with-catalog-pointers.ts
943
- /**
944
- * The pointer one palette slot is restored to.
945
- *
946
- * Unlike the other roles, this keeps the consumer's own color as the pointer's terminal literal instead of the catalog's. The literal is only ever reached where `getComputedStyle` resolves nothing — SSR and jsdom — and there the palette is the one field where falling back to the catalog default would be visible: every series would paint the same seeded blue. In a browser the slot resolves first, so a CSS declaration of it still outranks the consumer's `theme.colors`.
947
- *
948
- * @param index - The slot's zero-based index in `colors`.
949
- * @param value - The merged theme's entry for it.
950
- * @return The catalog pointer for that slot.
951
- */
952
- const seriesPointer = (index, value) => {
953
- const role = seriesRole(index + 1);
954
- if (typeof value === "string" && value.startsWith(`var(${role}`)) return value;
955
- if (typeof value !== "string" || value === "") return SERIES_PALETTE_POINTERS[index];
956
- return `var(${role}, ${value})`;
957
- };
958
- const CATALOG_RESTORE_FOR_ROLE = {
959
- ...Object.fromEntries(Array.from({ length: 5 }, (_, index) => [seriesRole(index + 1), (theme) => ({ colors: SERIES_PALETTE_POINTERS.map((_pointer, slot) => seriesPointer(slot, theme.colors?.[slot])) })])),
960
- "--a8c-charts-color-background": () => ({ backgroundColor: defaultTheme.backgroundColor }),
961
- "--a8c-charts-color-grid": (theme) => ({ gridStyles: {
962
- ...theme.gridStyles,
963
- stroke: defaultTheme.gridStyles.stroke
964
- } }),
965
- "--a8c-charts-color-axis": (theme) => ({ xAxisLineStyles: {
966
- ...theme.xAxisLineStyles,
967
- stroke: defaultTheme.xAxisLineStyles.stroke
968
- } }),
969
- "--a8c-charts-color-tick": (theme) => ({ xTickLineStyles: {
970
- ...theme.xTickLineStyles,
971
- stroke: defaultTheme.xTickLineStyles.stroke
972
- } }),
973
- "--a8c-charts-color-label-axis": (theme) => ({ svgLabelSmall: {
974
- ...theme.svgLabelSmall,
975
- fill: defaultTheme.svgLabelSmall.fill
976
- } }),
977
- "--a8c-charts-color-label-background": () => ({ labelBackgroundColor: defaultTheme.labelBackgroundColor }),
978
- "--a8c-charts-color-label-on-fill": () => ({ labelTextColor: defaultTheme.labelTextColor })
979
- };
980
- /**
981
- * Restores the mapped theme fields of `overriddenRoles` to their catalog pointer, so the theme-layer variable `themeOverrideVars` writes on the provider wrapper is the only carrier for an overridden role — CSS and the JS bridge (`useXYChartTheme`) then resolve it through the same cascade instead of visx reading a baked literal that can disagree with a closer CSS override.
982
- *
983
- * This covers roles `themeOverrideVars` deliberately left unpublished as well as the ones it published. A value that reads its own role is not publishable, but leaving visx the consumer's literal is what the whole mechanism exists to prevent: `theme={ { gridStyles: { stroke: 'var(--brand, var(--a8c-charts-color-grid, red))' } } }` would have CSS paint the catalog default while visx painted `--brand`.
984
- *
985
- * Every field outside the mapped roles is left exactly as `merged` provided it: those aren't published as theme-layer vars, so rewriting them would erase a consumer's override with no replacement carrier.
986
- *
987
- * @param merged - The consumer theme merged onto `defaultTheme` (`mergeThemes` output).
988
- * @param overriddenRoles - The catalog roles the consumer overrode, from `themeOverrideVars`.
989
- * @return A new theme object; `merged` is not mutated.
990
- */
991
- const withCatalogPointers = (merged, overriddenRoles) => {
992
- let result = merged;
993
- for (const role of overriddenRoles) {
994
- const restore = CATALOG_RESTORE_FOR_ROLE[role];
995
- if (restore) result = {
996
- ...result,
997
- ...restore(result)
998
- };
999
- }
1000
- return result;
1001
- };
1002
- //#endregion
1003
947
  //#region src/providers/chart-context/global-charts-provider.tsx
1004
948
  const GlobalChartsContext = createContext(null);
1005
- const GlobalChartsProvider = ({ children, theme }) => {
949
+ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
1006
950
  const [charts, setCharts] = useState(() => /* @__PURE__ */ new Map());
1007
951
  const [hiddenSeries, setHiddenSeries] = useState(() => /* @__PURE__ */ new Map());
1008
952
  const seededCharts = useRef(/* @__PURE__ */ new Set());
@@ -1012,11 +956,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1012
956
  wrapperRef.current = node;
1013
957
  setScopeNode(node);
1014
958
  }, []);
1015
- const { vars: overrideVars, roles: overriddenRoles } = useMemo(() => themeOverrideVars(theme), [theme]);
1016
- const providerTheme = useMemo(() => {
1017
- if (!theme) return defaultTheme;
1018
- return withCatalogPointers(mergeThemes(defaultTheme, theme), overriddenRoles);
1019
- }, [theme, overriddenRoles]);
959
+ const providerTheme = useMemo(() => theme ? mergeThemes(defaultTheme, theme) : defaultTheme, [theme]);
1020
960
  const [colorCache, setColorCache] = useState(() => ({
1021
961
  colors: [],
1022
962
  hues: [],
@@ -1032,7 +972,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1032
972
  const existingHslColors = [];
1033
973
  let minHue = 360;
1034
974
  let maxHue = 0;
1035
- for (const color of providerTheme.colors ?? SERIES_PALETTE_POINTERS) {
975
+ for (const color of SERIES_PALETTE_POINTERS) {
1036
976
  const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
1037
977
  if (normalizedColor.startsWith("#")) {
1038
978
  resolvedColors.push(normalizedColor);
@@ -1057,7 +997,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1057
997
  minHue,
1058
998
  maxHue
1059
999
  });
1060
- }, [providerTheme]);
1000
+ }, []);
1061
1001
  useEffect(() => {
1062
1002
  if (colorCache.colors.length > 0) setIsColorPaletteResolved(true);
1063
1003
  }, [colorCache]);
@@ -1149,12 +1089,17 @@ const GlobalChartsProvider = ({ children, theme }) => {
1149
1089
  const set = hiddenSeries.get(chartId);
1150
1090
  return set ? new Set(set) : /* @__PURE__ */ new Set();
1151
1091
  }, [hiddenSeries]);
1092
+ const formatting = useMemo(() => sanitizeFormatting({
1093
+ locale,
1094
+ timeZone
1095
+ }), [locale, timeZone]);
1152
1096
  const value = useMemo(() => ({
1153
1097
  charts,
1154
1098
  registerChart,
1155
1099
  unregisterChart,
1156
1100
  getChartData,
1157
1101
  theme: providerTheme,
1102
+ formatting,
1158
1103
  getElementStyles,
1159
1104
  toggleSeriesVisibility,
1160
1105
  setSeriesVisibility,
@@ -1170,6 +1115,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1170
1115
  unregisterChart,
1171
1116
  getChartData,
1172
1117
  providerTheme,
1118
+ formatting,
1173
1119
  getElementStyles,
1174
1120
  toggleSeriesVisibility,
1175
1121
  setSeriesVisibility,
@@ -1185,10 +1131,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1185
1131
  children: /* @__PURE__ */ jsx("div", {
1186
1132
  ref: setWrapperNode,
1187
1133
  className: CHART_SCOPE_CLASS,
1188
- style: {
1189
- display: "contents",
1190
- ...overrideVars
1191
- },
1134
+ style: { display: "contents" },
1192
1135
  children: /* @__PURE__ */ jsx(ChartScopeContext.Provider, {
1193
1136
  value: scopeNode,
1194
1137
  children
@@ -1252,6 +1195,39 @@ const useDeepMemo = (value) => {
1252
1195
  return ref.current;
1253
1196
  };
1254
1197
  //#endregion
1198
+ //#region src/providers/chart-context/private/catalog-pointers.ts
1199
+ const CATALOG_POINTERS = {
1200
+ background: "var(--a8c-charts-color-background, #fff)",
1201
+ labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
1202
+ grid: "var(--a8c-charts-color-grid, #dbdbdb)",
1203
+ axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
1204
+ tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
1205
+ axisY: "var(--a8c-charts-color-axis-y, none)",
1206
+ tickY: "var(--a8c-charts-color-tick-y, none)",
1207
+ annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
1208
+ surface: "var(--a8c-charts-color-surface, #fff)",
1209
+ surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
1210
+ trendUp: "var(--a8c-charts-color-trend-up, #008030)",
1211
+ trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
1212
+ series: SERIES_PALETTE_POINTERS
1213
+ };
1214
+ /**
1215
+ * The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
1216
+ * rides along as the base the theme and the per-datum styles merge onto.
1217
+ */
1218
+ const ANNOTATION_POINTERS = {
1219
+ label: {
1220
+ anchorLineStroke: CATALOG_POINTERS.annotation,
1221
+ backgroundFill: CATALOG_POINTERS.surface
1222
+ },
1223
+ connector: { stroke: CATALOG_POINTERS.annotation },
1224
+ circleSubject: {
1225
+ stroke: "transparent",
1226
+ fill: CATALOG_POINTERS.annotation,
1227
+ radius: 5
1228
+ }
1229
+ };
1230
+ //#endregion
1255
1231
  //#region src/providers/chart-scope/use-chart-scope-element.ts
1256
1232
  /**
1257
1233
  * Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
@@ -1278,13 +1254,34 @@ const useXYChartTheme = (data) => {
1278
1254
  return useMemo(() => {
1279
1255
  const resolve = createCssVariableResolver(scopeElement);
1280
1256
  const resolveColor = (value) => value ? resolve(value) ?? value : value;
1281
- const paletteColors = [...JSON.parse(seriesColorKey), ...theme.colors ?? []].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1282
- const htmlLabelColor = resolveColor(theme.svgLabelSmall?.fill);
1257
+ const paletteColors = [...JSON.parse(seriesColorKey), ...CATALOG_POINTERS.series].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1258
+ const resolvedLabelColor = resolveColor(CATALOG_POINTERS.labelAxis);
1259
+ const htmlLabelColor = resolvedLabelColor ? normalizeColorToHex(resolvedLabelColor) || resolvedLabelColor : resolvedLabelColor;
1283
1260
  return buildChartTheme({
1284
1261
  ...theme,
1262
+ gridColor: "",
1263
+ gridColorDark: "",
1285
1264
  colors: paletteColors,
1286
- backgroundColor: resolveColor(theme.backgroundColor),
1287
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0
1265
+ backgroundColor: resolveColor(CATALOG_POINTERS.background),
1266
+ htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1267
+ gridStyles: {
1268
+ ...theme.gridStyles,
1269
+ stroke: CATALOG_POINTERS.grid
1270
+ },
1271
+ xAxisLineStyles: {
1272
+ ...theme.xAxisLineStyles,
1273
+ stroke: CATALOG_POINTERS.axisX
1274
+ },
1275
+ xTickLineStyles: {
1276
+ ...theme.xTickLineStyles,
1277
+ stroke: CATALOG_POINTERS.tickX
1278
+ },
1279
+ yAxisLineStyles: { stroke: CATALOG_POINTERS.axisY },
1280
+ yTickLineStyles: { stroke: CATALOG_POINTERS.tickY },
1281
+ svgLabelSmall: {
1282
+ ...theme.svgLabelSmall,
1283
+ fill: CATALOG_POINTERS.labelAxis
1284
+ }
1288
1285
  });
1289
1286
  }, [
1290
1287
  theme,
@@ -1689,6 +1686,21 @@ const useGlobalChartsTheme = () => {
1689
1686
  return useContext(GlobalChartsContext)?.theme ?? defaultTheme;
1690
1687
  };
1691
1688
  //#endregion
1689
+ //#region src/providers/chart-context/hooks/use-chart-formatting.ts
1690
+ const RUNTIME_FORMATTING = {};
1691
+ /**
1692
+ * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
1693
+ *
1694
+ * Falls back to the runtime's own rather than throwing: charts render outside a
1695
+ * provider, and a host that sets neither gets exactly the browser-default
1696
+ * behavior this package had before the context existed.
1697
+ *
1698
+ * @return The host's formatting context.
1699
+ */
1700
+ const useChartFormatting = () => {
1701
+ return useContext(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
1702
+ };
1703
+ //#endregion
1692
1704
  //#region src/components/legend/utils/value-or-identity.ts
1693
1705
  /**
1694
1706
  * Returns an object's value if defined, or the object.
@@ -2126,15 +2138,311 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2126
2138
  });
2127
2139
  };
2128
2140
  //#endregion
2141
+ //#region src/components/tooltip/private/bounded-tooltip.tsx
2142
+ const DEFAULT_OFFSET = 10;
2143
+ const isClipping = (element) => {
2144
+ const { overflow, overflowX, overflowY } = getComputedStyle(element);
2145
+ return [
2146
+ overflow,
2147
+ overflowX,
2148
+ overflowY
2149
+ ].some((value) => value && value !== "visible");
2150
+ };
2151
+ const findClippingAncestor = (wrapper) => {
2152
+ let element = wrapper.parentElement;
2153
+ while (element && element !== document.body) {
2154
+ if (isClipping(element)) return element;
2155
+ element = element.parentElement;
2156
+ }
2157
+ return null;
2158
+ };
2159
+ /**
2160
+ * Where the box goes, in wrapper coordinates. Below and to the right of the
2161
+ * anchor; flipped to the other side when that side clips less, as visx's
2162
+ * `TooltipWithBounds` decides it; then clamped so the box stays inside
2163
+ * `bounds` whenever it fits at all.
2164
+ *
2165
+ * @param params - Anchor, offsets, the box size and the bounds to keep inside.
2166
+ * @param params.left - Anchor x, in wrapper coordinates.
2167
+ * @param params.top - Anchor y, in wrapper coordinates.
2168
+ * @param params.offsetLeft - Gap between the anchor and the box, horizontally.
2169
+ * @param params.offsetTop - Gap between the anchor and the box, vertically.
2170
+ * @param params.box - Rendered size of the box.
2171
+ * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2172
+ * @return The box's top-left corner, rounded to whole pixels.
2173
+ */
2174
+ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) => {
2175
+ const rightX = left + offsetLeft;
2176
+ const leftX = left - offsetLeft - box.width;
2177
+ const rightOverflow = rightX + box.width - bounds.right;
2178
+ const leftOverflow = bounds.left - leftX;
2179
+ let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
2180
+ const downY = top + offsetTop;
2181
+ const upY = top - offsetTop - box.height;
2182
+ const downOverflow = downY + box.height - bounds.bottom;
2183
+ const upOverflow = bounds.top - upY;
2184
+ let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2185
+ x = Math.min(Math.max(x, bounds.left), Math.max(bounds.left, bounds.right - box.width));
2186
+ y = Math.min(Math.max(y, bounds.top), Math.max(bounds.top, bounds.bottom - box.height));
2187
+ return {
2188
+ x: Math.round(x),
2189
+ y: Math.round(y)
2190
+ };
2191
+ };
2192
+ /**
2193
+ * visx's `Tooltip`, positioned like its `TooltipWithBounds` but kept inside the
2194
+ * nearest clipping ancestor — or the viewport when there is none — rather than
2195
+ * inside its own parent. Rendered in-tree, a tooltip's parent is the chart
2196
+ * wrapper, which is often narrower than the box; measuring against the parent
2197
+ * alone would let the box spill into an `overflow: hidden` card and be cut off.
2198
+ *
2199
+ * Re-measures on every render, so a box whose content changes width between
2200
+ * two hovers is placed for its current size.
2201
+ *
2202
+ * @param props - visx `Tooltip` props.
2203
+ * @param props.left - Anchor x, in wrapper coordinates.
2204
+ * @param props.top - Anchor y, in wrapper coordinates.
2205
+ * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2206
+ * @param props.offsetTop - Gap between the anchor and the box, vertically.
2207
+ * @param props.style - Box styles; visx's defaults unless `unstyled`.
2208
+ * @param props.unstyled - Skip `style` and leave the box bare.
2209
+ * @param props.children - Box content.
2210
+ * @return The tooltip box.
2211
+ */
2212
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, ...rest }) => {
2213
+ const nodeRef = useRef(null);
2214
+ const clipRef = useRef(null);
2215
+ const [position, setPosition] = useState(null);
2216
+ useLayoutEffect(() => {
2217
+ const node = nodeRef.current;
2218
+ const wrapper = node?.parentElement;
2219
+ if (!node || !wrapper) return;
2220
+ if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2221
+ wrapper,
2222
+ clip: findClippingAncestor(wrapper)
2223
+ };
2224
+ const own = node.getBoundingClientRect();
2225
+ const wrapperRect = wrapper.getBoundingClientRect();
2226
+ const clipRect = clipRef.current.clip?.getBoundingClientRect() ?? {
2227
+ left: 0,
2228
+ top: 0,
2229
+ right: window.innerWidth,
2230
+ bottom: window.innerHeight
2231
+ };
2232
+ const next = getBoundedPosition({
2233
+ left,
2234
+ top,
2235
+ offsetLeft,
2236
+ offsetTop,
2237
+ box: {
2238
+ width: own.width,
2239
+ height: own.height
2240
+ },
2241
+ bounds: {
2242
+ left: clipRect.left - wrapperRect.left,
2243
+ top: clipRect.top - wrapperRect.top,
2244
+ right: clipRect.right - wrapperRect.left,
2245
+ bottom: clipRect.bottom - wrapperRect.top
2246
+ }
2247
+ });
2248
+ setPosition((current) => current && current.x === next.x && current.y === next.y ? current : next);
2249
+ }, [
2250
+ left,
2251
+ top,
2252
+ offsetLeft,
2253
+ offsetTop,
2254
+ children
2255
+ ]);
2256
+ const x = position?.x ?? left + offsetLeft;
2257
+ const y = position?.y ?? top + offsetTop;
2258
+ return /* @__PURE__ */ jsx(Tooltip, {
2259
+ ref: nodeRef,
2260
+ style: {
2261
+ position: "absolute",
2262
+ left: 0,
2263
+ top: 0,
2264
+ transform: `translate(${x}px, ${y}px)`,
2265
+ ...!unstyled && style
2266
+ },
2267
+ ...rest,
2268
+ children
2269
+ });
2270
+ };
2271
+ //#endregion
2272
+ //#region src/components/tooltip/xy-chart-tooltip.tsx
2273
+ const CROSSHAIR_STROKE_WIDTH = 1.5;
2274
+ const DEFAULT_GLYPH_RADIUS = 4;
2275
+ const FALLBACK_COLOR = "#222";
2276
+ const DEFAULT_TOOLTIP_Z_INDEX = 3;
2277
+ const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
2278
+ "scroll",
2279
+ "debounce",
2280
+ "resizeObserverPolyfill"
2281
+ ]);
2282
+ const isValidNumber = (value) => typeof value === "number" && Number.isFinite(value);
2283
+ const scaleBandwidth = (scale) => {
2284
+ const bandwidth = scale?.bandwidth;
2285
+ return typeof bandwidth === "function" ? Number(bandwidth()) : 0;
2286
+ };
2287
+ const DefaultGlyph$1 = ({ x, y, size, color, glyphStyle, seriesKey }) => {
2288
+ const { theme } = useContext(DataContext) || {};
2289
+ return /* @__PURE__ */ jsx("circle", {
2290
+ cx: x,
2291
+ cy: y,
2292
+ r: size,
2293
+ fill: color,
2294
+ stroke: theme?.backgroundColor,
2295
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2296
+ paintOrder: "fill",
2297
+ ...glyphStyle
2298
+ });
2299
+ };
2300
+ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ jsx(DefaultGlyph$1, {
2301
+ seriesKey: key,
2302
+ ...props
2303
+ }, key);
2304
+ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, renderGlyph = defaultRenderGlyph$1, glyphStyle, snapTooltipToDatumX = false, snapTooltipToDatumY = false, showVerticalCrosshair = false, showHorizontalCrosshair = false, showDatumGlyph = false, showSeriesGlyphs = false, verticalCrosshairStyle, horizontalCrosshairStyle, detectBounds = true, zIndex = DEFAULT_TOOLTIP_Z_INDEX, ...rest }) => {
2305
+ const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2306
+ const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = useContext(DataContext) || {};
2307
+ const tooltipContent = renderTooltip ? renderTooltip({
2308
+ ...tooltipContext,
2309
+ colorScale
2310
+ }) : null;
2311
+ if (tooltipContent == null) return null;
2312
+ const scaleX = xScale;
2313
+ const scaleY = yScale;
2314
+ const getDatumLeftTop = (key, datum) => {
2315
+ const entry = dataRegistry?.get(key);
2316
+ return {
2317
+ left: scaleX && entry?.xAccessor ? Number(scaleX(entry.xAccessor(datum))) + scaleBandwidth(xScale) / 2 : void 0,
2318
+ top: scaleY && entry?.yAccessor ? Number(scaleY(entry.yAccessor(datum))) + scaleBandwidth(yScale) / 2 : void 0
2319
+ };
2320
+ };
2321
+ const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
2322
+ let { tooltipLeft, tooltipTop } = tooltipContext;
2323
+ if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY)) {
2324
+ const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
2325
+ if (snapTooltipToDatumX && isValidNumber(left)) tooltipLeft = left;
2326
+ if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
2327
+ }
2328
+ const glyphs = [];
2329
+ if (showDatumGlyph || showSeriesGlyphs) {
2330
+ const size = Number(glyphStyle?.radius ?? DEFAULT_GLYPH_RADIUS);
2331
+ const labelColor = theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2332
+ const fallbackColor = showSeriesGlyphs ? labelColor : theme?.gridStyles?.stroke ?? labelColor;
2333
+ let entries = [];
2334
+ if (showSeriesGlyphs) entries = Object.values(tooltipContext.tooltipData?.datumByKey ?? {});
2335
+ else if (nearestDatum) entries = [nearestDatum];
2336
+ for (const { key, datum, index } of entries) {
2337
+ const { left, top } = getDatumLeftTop(key, datum);
2338
+ if (!isValidNumber(left) || !isValidNumber(top)) continue;
2339
+ glyphs.push(/* @__PURE__ */ jsx("g", {
2340
+ className: "visx-tooltip-glyph",
2341
+ transform: `translate(${left}, ${top})`,
2342
+ children: renderGlyph({
2343
+ key,
2344
+ color: colorScale?.(key) ?? fallbackColor,
2345
+ datum,
2346
+ index,
2347
+ size,
2348
+ x: 0,
2349
+ y: 0,
2350
+ glyphStyle,
2351
+ isNearestDatum: nearestDatum?.key === key
2352
+ })
2353
+ }, key));
2354
+ }
2355
+ }
2356
+ const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2357
+ const marginTop = margin?.top ?? 0;
2358
+ const marginLeft = margin?.left ?? 0;
2359
+ const TooltipComponent = detectBounds ? BoundedTooltip : Tooltip;
2360
+ const boxStyle = {
2361
+ ...defaultStyles,
2362
+ zIndex,
2363
+ background: theme?.backgroundColor ?? "white",
2364
+ boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2365
+ ...theme?.htmlLabel
2366
+ };
2367
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("g", {
2368
+ className: "visx-tooltip-overlay",
2369
+ pointerEvents: "none",
2370
+ children: [
2371
+ showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ jsx("line", {
2372
+ className: "visx-crosshair visx-crosshair-vertical",
2373
+ x1: tooltipLeft,
2374
+ x2: tooltipLeft,
2375
+ y1: marginTop,
2376
+ y2: marginTop + innerHeight,
2377
+ stroke: crosshairStroke,
2378
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2379
+ ...verticalCrosshairStyle
2380
+ }),
2381
+ showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ jsx("line", {
2382
+ className: "visx-crosshair visx-crosshair-horizontal",
2383
+ x1: marginLeft,
2384
+ x2: marginLeft + innerWidth,
2385
+ y1: tooltipTop,
2386
+ y2: tooltipTop,
2387
+ stroke: crosshairStroke,
2388
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2389
+ ...horizontalCrosshairStyle
2390
+ }),
2391
+ glyphs
2392
+ ]
2393
+ }), container && createPortal(/* @__PURE__ */ jsx(TooltipComponent, {
2394
+ left: tooltipLeft,
2395
+ top: tooltipTop,
2396
+ style: boxStyle,
2397
+ applyPositionStyle: true,
2398
+ ...tooltipProps,
2399
+ children: tooltipContent
2400
+ }), container)] });
2401
+ };
2402
+ /**
2403
+ * In-tree replacement for `@visx/xychart`'s `Tooltip`.
2404
+ *
2405
+ * visx renders its tooltip box, glyphs and crosshairs through portals appended
2406
+ * to `document.body`. That puts them in the page's root stacking context, where
2407
+ * they paint above every sticky or fixed element that lives inside a nested
2408
+ * stacking context, and no z-index on that element can change the order. This
2409
+ * component draws the glyphs and crosshairs straight into the chart SVG and
2410
+ * renders the tooltip box into the SVG's parent element, so all of them stack
2411
+ * as ordinary descendants of the chart.
2412
+ *
2413
+ * Render it as a child of `XYChart`. The element wrapping that `XYChart` must
2414
+ * be `position: relative` with the SVG at its origin, and `isolation: isolate`:
2415
+ * the box is placed with the SVG-local coordinates visx reports, and the
2416
+ * isolation keeps the box's `zIndex` from competing with page chrome. The box
2417
+ * flips and clamps to stay inside the nearest ancestor that clips its overflow
2418
+ * (or the viewport), so it may extend past the chart wrapper but is never cut
2419
+ * off unless that ancestor is smaller than the box itself.
2420
+ *
2421
+ * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2422
+ * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
2423
+ */
2424
+ const XyChartTooltip = (props) => {
2425
+ const tooltipContext = useContext(TooltipContext);
2426
+ const [container, setContainer] = useState(null);
2427
+ const anchorRef = useCallback((node) => {
2428
+ setContainer(node?.ownerSVGElement?.parentElement ?? null);
2429
+ }, []);
2430
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("g", { ref: anchorRef }), tooltipContext?.tooltipOpen && /* @__PURE__ */ jsx(XyChartTooltipContent, {
2431
+ ...props,
2432
+ tooltipContext,
2433
+ container
2434
+ })] });
2435
+ };
2436
+ //#endregion
2129
2437
  //#region src/components/tooltip/accessible-tooltip.tsx
2130
2438
  const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2131
2439
  const tooltipContext = useContext(TooltipContext);
2132
2440
  const scopeElement = useChartScopeElement();
2133
- const gridStroke = useGlobalChartsTheme().gridStyles?.stroke;
2134
2441
  const crosshairStroke = useMemo(() => {
2135
- const stroke = gridStroke ? resolveCssVariable(gridStroke, scopeElement) : null;
2442
+ const stroke = resolveCssVariable(CATALOG_POINTERS.grid, scopeElement);
2136
2443
  return stroke ? { stroke } : void 0;
2137
- }, [gridStroke, scopeElement]);
2444
+ }, [scopeElement]);
2445
+ const standaloneScopeClass = useStandaloneScopeClass();
2138
2446
  const tooltipData = useMemo(() => {
2139
2447
  if (mode !== "individual") return [];
2140
2448
  if (series.length === 0) return [];
@@ -2195,11 +2503,11 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2195
2503
  tabIndex: -1,
2196
2504
  role: "tooltip",
2197
2505
  "aria-atomic": "true",
2198
- className: clsx(CHART_SCOPE_CLASS, keyboardFocusedClassName),
2506
+ className: clsx(standaloneScopeClass, keyboardFocusedClassName),
2199
2507
  children: tooltipContent
2200
2508
  }, `chart-tooltip-${selectedIndex}`);
2201
2509
  return /* @__PURE__ */ jsx("div", {
2202
- className: CHART_SCOPE_CLASS,
2510
+ className: standaloneScopeClass,
2203
2511
  role: "tooltip",
2204
2512
  "aria-live": "polite",
2205
2513
  children: tooltipContent
@@ -2209,9 +2517,10 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2209
2517
  renderTooltip,
2210
2518
  selectedIndex,
2211
2519
  tooltipRef,
2212
- keyboardFocusedClassName
2520
+ keyboardFocusedClassName,
2521
+ standaloneScopeClass
2213
2522
  ]);
2214
- return /* @__PURE__ */ jsx(Tooltip, {
2523
+ return /* @__PURE__ */ jsx(XyChartTooltip, {
2215
2524
  ...props,
2216
2525
  verticalCrosshairStyle: {
2217
2526
  ...crosshairStroke,
@@ -2559,72 +2868,90 @@ const getCurveType = (type, smoothing) => {
2559
2868
  default: return curveLinear;
2560
2869
  }
2561
2870
  };
2562
- const dateTimeFormat = (options) => {
2563
- let formatter;
2564
- return (timestamp) => {
2565
- if (!Number.isFinite(Number(timestamp))) return new Date(timestamp).toLocaleDateString();
2566
- formatter = formatter ?? new Intl.DateTimeFormat(void 0, options);
2567
- return formatter.format(timestamp);
2568
- };
2569
- };
2570
- const formatYearTick = dateTimeFormat({ year: "numeric" });
2571
- const formatDateTick = dateTimeFormat({
2871
+ const YEAR_TICK = { year: "numeric" };
2872
+ const DATE_TICK = {
2572
2873
  month: "short",
2573
2874
  day: "numeric"
2574
- });
2575
- const formatHourTick = dateTimeFormat({
2576
- hour: "numeric",
2577
- hour12: true
2578
- });
2579
- const formatMonthTick = dateTimeFormat({ month: "short" });
2875
+ };
2876
+ const HOUR_TICK = { hour: "numeric" };
2877
+ const MONTH_TICK = { month: "short" };
2580
2878
  const boundaryFormat = (isAnchor, atBoundary, between) => {
2581
2879
  const format = (timestamp) => isAnchor(new Date(timestamp)) ? atBoundary(timestamp) : between(timestamp);
2582
2880
  format.isAnchor = isAnchor;
2583
2881
  return format;
2584
2882
  };
2585
- const formatDateOrHourTick = boundaryFormat((date) => date.getHours() === 0 && date.getMinutes() === 0, formatDateTick, formatHourTick);
2586
- const formatMonthOrYearTick = boundaryFormat((date) => date.getMonth() === 0, formatYearTick, formatMonthTick);
2883
+ const tickFormats = (formatting) => {
2884
+ const clock = createZonedClock(formatting.timeZone);
2885
+ const year = createDateFormatter(YEAR_TICK, formatting);
2886
+ const date = createDateFormatter(DATE_TICK, formatting);
2887
+ const hour = createDateFormatter(HOUR_TICK, formatting);
2888
+ const month = createDateFormatter(MONTH_TICK, formatting);
2889
+ const datedTick = (timestamp) => date(timestamp);
2890
+ datedTick.anchorsAtLabelChange = true;
2891
+ return {
2892
+ year,
2893
+ date: datedTick,
2894
+ dateOrHour: boundaryFormat((value) => clock(value).hour === 0, date, hour),
2895
+ hour,
2896
+ monthOrYear: boundaryFormat((value) => clock(value).month === 1, year, month)
2897
+ };
2898
+ };
2587
2899
  const getSpan = (sortedData) => {
2588
- const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => first !== void 0 && last !== void 0);
2900
+ const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => Number.isFinite(Number(first)) && Number.isFinite(Number(last)));
2589
2901
  if (!bounds.length) return null;
2590
2902
  return {
2591
2903
  minX: Math.min(...bounds.map(([first]) => Number(first))),
2592
2904
  maxX: Math.max(...bounds.map(([, last]) => Number(last)))
2593
2905
  };
2594
2906
  };
2595
- const getPointSpacingInHours = (sortedData) => {
2596
- return sortedData.reduce((spacing, datom) => datom.data.reduce((seriesSpacing, point, index) => {
2597
- const previous = datom.data[index - 1];
2598
- if (previous?.date === void 0 || point?.date === void 0) return seriesSpacing;
2599
- return Math.min(seriesSpacing, Math.abs(differenceInHours(point.date, previous.date)));
2600
- }, spacing), Number.POSITIVE_INFINITY);
2601
- };
2602
- const SUB_DAILY_SPACING_HOURS = 23;
2603
- const MONTHLY_SPACING_HOURS = 672;
2604
- const getBucketResolution = (sortedData, tickResolution) => {
2605
- if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
2606
- const spacingInHours = getPointSpacingInHours(sortedData);
2607
- if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
2608
- if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
2609
- return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
2610
- };
2611
- const getFormatter = (sortedData, tickResolution) => {
2907
+ /**
2908
+ * The most informative tick format for a bucket resolution and the span on screen.
2909
+ *
2910
+ * @param sortedData - Series as returned by `useChartDataTransform`.
2911
+ * @param tickResolution - Caller-declared bucket resolution, when known.
2912
+ * @param formatting - Host locale and time zone.
2913
+ * @param domain - The effective scale domain, or undefined for the data's own extent.
2914
+ * @return The formatter the axis labels its ticks with.
2915
+ */
2916
+ const getFormatter = (sortedData, tickResolution, formatting = {}, domain) => {
2917
+ const format = tickFormats(formatting);
2612
2918
  const resolution = getBucketResolution(sortedData, tickResolution);
2613
- if (resolution === "year") return formatYearTick;
2614
- if (resolution === "month") return formatMonthOrYearTick;
2615
- const span = getSpan(sortedData);
2616
- if (!span) return formatDateTick;
2919
+ if (resolution === "year") return format.year;
2920
+ if (resolution === "month") return format.monthOrYear;
2921
+ const span = domain ? {
2922
+ minX: domain[0].getTime(),
2923
+ maxX: domain[1].getTime()
2924
+ } : getSpan(sortedData);
2925
+ if (!span) return format.date;
2617
2926
  const diffInHours = Math.abs(differenceInHours(span.maxX, span.minX));
2618
2927
  if (resolution === "hour") {
2619
- if (diffInHours <= 24) return formatHourTick;
2620
- if (diffInHours <= 168) return formatDateOrHourTick;
2928
+ if (diffInHours < 24) return format.hour;
2929
+ if (diffInHours <= 168) return format.dateOrHour;
2621
2930
  }
2622
- return Math.abs(differenceInYears(span.maxX, span.minX)) <= 1 ? formatDateTick : formatYearTick;
2931
+ return Math.abs(differenceInYears(span.maxX, span.minX)) <= 1 ? format.date : format.year;
2932
+ };
2933
+ /**
2934
+ * The instants a set of series spans, as a scale domain.
2935
+ *
2936
+ * @param sortedData - Series as returned by `useChartDataTransform`.
2937
+ * @return Earliest and latest dated point, or undefined when nothing is dated.
2938
+ */
2939
+ const getSeriesExtent = (sortedData) => {
2940
+ const span = getSpan(sortedData);
2941
+ return span ? [new Date(span.minX), new Date(span.maxX)] : void 0;
2942
+ };
2943
+ const getAnchorIndices = (domain, tickFormatter, labels) => {
2944
+ if (tickFormatter.anchorsAtLabelChange) return labels.reduce((indices, label, index) => {
2945
+ if (index === 0 || label !== labels[index - 1]) indices.push(index);
2946
+ return indices;
2947
+ }, []);
2948
+ const { isAnchor } = tickFormatter;
2949
+ if (!isAnchor) return [];
2950
+ return domain.reduce((indices, date, index) => {
2951
+ if (isAnchor(date)) indices.push(index);
2952
+ return indices;
2953
+ }, []);
2623
2954
  };
2624
- const getAnchorIndices = (domain, isAnchor) => domain.reduce((indices, date, index) => {
2625
- if (isAnchor(date)) indices.push(index);
2626
- return indices;
2627
- }, []);
2628
2955
  const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
2629
2956
  const steps = /* @__PURE__ */ new Set();
2630
2957
  for (let count = maxTicks; count > 1; count--) {
@@ -2639,26 +2966,25 @@ const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
2639
2966
  return steps;
2640
2967
  };
2641
2968
  /**
2642
- * Tick values for a band time axis.
2969
+ * Tick values sampled by index, for the bar chart's band axis and for evenly
2970
+ * spaced points on a continuous one.
2643
2971
  *
2644
- * visx samples a band domain by index from offset zero, blind to which labels
2645
- * carry a boundary and without collapsing repeats — so the tick that prints the
2646
- * year or the date often isn't sampled at all, and a long series can show the
2647
- * same label twice. Choose the values instead: sweep the evenly spaced
2648
- * candidates, including those that step from anchor to anchor, and keep the one
2649
- * that reaches the most anchors without thinning the axis or putting two
2650
- * identical labels side by side.
2972
+ * visx samples by index from offset zero, blind to which labels carry a boundary
2973
+ * and without collapsing repeats — so the tick that prints the year or the date
2974
+ * often isn't sampled at all, and a long series can show the same label twice.
2975
+ * Choose the values instead: sweep the evenly spaced candidates, including those
2976
+ * that step from anchor to anchor, and keep the one that reaches the most anchors
2977
+ * without thinning the axis or putting two identical labels side by side.
2651
2978
  *
2652
- * @param domain - Band domain, in axis order.
2979
+ * @param domain - Buckets in axis order.
2653
2980
  * @param tickFormatter - Formatter the axis will render these values with.
2654
2981
  * @param maxTicks - Most ticks the axis should carry.
2655
2982
  * @return Values to hand the axis as `tickValues`.
2656
2983
  */
2657
2984
  const getBandTickValues = (domain, tickFormatter, maxTicks) => {
2658
2985
  if (!domain.length) return [];
2659
- const { isAnchor } = tickFormatter;
2660
- const anchorIndices = isAnchor ? getAnchorIndices(domain, isAnchor) : [];
2661
2986
  const domainLabels = domain.map((date) => tickFormatter(date.getTime()));
2987
+ const anchorIndices = getAnchorIndices(domain, tickFormatter, domainLabels);
2662
2988
  const candidates = [];
2663
2989
  const consider = (indices) => {
2664
2990
  if (!indices.length || indices.length > maxTicks) return;
@@ -2670,15 +2996,108 @@ const getBandTickValues = (domain, tickFormatter, maxTicks) => {
2670
2996
  for (let index = offset; index < domain.length; index += step) indices.push(index);
2671
2997
  consider(indices);
2672
2998
  }
2673
- for (let stride = 1; stride <= anchorIndices.length; stride++) consider(anchorIndices.filter((_, position) => position % stride === 0));
2999
+ const minStride = Math.max(1, Math.ceil(anchorIndices.length / maxTicks));
3000
+ for (let stride = minStride; stride <= anchorIndices.length; stride++) {
3001
+ const stepped = [];
3002
+ for (let position = 0; position < anchorIndices.length; position += stride) stepped.push(anchorIndices[position]);
3003
+ consider(stepped);
3004
+ }
2674
3005
  if (!candidates.length) return [domain[0]];
2675
3006
  const densest = candidates.reduce((most, indices) => Math.max(most, indices.length), 0);
2676
- const anchorsIn = (indices) => isAnchor ? indices.filter((index) => isAnchor(domain[index])).length : 0;
3007
+ const anchorIndexSet = new Set(anchorIndices);
3008
+ const anchorsIn = (indices) => indices.filter((index) => anchorIndexSet.has(index)).length;
2677
3009
  return candidates.filter((indices) => indices.length >= densest - 1).reduce((chosen, indices) => {
2678
3010
  const gain = anchorsIn(indices) - anchorsIn(chosen);
2679
3011
  return gain > 0 || gain === 0 && indices.length > chosen.length ? indices : chosen;
2680
3012
  }).map((index) => domain[index]);
2681
3013
  };
3014
+ /**
3015
+ * The most x-axis ticks a chart of this width has room for.
3016
+ *
3017
+ * @param chartWidth - Chart width in pixels.
3018
+ * @return A tick budget of at least one.
3019
+ */
3020
+ const getMaxTicksForWidth = (chartWidth) => Math.max(1, Math.floor(chartWidth / X_TICK_WIDTH));
3021
+ const MIN_GAP_IN_LABELS = .9;
3022
+ const nearestValue = (sorted, target) => {
3023
+ let low = 0;
3024
+ let high = sorted.length - 1;
3025
+ while (low < high) {
3026
+ const middle = Math.floor((low + high) / 2);
3027
+ if (sorted[middle] < target) low = middle + 1;
3028
+ else high = middle;
3029
+ }
3030
+ const above = sorted[low];
3031
+ const below = low > 0 ? sorted[low - 1] : above;
3032
+ return Math.abs(above - target) <= Math.abs(below - target) ? above : below;
3033
+ };
3034
+ const getPositionTickValues = (points, tickFormatter, maxTicks, axis) => {
3035
+ const times = points.map((point) => point.getTime());
3036
+ const { first, last } = axis;
3037
+ if (maxTicks <= 1 || times.length === 1 || first === last) return [points[0]];
3038
+ const step = (last - first) / (maxTicks - 1);
3039
+ const labels = points.map((point) => tickFormatter(point.getTime()));
3040
+ const minGap = (last - first) / maxTicks * MIN_GAP_IN_LABELS;
3041
+ const anchors = getAnchorIndices(points, tickFormatter, labels).map((index) => times[index]);
3042
+ const chosen = [];
3043
+ let lastLabel = null;
3044
+ for (let index = 0; index < maxTicks; index++) {
3045
+ const target = first + index * step;
3046
+ const anchor = anchors.length ? nearestValue(anchors, target) : null;
3047
+ let tick = anchor !== null && Math.abs(anchor - target) <= step / 2 ? anchor : null;
3048
+ if (tick === null) {
3049
+ const point = nearestValue(times, target);
3050
+ tick = Math.abs(point - target) <= step / 2 ? point : target;
3051
+ }
3052
+ const previous = chosen[chosen.length - 1];
3053
+ if (previous !== void 0 && (tick <= previous || tick - previous < minGap)) continue;
3054
+ const label = tickFormatter(tick);
3055
+ if (label === lastLabel) continue;
3056
+ chosen.push(tick);
3057
+ lastLabel = label;
3058
+ }
3059
+ return chosen.map((timestamp) => new Date(timestamp));
3060
+ };
3061
+ const isIndexSamplingUnusable = (ticks, points, maxTicks, axis) => {
3062
+ if (ticks.length < 2 || points.length < 2) return false;
3063
+ const span = axis.last - axis.first;
3064
+ if (!span) return false;
3065
+ return ticks.slice(1).reduce((nearest, tick, index) => Math.min(nearest, tick.getTime() - ticks[index].getTime()), Infinity) < span / maxTicks * MIN_GAP_IN_LABELS || ticks.length < Math.min(maxTicks, points.length) / 3;
3066
+ };
3067
+ /**
3068
+ * Tick values for a continuous time axis, chosen from the points it draws.
3069
+ *
3070
+ * Sampled by index while the points are evenly spaced, which keeps the axis on
3071
+ * the calendar boundaries `getBandTickValues` steers towards, and by position
3072
+ * once they are not, where an index stride crowds one end of the scale.
3073
+ *
3074
+ * Values are taken from the data wherever there is one to take, so a local time
3075
+ * that does not exist cannot become a tick and DST needs no special case. Only a
3076
+ * target stranded in a gap is constructed, as a plain instant.
3077
+ *
3078
+ * @param sortedData - Series as returned by `useChartDataTransform`.
3079
+ * @param domain - The effective scale domain, or undefined for all data.
3080
+ * @param tickFormatter - The formatter the ticks will be labeled with.
3081
+ * @param maxTicks - The most ticks the axis has room for.
3082
+ * @return Tick dates, or null when no series carries a usable date.
3083
+ */
3084
+ const getTimeAxisTickValues = (sortedData, domain, tickFormatter, maxTicks) => {
3085
+ const byTimestamp = /* @__PURE__ */ new Map();
3086
+ for (const series of sortedData) for (const point of series.data) {
3087
+ const date = point.date;
3088
+ if (date instanceof Date && Number.isFinite(date.getTime())) byTimestamp.set(date.getTime(), date);
3089
+ }
3090
+ if (!byTimestamp.size) return null;
3091
+ const min = domain?.[0]?.getTime();
3092
+ const max = domain?.[1]?.getTime();
3093
+ const visible = [...byTimestamp.keys()].sort((a, b) => a - b).filter((timestamp) => (min === void 0 || timestamp >= min) && (max === void 0 || timestamp <= max)).map((timestamp) => byTimestamp.get(timestamp));
3094
+ const axis = {
3095
+ first: min ?? visible[0].getTime(),
3096
+ last: max ?? visible[visible.length - 1].getTime()
3097
+ };
3098
+ const ticks = getBandTickValues(visible, tickFormatter, maxTicks);
3099
+ return isIndexSamplingUnusable(ticks, visible, maxTicks, axis) ? getPositionTickValues(visible, tickFormatter, maxTicks, axis) : ticks;
3100
+ };
2682
3101
  const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
2683
3102
  const span = getSpan(data);
2684
3103
  if (!span) return 1;
@@ -2693,7 +3112,43 @@ const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
2693
3112
  if (ticks.some((tick, idx) => idx > 0 && tick === ticks[idx - 1])) continue;
2694
3113
  return ticks.length;
2695
3114
  }
2696
- return secondBestGuess;
3115
+ return Math.max(1, secondBestGuess);
3116
+ };
3117
+ //#endregion
3118
+ //#region src/charts/private/time-axis-options.ts
3119
+ const toDateDomain = (domain) => domain ? [new Date(domain[0]), new Date(domain[1])] : void 0;
3120
+ /**
3121
+ * The x-axis options for a continuous time chart, shared by LineChart and AreaChart.
3122
+ *
3123
+ * Kept in one place so the tick-selection logic isn't duplicated between them.
3124
+ *
3125
+ * @param args - Named arguments.
3126
+ * @param args.dataSorted - Series as returned by `useChartDataTransform`.
3127
+ * @param args.width - Chart width in pixels, which bounds how many ticks fit.
3128
+ * @param args.axisOptions - The caller's `options.axis.x`.
3129
+ * @param args.scaleDomain - The caller's `options.xScale.domain`.
3130
+ * @param args.zoomDomain - The chart's current zoom window, when zoomed.
3131
+ * @param args.formatting - Host locale and time zone.
3132
+ * @param args.isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it. Defaults to all of them.
3133
+ * @return Options ready to spread into the chart's `axis.x`; `orientation` is always set.
3134
+ */
3135
+ const buildTimeAxisOptions = ({ dataSorted, width, axisOptions, scaleDomain, zoomDomain, formatting, isSeriesRendered }) => {
3136
+ const { tickResolution, tickFormat, tickValues: callerTickValues, numTicks: callerNumTicks, orientation: callerOrientation, display: callerDisplay, ...rest } = axisOptions ?? {};
3137
+ const rendered = isSeriesRendered ? dataSorted.filter(isSeriesRendered) : dataSorted;
3138
+ const effectiveDomain = zoomDomain ?? toDateDomain(scaleDomain) ?? getSeriesExtent(rendered);
3139
+ const ownFormatter = getFormatter(rendered, tickResolution, formatting, effectiveDomain);
3140
+ const formatter = tickFormat || ownFormatter;
3141
+ const ownTickValues = tickFormat || callerTickValues ? null : getTimeAxisTickValues(rendered, effectiveDomain, ownFormatter, callerNumTicks ?? getMaxTicksForWidth(width));
3142
+ const tickValues = callerTickValues ?? (ownTickValues?.length ? ownTickValues : void 0);
3143
+ const numTicks = callerNumTicks ?? (tickValues ? void 0 : guessOptimalNumTicks(rendered, width, formatter));
3144
+ return {
3145
+ orientation: callerOrientation ?? "bottom",
3146
+ tickFormat: formatter,
3147
+ display: callerDisplay ?? true,
3148
+ ...rest,
3149
+ ...tickValues ? { tickValues } : {},
3150
+ ...numTicks === void 0 ? {} : { numTicks }
3151
+ };
2697
3152
  };
2698
3153
  //#endregion
2699
3154
  //#region src/charts/private/with-responsive/with-responsive.module.scss
@@ -2974,6 +3429,9 @@ function ZoomResetButton({ onClick }) {
2974
3429
  });
2975
3430
  }
2976
3431
  //#endregion
3432
+ //#region src/charts/private/xy-plot/xy-plot.module.scss
3433
+ var xy_plot_module_default = { "xy-plot": "a8ccharts-bIyhgG-xy-plot" };
3434
+ //#endregion
2977
3435
  //#region src/charts/line-chart/line-chart.module.scss
2978
3436
  var line_chart_module_default = {
2979
3437
  "line-chart": "a8ccharts-inuQka-line-chart",
@@ -3214,12 +3672,14 @@ const getVerticalAnchor = (subjectType, isFlippedVertically, y, yMax, height) =>
3214
3672
  };
3215
3673
  const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", styles: datumStyles, testId, renderLabel, renderLabelPopover }) => {
3216
3674
  const providerTheme = useGlobalChartsTheme();
3217
- const scopeElement = useChartScopeElement();
3218
3675
  const { xScale, yScale } = useContext(DataContext) || {};
3219
3676
  const labelRef = useRef(null);
3220
3677
  const [height, setHeight] = useState(null);
3221
- const styles = deepmerge(providerTheme.annotationStyles ?? {}, datumStyles ?? {});
3222
- const resolveColor = (value) => value ? resolveCssVariable(value, scopeElement) ?? value : value;
3678
+ const styles = useMemo(() => deepmerge.all([
3679
+ ANNOTATION_POINTERS,
3680
+ providerTheme.annotationStyles ?? {},
3681
+ datumStyles ?? {}
3682
+ ]), [providerTheme.annotationStyles, datumStyles]);
3223
3683
  useEffect(() => {
3224
3684
  if (labelRef.current?.getBBox) {
3225
3685
  const bbox = labelRef.current.getBBox();
@@ -3305,15 +3765,8 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
3305
3765
  dx,
3306
3766
  dy,
3307
3767
  children: [
3308
- /* @__PURE__ */ jsx(Connector, {
3309
- ...styles?.connector,
3310
- stroke: resolveColor(styles?.connector?.stroke)
3311
- }),
3312
- subjectType === "circle" && /* @__PURE__ */ jsx(CircleSubject, {
3313
- ...styles?.circleSubject,
3314
- fill: resolveColor(styles?.circleSubject?.fill),
3315
- stroke: resolveColor(styles?.circleSubject?.stroke)
3316
- }),
3768
+ /* @__PURE__ */ jsx(Connector, { ...styles?.connector }),
3769
+ subjectType === "circle" && /* @__PURE__ */ jsx(CircleSubject, { ...styles?.circleSubject }),
3317
3770
  subjectType === "line-vertical" && /* @__PURE__ */ jsx(LineSubject, {
3318
3771
  min: yMax,
3319
3772
  max: yMin,
@@ -3347,8 +3800,6 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
3347
3800
  title,
3348
3801
  subtitle,
3349
3802
  ...styles?.label,
3350
- anchorLineStroke: resolveColor(styles?.label?.anchorLineStroke),
3351
- backgroundFill: resolveColor(styles?.label?.backgroundFill),
3352
3803
  ...labelPosition,
3353
3804
  horizontalAnchor: getHorizontalAnchor(subjectType, isFlippedHorizontally),
3354
3805
  verticalAnchor: getVerticalAnchor(subjectType, isFlippedVertically, y, yMax, height ?? ANNOTATION_INIT_HEIGHT)
@@ -3396,16 +3847,33 @@ const toNumber = (val) => {
3396
3847
  const num = typeof val === "number" ? val : parseFloat(val);
3397
3848
  return isNaN(num) ? void 0 : num;
3398
3849
  };
3850
+ const TOOLTIP_DATE = {};
3851
+ const TOOLTIP_FORMAT_BY_RESOLUTION$1 = {
3852
+ hour: {
3853
+ year: "numeric",
3854
+ month: "numeric",
3855
+ day: "numeric",
3856
+ hour: "numeric"
3857
+ },
3858
+ day: TOOLTIP_DATE,
3859
+ month: TOOLTIP_DATE,
3860
+ year: TOOLTIP_DATE
3861
+ };
3862
+ const TooltipDate = ({ date, displayResolution }) => {
3863
+ const formatting = useChartFormatting();
3864
+ const format = useMemo(() => createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION$1[displayResolution], formatting), [displayResolution, formatting]);
3865
+ return /* @__PURE__ */ jsx(Fragment$1, { children: date ? format(date) : null });
3866
+ };
3399
3867
  /**
3400
3868
  * Default visx-tooltip render that prints the hovered date as a heading and
3401
3869
  * one row per visible series (label + formatted value), sorted descending by
3402
3870
  * value. Reused by AreaChart, which has the same multi-series shape.
3403
3871
  *
3404
- * @param params - visx `RenderTooltipParams< DataPointDate >`.
3872
+ * @param params - visx `RenderTooltipParams< DataPointDate >`, plus the chart's optional `bucketInfo`.
3405
3873
  * @return Tooltip JSX, or `null` when no datum is hovered.
3406
3874
  */
3407
3875
  const renderDefaultTooltip = (params) => {
3408
- const { tooltipData } = params;
3876
+ const { tooltipData, bucketInfo } = params;
3409
3877
  const nearestDatum = tooltipData?.nearestDatum?.datum;
3410
3878
  if (!nearestDatum) return null;
3411
3879
  const tooltipPoints = Object.entries(tooltipData?.datumByKey || {}).map(([key, { datum }]) => ({
@@ -3416,7 +3884,10 @@ const renderDefaultTooltip = (params) => {
3416
3884
  className: line_chart_module_default["line-chart__tooltip"],
3417
3885
  children: [/* @__PURE__ */ jsx("div", {
3418
3886
  className: line_chart_module_default["line-chart__tooltip-date"],
3419
- children: nearestDatum.date?.toLocaleDateString()
3887
+ children: /* @__PURE__ */ jsx(TooltipDate, {
3888
+ date: nearestDatum.date,
3889
+ displayResolution: bucketInfo?.displayResolution ?? "day"
3890
+ })
3420
3891
  }), tooltipPoints.map((point) => /* @__PURE__ */ jsxs(Stack, {
3421
3892
  direction: "row",
3422
3893
  align: "center",
@@ -3465,9 +3936,9 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3465
3936
  const legendCollapseGroups = legend.collapseGroups ?? false;
3466
3937
  const legendShape = legend.shape ?? "line";
3467
3938
  const legendPosition = legend.position ?? "bottom";
3468
- const providerTheme = useGlobalChartsTheme();
3939
+ const formatting = useChartFormatting();
3469
3940
  const theme = useXYChartTheme(data);
3470
- const resolvedBackgroundColor = theme.backgroundColor ?? providerTheme.backgroundColor;
3941
+ const resolvedBackgroundColor = theme.backgroundColor;
3471
3942
  const chartId = useChartId(providedChartId);
3472
3943
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
3473
3944
  const isSeriesVisible = useCallback((seriesLabel) => !hiddenSeries.has(seriesLabel), [hiddenSeries]);
@@ -3541,17 +4012,17 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3541
4012
  onActivate: activateSelectedPoint
3542
4013
  });
3543
4014
  const chartOptions = useMemo(() => {
3544
- const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
3545
- const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
3546
4015
  return {
3547
4016
  axis: {
3548
- x: {
3549
- orientation: "bottom",
3550
- numTicks: guessOptimalNumTicks(dataSorted, width, formatter),
3551
- tickFormat: formatter,
3552
- display: true,
3553
- ...xAxisOptions
3554
- },
4017
+ x: buildTimeAxisOptions({
4018
+ dataSorted,
4019
+ width,
4020
+ axisOptions: options?.axis?.x,
4021
+ scaleDomain: options?.xScale?.domain,
4022
+ zoomDomain: zoom.domain,
4023
+ formatting,
4024
+ isSeriesRendered: (series) => isSeriesVisible(series.label)
4025
+ }),
3555
4026
  y: {
3556
4027
  orientation: "left",
3557
4028
  numTicks: 4,
@@ -3578,7 +4049,14 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3578
4049
  dataSorted,
3579
4050
  width,
3580
4051
  zoom.domain,
3581
- stableYDomain
4052
+ stableYDomain,
4053
+ formatting,
4054
+ isSeriesVisible
4055
+ ]);
4056
+ const bucketInfo = useMemo(() => getBucketInfo(dataSorted.filter((series) => isSeriesVisible(series.label)), options?.axis?.x?.tickResolution), [
4057
+ dataSorted,
4058
+ isSeriesVisible,
4059
+ options?.axis?.x?.tickResolution
3582
4060
  ]);
3583
4061
  const tooltipRenderGlyph = useMemo(() => {
3584
4062
  return (props) => {
@@ -3640,6 +4118,10 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3640
4118
  xAccessor: (d) => d?.date,
3641
4119
  yAccessor: (d) => d?.value
3642
4120
  };
4121
+ const tooltipRenderer = useMemo(() => (params) => renderTooltip({
4122
+ ...params,
4123
+ bucketInfo
4124
+ }), [renderTooltip, bucketInfo]);
3643
4125
  if (error) return /* @__PURE__ */ jsx("div", {
3644
4126
  className: clsx("line-chart", line_chart_module_default["line-chart"]),
3645
4127
  children: error
@@ -3688,7 +4170,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3688
4170
  onBlur: onChartBlur,
3689
4171
  children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
3690
4172
  ref: chartRef,
3691
- style: { position: "relative" },
4173
+ className: xy_plot_module_default["xy-plot"],
3692
4174
  children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
3693
4175
  theme,
3694
4176
  width,
@@ -3780,7 +4262,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3780
4262
  snapTooltipToDatumX: true,
3781
4263
  snapTooltipToDatumY: true,
3782
4264
  showSeriesGlyphs: true,
3783
- renderTooltip,
4265
+ renderTooltip: tooltipRenderer,
3784
4266
  renderGlyph: tooltipRenderGlyph,
3785
4267
  glyphStyle,
3786
4268
  showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
@@ -3918,12 +4400,11 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
3918
4400
  };
3919
4401
  //#endregion
3920
4402
  //#region src/charts/area-chart/area-chart.tsx
3921
- const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange, rescaleYOnLegendToggle, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
4403
+ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, stacked = true, stackOffset = "none", smoothing = true, curveType, fillOpacity, withStroke, renderTooltip = renderDefaultTooltip, animation, options = {}, onPointerDown, onPointerUp, onPointerMove, onPointerOut, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
3922
4404
  const legendInteractive = legend.interactive ?? false;
3923
4405
  const legendShape = legend.shape ?? "rect";
3924
4406
  const legendPosition = legend.position ?? "bottom";
3925
- const rescaleYOnVisibility = rescaleYOnVisibilityChange ?? rescaleYOnLegendToggle ?? true;
3926
- const providerTheme = useGlobalChartsTheme();
4407
+ const formatting = useChartFormatting();
3927
4408
  const theme = useXYChartTheme(data);
3928
4409
  const chartId = useChartId(providedChartId);
3929
4410
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -3973,7 +4454,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
3973
4454
  totalPoints: dataSorted[0]?.data.length || 0
3974
4455
  });
3975
4456
  const fixedYDomain = useMemo(() => {
3976
- if (rescaleYOnVisibility || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
4457
+ if (rescaleYOnVisibilityChange || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
3977
4458
  if (stacked) {
3978
4459
  const numPoints = Math.max(...dataSorted.map((s) => s.data.length));
3979
4460
  let posMax = 0;
@@ -4007,20 +4488,19 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4007
4488
  dataSorted,
4008
4489
  stacked,
4009
4490
  stackOffset,
4010
- rescaleYOnVisibility
4491
+ rescaleYOnVisibilityChange
4011
4492
  ]);
4012
4493
  const chartOptions = useMemo(() => {
4013
- const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
4014
- const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
4015
4494
  return {
4016
4495
  axis: {
4017
- x: {
4018
- orientation: "bottom",
4019
- numTicks: guessOptimalNumTicks(dataSorted, width, formatter),
4020
- tickFormat: formatter,
4021
- display: true,
4022
- ...xAxisOptions
4023
- },
4496
+ x: buildTimeAxisOptions({
4497
+ dataSorted,
4498
+ width,
4499
+ axisOptions: options?.axis?.x,
4500
+ scaleDomain: options?.xScale?.domain,
4501
+ zoomDomain: zoom.domain,
4502
+ formatting
4503
+ }),
4024
4504
  y: {
4025
4505
  orientation: "left",
4026
4506
  numTicks: 4,
@@ -4048,7 +4528,8 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4048
4528
  width,
4049
4529
  stacked,
4050
4530
  fixedYDomain,
4051
- zoom.domain
4531
+ zoom.domain,
4532
+ formatting
4052
4533
  ]);
4053
4534
  const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme);
4054
4535
  const error = validateData$3(dataSorted);
@@ -4084,9 +4565,17 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4084
4565
  };
4085
4566
  const zeroYAccessor = useCallback(() => 0, []);
4086
4567
  const visibleLabels = useMemo(() => new Set(seriesWithVisibility.filter((s) => s.isVisible).map((s) => s.series.label)), [seriesWithVisibility]);
4568
+ const bucketInfo = useMemo(() => getBucketInfo(dataSorted.filter((series) => visibleLabels.has(series.label)), options?.axis?.x?.tickResolution), [
4569
+ dataSorted,
4570
+ visibleLabels,
4571
+ options?.axis?.x?.tickResolution
4572
+ ]);
4087
4573
  const filteredRenderTooltip = useCallback((params) => {
4088
4574
  const datumByKey = params?.tooltipData?.datumByKey;
4089
- if (!datumByKey) return renderTooltip(params);
4575
+ if (!datumByKey) return renderTooltip({
4576
+ ...params,
4577
+ bucketInfo
4578
+ });
4090
4579
  const filtered = Object.fromEntries(Object.entries(datumByKey).filter(([key]) => visibleLabels.has(key)));
4091
4580
  if (Object.keys(filtered).length === 0) return null;
4092
4581
  const nearestDatum = params?.tooltipData?.nearestDatum;
@@ -4096,13 +4585,18 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4096
4585
  };
4097
4586
  return renderTooltip({
4098
4587
  ...params,
4588
+ bucketInfo,
4099
4589
  tooltipData: {
4100
4590
  ...params.tooltipData,
4101
4591
  datumByKey: filtered,
4102
4592
  nearestDatum: nextNearest
4103
4593
  }
4104
4594
  });
4105
- }, [renderTooltip, visibleLabels]);
4595
+ }, [
4596
+ renderTooltip,
4597
+ visibleLabels,
4598
+ bucketInfo
4599
+ ]);
4106
4600
  const resolvedFillOpacity = fillOpacity ?? (stacked ? .85 : .4);
4107
4601
  const resolvedWithStroke = withStroke ?? !stacked;
4108
4602
  if (error) return /* @__PURE__ */ jsx("div", {
@@ -4177,7 +4671,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4177
4671
  onBlur: onChartBlur,
4178
4672
  children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
4179
4673
  ref: chartRef,
4180
- style: { position: "relative" },
4674
+ className: xy_plot_module_default["xy-plot"],
4181
4675
  children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
4182
4676
  theme,
4183
4677
  width,
@@ -4233,7 +4727,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4233
4727
  stacked,
4234
4728
  stackOffset,
4235
4729
  getElementStyles,
4236
- strokeColor: theme.backgroundColor ?? providerTheme.backgroundColor
4730
+ strokeColor: theme.backgroundColor
4237
4731
  })] }),
4238
4732
  /* @__PURE__ */ jsx(AreaChartScalesRef, {
4239
4733
  chartRef: internalChartRef,
@@ -4401,8 +4895,7 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
4401
4895
  year: "numeric",
4402
4896
  month: "long",
4403
4897
  day: "numeric",
4404
- hour: "numeric",
4405
- hour12: true
4898
+ hour: "numeric"
4406
4899
  },
4407
4900
  day: {
4408
4901
  year: "numeric",
@@ -4421,16 +4914,16 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
4421
4914
  *
4422
4915
  * @param data - Date-based series, already parsed and sorted by `useChartDataTransform`.
4423
4916
  * @param tickResolution - Caller-declared bucket resolution, when known.
4917
+ * @param formatting - Host locale and time zone.
4424
4918
  * @return Tooltip label formatter.
4425
4919
  */
4426
- const getTooltipFormatter = (data, tickResolution) => {
4427
- if (tickResolution === "week") return (timestamp) => sprintf(__("Week of %s", "jetpack-charts"), new Date(timestamp).toLocaleDateString(void 0, {
4428
- year: "numeric",
4429
- month: "long",
4430
- day: "numeric"
4431
- }));
4920
+ const getTooltipFormatter = (data, tickResolution, formatting) => {
4921
+ if (tickResolution === "week") {
4922
+ const formatDay = createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION.day, formatting);
4923
+ return (timestamp) => sprintf(__("Week of %s", "jetpack-charts"), formatDay(timestamp));
4924
+ }
4432
4925
  const format = TOOLTIP_FORMAT_BY_RESOLUTION[getBucketResolution(data, tickResolution)] ?? TOOLTIP_FORMAT_BY_RESOLUTION.day;
4433
- return (timestamp) => new Date(timestamp).toLocaleString(void 0, format);
4926
+ return createDateFormatter(format, formatting);
4434
4927
  };
4435
4928
  const identity = (label) => label;
4436
4929
  const byBucket = (format) => (bucket) => typeof bucket === "string" ? bucket : format(Number(bucket));
@@ -4481,6 +4974,7 @@ const getGroupPadding = (scale) => {
4481
4974
  */
4482
4975
  function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = ALL_RENDERED) {
4483
4976
  const stableOptions = useDeepMemo(options);
4977
+ const formatting = useChartFormatting();
4484
4978
  const axisConfig = useMemo(() => {
4485
4979
  const { labelOverflow: xLabelOverflow, tickResolution: xTickResolution, tickFormat: xTickFormat, ...xAxisOptions } = stableOptions.axis?.x || {};
4486
4980
  const { labelOverflow: yLabelOverflow, tickResolution: yTickResolution, tickFormat: yTickFormat, ...yAxisOptions } = stableOptions.axis?.y || {};
@@ -4507,9 +5001,9 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
4507
5001
  zero: false
4508
5002
  };
4509
5003
  const hasLabels = Boolean(data?.[0]?.data?.[0]?.label);
4510
- const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution);
5004
+ const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution, formatting);
4511
5005
  const labelFormatter = timeTickFormatter ? byBucket(timeTickFormatter) : identity;
4512
- const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution));
5006
+ const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution, formatting));
4513
5007
  const valueFormatter = formatNumberCompact;
4514
5008
  const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
4515
5009
  const valueAccessor = (d) => {
@@ -4545,7 +5039,8 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
4545
5039
  }, [
4546
5040
  data,
4547
5041
  tickResolution,
4548
- isSeriesRendered
5042
+ isSeriesRendered,
5043
+ formatting
4549
5044
  ]);
4550
5045
  return useMemo(() => {
4551
5046
  const { xTickFormat: defaultXTickFormat, yTickFormat: defaultYTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
@@ -5054,6 +5549,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5054
5549
  onBlur: onChartBlur,
5055
5550
  children: chartHeight > 0 && /* @__PURE__ */ jsx("div", {
5056
5551
  ref: chartRef,
5552
+ className: xy_plot_module_default["xy-plot"],
5057
5553
  children: /* @__PURE__ */ jsxs(XYChart, {
5058
5554
  theme,
5059
5555
  width,
@@ -5221,7 +5717,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
5221
5717
  right: 20,
5222
5718
  bottom: 0,
5223
5719
  top: 0
5224
- }, ...rest }) => {
5720
+ }, className, ...rest }) => {
5225
5721
  const { getElementStyles } = useGlobalChartsContext();
5226
5722
  const tintedData = useMemo(() => {
5227
5723
  if (data.length > 1) return data;
@@ -5273,6 +5769,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
5273
5769
  return /* @__PURE__ */ jsx(BarChart, {
5274
5770
  orientation: "horizontal",
5275
5771
  gridVisibility: "none",
5772
+ className: clsx("bar-list-chart", className),
5276
5773
  data: tintedData,
5277
5774
  width,
5278
5775
  height,
@@ -5309,6 +5806,9 @@ const BarListChartResponsive = withResponsive(BarListChart);
5309
5806
  var conversion_funnel_chart_module_default = {
5310
5807
  "bar-container": "a8ccharts-mGEVca-bar-container",
5311
5808
  "change-indicator": "a8ccharts-mGEVca-change-indicator",
5809
+ "change-indicator--negative": "a8ccharts-mGEVca-change-indicator--negative",
5810
+ "change-indicator--positive": "a8ccharts-mGEVca-change-indicator--positive",
5811
+ "conversion-funnel-chart": "a8ccharts-mGEVca-conversion-funnel-chart",
5312
5812
  "conversion-funnel-chart--loading": "a8ccharts-mGEVca-conversion-funnel-chart--loading",
5313
5813
  "empty-state": "a8ccharts-mGEVca-empty-state",
5314
5814
  "funnel-bar": "a8ccharts-mGEVca-funnel-bar",
@@ -5389,23 +5889,14 @@ const useFunnelSelection = (hideTooltip) => {
5389
5889
  */
5390
5890
  const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
5391
5891
  const chartId = useChartId(providedChartId);
5392
- const { conversionFunnelChart: conversionFunnelChartSettings } = useGlobalChartsTheme();
5393
5892
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
5394
5893
  const chartRef = useRef(null);
5395
5894
  const selectedBarRef = useRef(null);
5396
5895
  const [scopeNode, setScopeNode] = useState(null);
5397
5896
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = useTooltip();
5398
5897
  const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
5399
- const { containerRef: portalContainerRef, TooltipInPortal, containerBounds } = useTooltipInPortal({
5400
- detectBounds: true,
5401
- scroll: true
5402
- });
5403
- const portalContainerRefRef = useRef(portalContainerRef);
5404
- useEffect(() => {
5405
- portalContainerRefRef.current = portalContainerRef;
5406
- }, [portalContainerRef]);
5898
+ const standaloneScopeClass = useStandaloneScopeClass();
5407
5899
  const setChartRef = useCallback((node) => {
5408
- portalContainerRefRef.current(node);
5409
5900
  chartRef.current = node;
5410
5901
  setScopeNode(node);
5411
5902
  }, []);
@@ -5422,30 +5913,22 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5422
5913
  });
5423
5914
  }, [showTooltip]);
5424
5915
  const getMouseTooltipCoords = useCallback((event) => {
5425
- if (containerBounds.width === 0 || containerBounds.height === 0) return null;
5916
+ const bounds = chartRef.current?.getBoundingClientRect();
5917
+ if (!bounds) return null;
5426
5918
  return {
5427
- x: event.clientX - containerBounds.left,
5428
- y: event.clientY - containerBounds.top
5919
+ x: event.clientX - bounds.left,
5920
+ y: event.clientY - bounds.top
5429
5921
  };
5430
- }, [
5431
- containerBounds.width,
5432
- containerBounds.height,
5433
- containerBounds.left,
5434
- containerBounds.top
5435
- ]);
5922
+ }, []);
5436
5923
  const getKeyboardTooltipCoords = useCallback((event) => {
5437
- if (containerBounds.width === 0 || containerBounds.height === 0) return null;
5924
+ const bounds = chartRef.current?.getBoundingClientRect();
5925
+ if (!bounds) return null;
5438
5926
  const rect = event.currentTarget.getBoundingClientRect();
5439
5927
  return {
5440
- x: rect.left + rect.width / 2 - containerBounds.left,
5441
- y: rect.top - containerBounds.top
5928
+ x: rect.left + rect.width / 2 - bounds.left,
5929
+ y: rect.top - bounds.top
5442
5930
  };
5443
- }, [
5444
- containerBounds.width,
5445
- containerBounds.height,
5446
- containerBounds.left,
5447
- containerBounds.top
5448
- ]);
5931
+ }, []);
5449
5932
  const handleStepInteraction = useCallback((step, event, interactionType) => {
5450
5933
  selectedBarRef.current = event.currentTarget;
5451
5934
  const { isClicked } = getStepState(step.id);
@@ -5506,19 +5989,14 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5506
5989
  };
5507
5990
  }, [clearSelectionAndRef]);
5508
5991
  const resolvedHeight = height ?? style?.height ?? "100%";
5509
- const { primaryColor, backgroundColor, positiveChangeColor, negativeChangeColor } = conversionFunnelChartSettings;
5510
- const { color: barColor } = getElementStyles ? getElementStyles({
5511
- index: 0,
5512
- overrideColor: primaryColor
5513
- }) : { color: primaryColor || "#000000" };
5514
- const changeColor = changeIndicator?.startsWith("+") ? positiveChangeColor : negativeChangeColor;
5515
- const barBackgroundColor = backgroundColor || hexToRgba(barColor, .08) || "rgba(0, 0, 0, 0.08)";
5992
+ const { color: barColor } = getElementStyles ? getElementStyles({ index: 0 }) : { color: "#000000" };
5993
+ const isPositiveChange = changeIndicator?.startsWith("+");
5994
+ const changeColor = isPositiveChange ? CATALOG_POINTERS.trendUp : CATALOG_POINTERS.trendDown;
5516
5995
  const renderDefaultMainMetric = () => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
5517
5996
  className: conversion_funnel_chart_module_default["main-rate"],
5518
5997
  children: formatPercentage(mainRate)
5519
5998
  }), changeIndicator && /* @__PURE__ */ jsx("span", {
5520
- className: conversion_funnel_chart_module_default["change-indicator"],
5521
- style: { color: changeColor },
5999
+ className: clsx(conversion_funnel_chart_module_default["change-indicator"], conversion_funnel_chart_module_default[isPositiveChange ? "change-indicator--positive" : "change-indicator--negative"]),
5522
6000
  children: changeIndicator
5523
6001
  })] });
5524
6002
  const renderDefaultTooltip = (step) => /* @__PURE__ */ jsxs(Stack, {
@@ -5556,7 +6034,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5556
6034
  align: "center",
5557
6035
  justify: "center",
5558
6036
  ref: setScopeNode,
5559
- className: clsx(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
6037
+ className: clsx("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
5560
6038
  style: {
5561
6039
  ...style,
5562
6040
  height: resolvedHeight
@@ -5567,16 +6045,16 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5567
6045
  })
5568
6046
  });
5569
6047
  const maxRate = Math.max(...steps.map((step) => step.rate));
5570
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Stack, {
6048
+ return /* @__PURE__ */ jsxs(Stack, {
5571
6049
  direction: "column",
5572
6050
  gap: "xl",
5573
6051
  ref: setChartRef,
5574
- className: clsx(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
6052
+ className: clsx("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
5575
6053
  style: {
5576
6054
  ...style,
5577
6055
  height: resolvedHeight
5578
6056
  },
5579
- children: /* @__PURE__ */ jsxs(ChartScopeContext.Provider, {
6057
+ children: [/* @__PURE__ */ jsxs(ChartScopeContext.Provider, {
5580
6058
  value: scopeNode,
5581
6059
  children: [renderMainMetric ? renderMainMetric({
5582
6060
  mainRate,
@@ -5628,7 +6106,6 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5628
6106
  role: "button",
5629
6107
  tabIndex: isBlurred ? -1 : 0,
5630
6108
  "aria-label": step.label,
5631
- style: { backgroundColor: barBackgroundColor },
5632
6109
  children: /* @__PURE__ */ jsx("div", {
5633
6110
  className: clsx(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
5634
6111
  style: {
@@ -5640,23 +6117,23 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5640
6117
  }, step.id);
5641
6118
  })
5642
6119
  })]
5643
- })
5644
- }), tooltipOpen && tooltipData && (() => {
5645
- const tooltipContent = renderTooltip ? renderTooltip({
5646
- step: tooltipData,
5647
- index: steps.findIndex((s) => s.id === tooltipData.id),
5648
- top: tooltipTop,
5649
- left: tooltipLeft,
5650
- className: conversion_funnel_chart_module_default["tooltip-wrapper"]
5651
- }) : renderDefaultTooltip(tooltipData);
5652
- if (!tooltipContent) return null;
5653
- return /* @__PURE__ */ jsx(TooltipInPortal, {
5654
- top: tooltipTop,
5655
- left: tooltipLeft,
5656
- className: clsx("a8c-charts-scope", conversion_funnel_chart_module_default["tooltip-wrapper"]),
5657
- children: tooltipContent
5658
- }, Math.random());
5659
- })()] });
6120
+ }), tooltipOpen && tooltipData && (() => {
6121
+ const tooltipContent = renderTooltip ? renderTooltip({
6122
+ step: tooltipData,
6123
+ index: steps.findIndex((s) => s.id === tooltipData.id),
6124
+ top: tooltipTop,
6125
+ left: tooltipLeft,
6126
+ className: conversion_funnel_chart_module_default["tooltip-wrapper"]
6127
+ }) : renderDefaultTooltip(tooltipData);
6128
+ if (!tooltipContent) return null;
6129
+ return /* @__PURE__ */ jsx(BoundedTooltip, {
6130
+ top: tooltipTop,
6131
+ left: tooltipLeft,
6132
+ className: clsx(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
6133
+ children: tooltipContent
6134
+ });
6135
+ })()]
6136
+ });
5660
6137
  };
5661
6138
  /**
5662
6139
  * ConversionFunnelChart component with provider wrapper
@@ -5802,7 +6279,7 @@ function normalizeGeoChartError(eventArgs) {
5802
6279
  * @return A React component displaying an interactive map with data visualization
5803
6280
  */
5804
6281
  const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
5805
- const { getElementStyles, theme: { geoChart: { featureFillColor }, backgroundColor } } = useGlobalChartsContext();
6282
+ const { getElementStyles } = useGlobalChartsContext();
5806
6283
  const scopeElement = useChartScopeElement();
5807
6284
  const containerRef = useRef(null);
5808
6285
  const reportedErrorIdsRef = useRef(/* @__PURE__ */ new Set());
@@ -5836,8 +6313,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
5836
6313
  });
5837
6314
  const fullColorHex = getElementStyles({ index: 0 }).color;
5838
6315
  const lightColorHex = lightenHexColor(fullColorHex, .8);
5839
- const backgroundColorHex = normalizeColorToHex(backgroundColor, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
5840
- const defaultFillColorHex = normalizeColorToHex(featureFillColor, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
6316
+ const backgroundColorHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
6317
+ const defaultFillColorHex = normalizeColorToHex(CATALOG_POINTERS.surfaceSecondary, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
5841
6318
  const sanitizedData = useMemo(() => {
5842
6319
  if (data.length === 0) return {
5843
6320
  data,
@@ -5898,8 +6375,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
5898
6375
  className: clsx("geo-chart", geo_chart_module_default.container, className),
5899
6376
  style: {
5900
6377
  width,
5901
- height,
5902
- backgroundColor
6378
+ height
5903
6379
  },
5904
6380
  children: /* @__PURE__ */ jsx(Chart, {
5905
6381
  chartType: "GeoChart",
@@ -5949,6 +6425,7 @@ var heatmap_chart_module_default = {
5949
6425
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
5950
6426
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
5951
6427
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
6428
+ "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
5952
6429
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
5953
6430
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
5954
6431
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
@@ -6091,7 +6568,7 @@ const HeatmapContext = createContext(null);
6091
6568
  //#region src/charts/heatmap-chart/private/heatmap-legend.tsx
6092
6569
  const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6093
6570
  const context = useContext(HeatmapContext);
6094
- const { legend, backgroundColor } = useGlobalChartsTheme();
6571
+ const { legend } = useGlobalChartsTheme();
6095
6572
  if (!context) return null;
6096
6573
  const { primaryColorHex } = context;
6097
6574
  const labelStyle = legend.labelStyles;
@@ -6102,6 +6579,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6102
6579
  children: [
6103
6580
  /* @__PURE__ */ jsx(Text$1, {
6104
6581
  variant: "body-sm",
6582
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6105
6583
  style: labelStyle,
6106
6584
  children: lessLabel ?? __("Less", "jetpack-charts")
6107
6585
  }),
@@ -6115,7 +6593,6 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6115
6593
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6116
6594
  style: {
6117
6595
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6118
- "--a8c-charts-color-heatmap-background": backgroundColor,
6119
6596
  "--a8c-charts-heatmap-cell-intensity": intensity
6120
6597
  }
6121
6598
  }, index);
@@ -6123,6 +6600,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6123
6600
  }),
6124
6601
  /* @__PURE__ */ jsx(Text$1, {
6125
6602
  variant: "body-sm",
6603
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6126
6604
  style: labelStyle,
6127
6605
  children: moreLabel ?? __("More", "jetpack-charts")
6128
6606
  })
@@ -6140,17 +6618,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6140
6618
  const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
6141
6619
  const [selectedIndex, setSelectedIndex] = useState();
6142
6620
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = useTooltip();
6143
- const { containerRef, containerBounds, TooltipInPortal } = useTooltipInPortal({
6144
- detectBounds: true,
6145
- scroll: true
6146
- });
6147
- const containerBoundsRef = useRef(containerBounds);
6148
- containerBoundsRef.current = containerBounds;
6621
+ const standaloneScopeClass = useStandaloneScopeClass();
6622
+ const containerRef = useRef(null);
6623
+ const getTooltipOrigin = useCallback(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
6149
6624
  const { color: primaryColorHex } = getElementStyles({
6150
6625
  index: 0,
6151
- overrideColor: primaryColor || heatmapChartSettings.primaryColor
6626
+ overrideColor: primaryColor
6152
6627
  });
6153
- const chartBackgroundHex = normalizeColorToHex(theme.backgroundColor, scopeElement, resolveCssVariable);
6628
+ const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
6154
6629
  const primaryHex = normalizeColorToHex(primaryColorHex);
6155
6630
  const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
6156
6631
  const extent = useMemo(() => getValueExtent(data), [data]);
@@ -6226,19 +6701,21 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6226
6701
  ]);
6227
6702
  const handleCellMouseMove = useCallback((event) => {
6228
6703
  if (!withTooltips) return;
6704
+ const origin = getTooltipOrigin();
6705
+ if (!origin) return;
6229
6706
  const target = event.currentTarget;
6230
6707
  const columnIndex = Number(target.dataset.column);
6231
6708
  const rowIndex = Number(target.dataset.row);
6232
- const bounds = containerBoundsRef.current;
6233
6709
  showTooltip({
6234
- tooltipLeft: event.clientX - bounds.left,
6235
- tooltipTop: event.clientY - bounds.top,
6710
+ tooltipLeft: event.clientX - origin.left,
6711
+ tooltipTop: event.clientY - origin.top,
6236
6712
  tooltipData: buildTooltipData(columnIndex, rowIndex)
6237
6713
  });
6238
6714
  }, [
6239
6715
  withTooltips,
6240
6716
  showTooltip,
6241
- buildTooltipData
6717
+ buildTooltipData,
6718
+ getTooltipOrigin
6242
6719
  ]);
6243
6720
  const handleCellMouseLeave = useCallback(() => {
6244
6721
  if (withTooltips && selectedIndex === void 0) hideTooltip();
@@ -6249,13 +6726,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6249
6726
  ]);
6250
6727
  useEffect(() => {
6251
6728
  if (!withTooltips || selectedIndex === void 0) return;
6729
+ const origin = getTooltipOrigin();
6730
+ if (!origin) return;
6252
6731
  const col = Math.floor(selectedIndex / rows);
6253
6732
  const row = selectedIndex % rows;
6254
6733
  const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
6255
- const bounds = containerBoundsRef.current;
6256
6734
  showTooltip({
6257
- tooltipLeft: rect ? rect.left + rect.width / 2 - bounds.left : 0,
6258
- tooltipTop: rect ? rect.top + rect.height / 2 - bounds.top : 0,
6735
+ tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
6736
+ tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
6259
6737
  tooltipData: buildTooltipData(col, row)
6260
6738
  });
6261
6739
  }, [
@@ -6264,7 +6742,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6264
6742
  rows,
6265
6743
  chartId,
6266
6744
  buildTooltipData,
6267
- showTooltip
6745
+ showTooltip,
6746
+ getTooltipOrigin
6268
6747
  ]);
6269
6748
  const defaultRenderTooltip = useCallback((info) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("strong", { children: info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim() }), /* @__PURE__ */ jsx("div", { children: info.value === null ? __("No data", "jetpack-charts") : formatNumber(info.value) })] }), []);
6270
6749
  if (!columns || !rows) return /* @__PURE__ */ jsx(Center, {
@@ -6282,7 +6761,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6282
6761
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6283
6762
  const gridStyle = {
6284
6763
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6285
- "--a8c-charts-color-heatmap-background": theme.backgroundColor,
6286
6764
  gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
6287
6765
  gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
6288
6766
  };
@@ -6380,11 +6858,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6380
6858
  })]
6381
6859
  }, `row-${rowIndex}`);
6382
6860
  })]
6383
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(TooltipInPortal, {
6861
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
6384
6862
  top: tooltipTop,
6385
6863
  left: tooltipLeft,
6386
6864
  children: /* @__PURE__ */ jsx("div", {
6387
- className: "a8c-charts-scope",
6865
+ className: standaloneScopeClass,
6388
6866
  role: "tooltip",
6389
6867
  tabIndex: -1,
6390
6868
  children: (renderTooltip ?? defaultRenderTooltip)(tooltipData)
@@ -6408,7 +6886,7 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ jsx(HeatmapChartW
6408
6886
  HeatmapChartResponsiveInner.displayName = "HeatmapChart";
6409
6887
  const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
6410
6888
  //#endregion
6411
- //#region ../../../node_modules/.pnpm/is-plain-object@5.0.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6889
+ //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6412
6890
  /*!
6413
6891
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
6414
6892
  *
@@ -6475,7 +6953,7 @@ function paramCase(input, options) {
6475
6953
  return dotCase(input, __assign({ delimiter: "-" }, options));
6476
6954
  }
6477
6955
  //#endregion
6478
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
6956
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
6479
6957
  function useUpdateEffect(effect, deps) {
6480
6958
  const mountedRef = useRef$1(false);
6481
6959
  useEffect$1(() => {
@@ -8045,7 +8523,7 @@ _createEmotion.css;
8045
8523
  _createEmotion.sheet;
8046
8524
  _createEmotion.cache;
8047
8525
  //#endregion
8048
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
8526
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
8049
8527
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
8050
8528
  var useCx = () => {
8051
8529
  const cache = __unsafe_useEmotionCache();
@@ -8152,7 +8630,7 @@ function memize(fn, options) {
8152
8630
  return memoized;
8153
8631
  }
8154
8632
  //#endregion
8155
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
8633
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
8156
8634
  var white = "#fff";
8157
8635
  var GRAY = {
8158
8636
  900: "#1e1e1e",
@@ -8231,7 +8709,7 @@ var COLORS = Object.freeze({
8231
8709
  ui: UI
8232
8710
  });
8233
8711
  //#endregion
8234
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/utils/config-values.mjs
8712
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/config-values.mjs
8235
8713
  var CONTROL_HEIGHT = "36px";
8236
8714
  var CONTROL_PROPS = {
8237
8715
  controlPaddingX: 12,
@@ -8246,9 +8724,6 @@ var CONTROL_PROPS = {
8246
8724
  };
8247
8725
  var config_values_default = Object.assign({}, CONTROL_PROPS, {
8248
8726
  colorDivider: "rgba(0, 0, 0, 0.1)",
8249
- colorScrollbarThumb: "rgba(0, 0, 0, 0.2)",
8250
- colorScrollbarThumbHover: "rgba(0, 0, 0, 0.5)",
8251
- colorScrollbarTrack: "rgba(0, 0, 0, 0.04)",
8252
8727
  elevationIntensity: 1,
8253
8728
  radiusXSmall: "1px",
8254
8729
  radiusSmall: "2px",
@@ -8259,7 +8734,6 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
8259
8734
  borderWidth: "1px",
8260
8735
  borderWidthFocus: "1.5px",
8261
8736
  borderWidthTab: "4px",
8262
- spinnerSize: 16,
8263
8737
  fontSize: "13px",
8264
8738
  fontSizeH1: "calc(2.44 * 13px)",
8265
8739
  fontSizeH2: "calc(1.95 * 13px)",
@@ -8336,10 +8810,10 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8336
8810
  };
8337
8811
  }));
8338
8812
  //#endregion
8339
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.53.0/node_modules/@wordpress/warning/build-module/utils.mjs
8813
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
8340
8814
  var logged = /* @__PURE__ */ new Set();
8341
8815
  //#endregion
8342
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.53.0/node_modules/@wordpress/warning/build-module/index.mjs
8816
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
8343
8817
  function isDev() {
8344
8818
  return globalThis.SCRIPT_DEBUG === true;
8345
8819
  }
@@ -8353,7 +8827,7 @@ function warning(message) {
8353
8827
  logged.add(message);
8354
8828
  }
8355
8829
  //#endregion
8356
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
8830
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
8357
8831
  var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
8358
8832
  var ComponentsContext = createContext$1(
8359
8833
  /** @type {Record<string, any>} */
@@ -8380,18 +8854,18 @@ var BaseContextSystemProvider = ({ children, value }) => {
8380
8854
  };
8381
8855
  memo(BaseContextSystemProvider);
8382
8856
  //#endregion
8383
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/context/constants.mjs
8857
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/constants.mjs
8384
8858
  var COMPONENT_NAMESPACE = "data-wp-component";
8385
8859
  var CONNECTED_NAMESPACE = "data-wp-c16t";
8386
8860
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
8387
8861
  //#endregion
8388
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
8862
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
8389
8863
  function getStyledClassName(namespace) {
8390
8864
  return `components-${paramCase(namespace)}`;
8391
8865
  }
8392
8866
  var getStyledClassNameFromKey = memize(getStyledClassName);
8393
8867
  //#endregion
8394
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/context/context-connect.mjs
8868
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/context-connect.mjs
8395
8869
  function contextConnect(Component, namespace) {
8396
8870
  return _contextConnect(Component, namespace, { forwardsRef: true });
8397
8871
  }
@@ -8408,7 +8882,7 @@ function _contextConnect(Component, namespace, options) {
8408
8882
  });
8409
8883
  }
8410
8884
  //#endregion
8411
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/context/utils.mjs
8885
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/utils.mjs
8412
8886
  function getNamespace(componentName) {
8413
8887
  return { [COMPONENT_NAMESPACE]: componentName };
8414
8888
  }
@@ -8416,7 +8890,7 @@ function getConnectedNamespace() {
8416
8890
  return { [CONNECTED_NAMESPACE]: true };
8417
8891
  }
8418
8892
  //#endregion
8419
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
8893
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
8420
8894
  function useContextSystem(props, namespace) {
8421
8895
  const contextSystemProps = useComponentsContext();
8422
8896
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -8436,7 +8910,7 @@ function useContextSystem(props, namespace) {
8436
8910
  return finalComponentProps;
8437
8911
  }
8438
8912
  //#endregion
8439
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
8913
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
8440
8914
  var visuallyHidden = {
8441
8915
  border: 0,
8442
8916
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -8452,7 +8926,7 @@ var visuallyHidden = {
8452
8926
  wordBreak: "normal"
8453
8927
  };
8454
8928
  //#endregion
8455
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
8929
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
8456
8930
  var customAttributeRegExp = /^(data|aria|x)-/i;
8457
8931
  var eventHandlerRegExp = /^on[A-Z]/;
8458
8932
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -8542,7 +9016,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
8542
9016
  }
8543
9017
  var PolymorphicElement = forwardRef$1(UnforwardedPolymorphicElement);
8544
9018
  //#endregion
8545
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/view/component.mjs
9019
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/view/component.mjs
8546
9020
  function UnforwardedView({ css, ...restProps }, ref) {
8547
9021
  return /* @__PURE__ */ jsx(PolymorphicElement, {
8548
9022
  ref,
@@ -8551,7 +9025,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
8551
9025
  }
8552
9026
  var component_default$2 = Object.assign(forwardRef$1(UnforwardedView), { selector: ".components-view" });
8553
9027
  //#endregion
8554
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
9028
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
8555
9029
  function UnconnectedVisuallyHidden(props, forwardedRef) {
8556
9030
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
8557
9031
  return /* @__PURE__ */ jsx(component_default$2, {
@@ -8566,7 +9040,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
8566
9040
  }
8567
9041
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
8568
9042
  //#endregion
8569
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
9043
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
8570
9044
  var breakpoints = [
8571
9045
  "40em",
8572
9046
  "52em",
@@ -8600,7 +9074,7 @@ function useResponsiveValue(values, options = {}) {
8600
9074
  return array[index >= array.length ? array.length - 1 : index];
8601
9075
  }
8602
9076
  //#endregion
8603
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/grid/utils.mjs
9077
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/utils.mjs
8604
9078
  var ALIGNMENTS = {
8605
9079
  bottom: {
8606
9080
  alignItems: "flex-end",
@@ -8648,7 +9122,7 @@ function getAlignmentProps(alignment) {
8648
9122
  return alignment ? ALIGNMENTS[alignment] : {};
8649
9123
  }
8650
9124
  //#endregion
8651
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/grid/hook.mjs
9125
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/hook.mjs
8652
9126
  function useGrid(props) {
8653
9127
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
8654
9128
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -8669,7 +9143,7 @@ function useGrid(props) {
8669
9143
  justifyContent: justify,
8670
9144
  verticalAlign: isInline ? "middle" : void 0,
8671
9145
  ...alignmentProps
8672
- }, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBdURzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBFeHRlcm5hbCBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuXG4vKipcbiAqIFdvcmRQcmVzcyBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5cbi8qKlxuICogSW50ZXJuYWwgZGVwZW5kZW5jaWVzXG4gKi9cbmltcG9ydCB0eXBlIHsgV29yZFByZXNzQ29tcG9uZW50UHJvcHMgfSBmcm9tICcuLi9jb250ZXh0JztcbmltcG9ydCB7IHVzZUNvbnRleHRTeXN0ZW0gfSBmcm9tICcuLi9jb250ZXh0JztcbmltcG9ydCB7IGdldEFsaWdubWVudFByb3BzIH0gZnJvbSAnLi91dGlscyc7XG5pbXBvcnQgeyB1c2VSZXNwb25zaXZlVmFsdWUgfSBmcm9tICcuLi91dGlscy91c2UtcmVzcG9uc2l2ZS12YWx1ZSc7XG5pbXBvcnQgQ09ORklHIGZyb20gJy4uL3V0aWxzL2NvbmZpZy12YWx1ZXMnO1xuaW1wb3J0IHsgdXNlQ3ggfSBmcm9tICcuLi91dGlscy9ob29rcy91c2UtY3gnO1xuaW1wb3J0IHR5cGUgeyBHcmlkUHJvcHMgfSBmcm9tICcuL3R5cGVzJztcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlR3JpZChcblx0cHJvcHM6IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzPCBHcmlkUHJvcHMsICdkaXYnID5cbikge1xuXHRjb25zdCB7XG5cdFx0YWxpZ24sXG5cdFx0YWxpZ25tZW50LFxuXHRcdGNsYXNzTmFtZSxcblx0XHRjb2x1bW5HYXAsXG5cdFx0Y29sdW1ucyA9IDIsXG5cdFx0Z2FwID0gMyxcblx0XHRpc0lubGluZSA9IGZhbHNlLFxuXHRcdGp1c3RpZnksXG5cdFx0cm93R2FwLFxuXHRcdHJvd3MsXG5cdFx0dGVtcGxhdGVDb2x1bW5zLFxuXHRcdHRlbXBsYXRlUm93cyxcblx0XHQuLi5vdGhlclByb3BzXG5cdH0gPSB1c2VDb250ZXh0U3lzdGVtKCBwcm9wcywgJ0dyaWQnICk7XG5cblx0Y29uc3QgY29sdW1uc0FzQXJyYXkgPSBBcnJheS5pc0FycmF5KCBjb2x1bW5zICkgPyBjb2x1bW5zIDogWyBjb2x1bW5zIF07XG5cdGNvbnN0IGNvbHVtbiA9IHVzZVJlc3BvbnNpdmVWYWx1ZSggY29sdW1uc0FzQXJyYXkgKTtcblx0Y29uc3Qgcm93c0FzQXJyYXkgPSBBcnJheS5pc0FycmF5KCByb3dzICkgPyByb3dzIDogWyByb3dzIF07XG5cdGNvbnN0IHJvdyA9IHVzZVJlc3BvbnNpdmVWYWx1ZSggcm93c0FzQXJyYXkgKTtcblxuXHRjb25zdCBncmlkVGVtcGxhdGVDb2x1bW5zID1cblx0XHR0ZW1wbGF0ZUNvbHVtbnMgfHwgKCAhISBjb2x1bW5zICYmIGByZXBlYXQoICR7IGNvbHVtbiB9LCAxZnIgKWAgKTtcblx0Y29uc3QgZ3JpZFRlbXBsYXRlUm93cyA9XG5cdFx0dGVtcGxhdGVSb3dzIHx8ICggISEgcm93cyAmJiBgcmVwZWF0KCAkeyByb3cgfSwgMWZyIClgICk7XG5cblx0Y29uc3QgY3ggPSB1c2VDeCgpO1xuXG5cdGNvbnN0IGNsYXNzZXMgPSB1c2VNZW1vKCAoKSA9PiB7XG5cdFx0Y29uc3QgYWxpZ25tZW50UHJvcHMgPSBnZXRBbGlnbm1lbnRQcm9wcyggYWxpZ25tZW50ICk7XG5cblx0XHRjb25zdCBncmlkQ2xhc3NlcyA9IGNzcygge1xuXHRcdFx0YWxpZ25JdGVtczogYWxpZ24sXG5cdFx0XHRkaXNwbGF5OiBpc0lubGluZSA/ICdpbmxpbmUtZ3JpZCcgOiAnZ3JpZCcsXG5cdFx0XHRnYXA6IGBjYWxjKCAkeyBDT05GSUcuZ3JpZEJhc2UgfSAqICR7IGdhcCB9IClgLFxuXHRcdFx0Z3JpZFRlbXBsYXRlQ29sdW1uczogZ3JpZFRlbXBsYXRlQ29sdW1ucyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkVGVtcGxhdGVSb3dzOiBncmlkVGVtcGxhdGVSb3dzIHx8IHVuZGVmaW5lZCxcblx0XHRcdGdyaWRSb3dHYXA6IHJvd0dhcCxcblx0XHRcdGdyaWRDb2x1bW5HYXA6IGNvbHVtbkdhcCxcblx0XHRcdGp1c3RpZnlDb250ZW50OiBqdXN0aWZ5LFxuXHRcdFx0dmVydGljYWxBbGlnbjogaXNJbmxpbmUgPyAnbWlkZGxlJyA6IHVuZGVmaW5lZCxcblx0XHRcdC4uLmFsaWdubWVudFByb3BzLFxuXHRcdH0gKTtcblxuXHRcdHJldHVybiBjeCggZ3JpZENsYXNzZXMsIGNsYXNzTmFtZSApO1xuXHR9LCBbXG5cdFx0YWxpZ24sXG5cdFx0YWxpZ25tZW50LFxuXHRcdGNsYXNzTmFtZSxcblx0XHRjb2x1bW5HYXAsXG5cdFx0Y3gsXG5cdFx0Z2FwLFxuXHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnMsXG5cdFx0Z3JpZFRlbXBsYXRlUm93cyxcblx0XHRpc0lubGluZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XSApO1xuXG5cdHJldHVybiB7IC4uLm90aGVyUHJvcHMsIGNsYXNzTmFtZTogY2xhc3NlcyB9O1xufVxuIl19 */");
9146
+ }, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBNENzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5pbXBvcnQgdHlwZSB7IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyB1c2VDb250ZXh0U3lzdGVtIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyBnZXRBbGlnbm1lbnRQcm9wcyB9IGZyb20gJy4vdXRpbHMnO1xuaW1wb3J0IHsgdXNlUmVzcG9uc2l2ZVZhbHVlIH0gZnJvbSAnLi4vdXRpbHMvdXNlLXJlc3BvbnNpdmUtdmFsdWUnO1xuaW1wb3J0IENPTkZJRyBmcm9tICcuLi91dGlscy9jb25maWctdmFsdWVzJztcbmltcG9ydCB7IHVzZUN4IH0gZnJvbSAnLi4vdXRpbHMvaG9va3MvdXNlLWN4JztcbmltcG9ydCB0eXBlIHsgR3JpZFByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIHVzZUdyaWQoXG5cdHByb3BzOiBXb3JkUHJlc3NDb21wb25lbnRQcm9wczwgR3JpZFByb3BzLCAnZGl2JyA+XG4pIHtcblx0Y29uc3Qge1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGNvbHVtbnMgPSAyLFxuXHRcdGdhcCA9IDMsXG5cdFx0aXNJbmxpbmUgPSBmYWxzZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XHRyb3dzLFxuXHRcdHRlbXBsYXRlQ29sdW1ucyxcblx0XHR0ZW1wbGF0ZVJvd3MsXG5cdFx0Li4ub3RoZXJQcm9wc1xuXHR9ID0gdXNlQ29udGV4dFN5c3RlbSggcHJvcHMsICdHcmlkJyApO1xuXG5cdGNvbnN0IGNvbHVtbnNBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggY29sdW1ucyApID8gY29sdW1ucyA6IFsgY29sdW1ucyBdO1xuXHRjb25zdCBjb2x1bW4gPSB1c2VSZXNwb25zaXZlVmFsdWUoIGNvbHVtbnNBc0FycmF5ICk7XG5cdGNvbnN0IHJvd3NBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggcm93cyApID8gcm93cyA6IFsgcm93cyBdO1xuXHRjb25zdCByb3cgPSB1c2VSZXNwb25zaXZlVmFsdWUoIHJvd3NBc0FycmF5ICk7XG5cblx0Y29uc3QgZ3JpZFRlbXBsYXRlQ29sdW1ucyA9XG5cdFx0dGVtcGxhdGVDb2x1bW5zIHx8ICggISEgY29sdW1ucyAmJiBgcmVwZWF0KCAkeyBjb2x1bW4gfSwgMWZyIClgICk7XG5cdGNvbnN0IGdyaWRUZW1wbGF0ZVJvd3MgPVxuXHRcdHRlbXBsYXRlUm93cyB8fCAoICEhIHJvd3MgJiYgYHJlcGVhdCggJHsgcm93IH0sIDFmciApYCApO1xuXG5cdGNvbnN0IGN4ID0gdXNlQ3goKTtcblxuXHRjb25zdCBjbGFzc2VzID0gdXNlTWVtbyggKCkgPT4ge1xuXHRcdGNvbnN0IGFsaWdubWVudFByb3BzID0gZ2V0QWxpZ25tZW50UHJvcHMoIGFsaWdubWVudCApO1xuXG5cdFx0Y29uc3QgZ3JpZENsYXNzZXMgPSBjc3MoIHtcblx0XHRcdGFsaWduSXRlbXM6IGFsaWduLFxuXHRcdFx0ZGlzcGxheTogaXNJbmxpbmUgPyAnaW5saW5lLWdyaWQnIDogJ2dyaWQnLFxuXHRcdFx0Z2FwOiBgY2FsYyggJHsgQ09ORklHLmdyaWRCYXNlIH0gKiAkeyBnYXAgfSApYCxcblx0XHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnM6IGdyaWRUZW1wbGF0ZUNvbHVtbnMgfHwgdW5kZWZpbmVkLFxuXHRcdFx0Z3JpZFRlbXBsYXRlUm93czogZ3JpZFRlbXBsYXRlUm93cyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkUm93R2FwOiByb3dHYXAsXG5cdFx0XHRncmlkQ29sdW1uR2FwOiBjb2x1bW5HYXAsXG5cdFx0XHRqdXN0aWZ5Q29udGVudDoganVzdGlmeSxcblx0XHRcdHZlcnRpY2FsQWxpZ246IGlzSW5saW5lID8gJ21pZGRsZScgOiB1bmRlZmluZWQsXG5cdFx0XHQuLi5hbGlnbm1lbnRQcm9wcyxcblx0XHR9ICk7XG5cblx0XHRyZXR1cm4gY3goIGdyaWRDbGFzc2VzLCBjbGFzc05hbWUgKTtcblx0fSwgW1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGN4LFxuXHRcdGdhcCxcblx0XHRncmlkVGVtcGxhdGVDb2x1bW5zLFxuXHRcdGdyaWRUZW1wbGF0ZVJvd3MsXG5cdFx0aXNJbmxpbmUsXG5cdFx0anVzdGlmeSxcblx0XHRyb3dHYXAsXG5cdF0gKTtcblxuXHRyZXR1cm4geyAuLi5vdGhlclByb3BzLCBjbGFzc05hbWU6IGNsYXNzZXMgfTtcbn1cbiJdfQ== */");
8673
9147
  return cx(gridClasses, className);
8674
9148
  }, [
8675
9149
  align,
@@ -8690,7 +9164,7 @@ function useGrid(props) {
8690
9164
  };
8691
9165
  }
8692
9166
  //#endregion
8693
- //#region ../../../node_modules/.pnpm/@wordpress+components@37.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_e283446b3cbc213906bb0d97b3dd78c5/node_modules/@wordpress/components/build-module/grid/component.mjs
9167
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/component.mjs
8694
9168
  function UnconnectedGrid(props, forwardedRef) {
8695
9169
  const gridProps = useGrid(props);
8696
9170
  return /* @__PURE__ */ jsx(component_default$2, {
@@ -8713,8 +9187,8 @@ const SUBPIXEL_TOLERANCE = .5;
8713
9187
  * Counts how many leading rows fit inside the content container.
8714
9188
  *
8715
9189
  * Rows are read from the DOM rather than derived from constants: row height
8716
- * depends on the theme's row gap, label wrapping, and whatever a caller renders
8717
- * as a label, so any restated number would drift.
9190
+ * depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
9191
+ * whatever a caller renders as a label, so any restated number would drift.
8718
9192
  *
8719
9193
  * Every row is a direct grid child marked with `data-row-index`; interactive rows
8720
9194
  * use a button wrapper and non-interactive rows use a div wrapper.
@@ -8799,14 +9273,13 @@ function useFittedRowCount(enabled, rowCount, data) {
8799
9273
  * @return Array of legend items for the leaderboard chart
8800
9274
  */
8801
9275
  function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withComparison = false, withOverlayLabel = false, legendLabels }) {
8802
- const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
8803
9276
  const { getElementStyles } = useGlobalChartsContext();
8804
9277
  return useMemo(() => {
8805
9278
  if (!data || data.length === 0) return [];
8806
9279
  const items = [];
8807
9280
  const { color: resolvedPrimaryColor } = getElementStyles({
8808
9281
  index: 0,
8809
- overrideColor: primaryColor || leaderboardChartSettings.primaryColor
9282
+ overrideColor: primaryColor
8810
9283
  });
8811
9284
  items.push({
8812
9285
  label: legendLabels?.primary || __("Current period", "jetpack-charts"),
@@ -8815,7 +9288,7 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
8815
9288
  if (withComparison && !withOverlayLabel) {
8816
9289
  const { color: resolvedSecondaryColor } = getElementStyles({
8817
9290
  index: 1,
8818
- overrideColor: secondaryColor || leaderboardChartSettings.secondaryColor
9291
+ overrideColor: secondaryColor
8819
9292
  });
8820
9293
  items.push({
8821
9294
  label: legendLabels?.comparison || __("Previous period", "jetpack-charts"),
@@ -8829,7 +9302,6 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
8829
9302
  secondaryColor,
8830
9303
  withComparison,
8831
9304
  legendLabels,
8832
- leaderboardChartSettings,
8833
9305
  getElementStyles,
8834
9306
  withOverlayLabel
8835
9307
  ]);
@@ -8843,6 +9315,9 @@ var leaderboard_chart_module_default = {
8843
9315
  "chevron": "a8ccharts-GovfoW-chevron",
8844
9316
  "deltaPlaceholder": "a8ccharts-GovfoW-deltaPlaceholder",
8845
9317
  "deltaValue": "a8ccharts-GovfoW-deltaValue",
9318
+ "deltaValue--down": "a8ccharts-GovfoW-deltaValue--down",
9319
+ "deltaValue--neutral": "a8ccharts-GovfoW-deltaValue--neutral",
9320
+ "deltaValue--up": "a8ccharts-GovfoW-deltaValue--up",
8846
9321
  "emptyState": "a8ccharts-GovfoW-emptyState",
8847
9322
  "fitEmptyState": "a8ccharts-GovfoW-fitEmptyState",
8848
9323
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
@@ -8861,6 +9336,14 @@ var leaderboard_chart_module_default = {
8861
9336
  };
8862
9337
  //#endregion
8863
9338
  //#region src/charts/leaderboard-chart/leaderboard-chart.tsx
9339
+ const ROW_GAP = "var(--a8c-charts-dimension-leaderboard-row-gap, 12px)";
9340
+ const COLUMN_GAP = "var(--a8c-charts-dimension-leaderboard-column-gap, 4px)";
9341
+ /** Indexed by `Math.sign( delta ) + 1`, so [negative, neutral, positive]. */
9342
+ const DELTA_TREND_CLASS = [
9343
+ "deltaValue--down",
9344
+ "deltaValue--neutral",
9345
+ "deltaValue--up"
9346
+ ];
8864
9347
  /**
8865
9348
  * Default value formatter using formatMetricValue
8866
9349
  *
@@ -8963,15 +9446,15 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
8963
9446
  ...legend.shapeStyles
8964
9447
  };
8965
9448
  const { legendChildren, nonLegendChildren } = useChartChildren(children, "LeaderboardChart");
8966
- const { labelSpacing, rowGap, columnGap, primaryColor: settingsPrimaryColor, secondaryColor: settingsSecondaryColor, deltaColors } = leaderboardChartSettings;
9449
+ const { labelSpacing } = leaderboardChartSettings;
8967
9450
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
8968
9451
  const { color: resolvedPrimaryColor } = getElementStyles({
8969
9452
  index: 0,
8970
- overrideColor: primaryColor || settingsPrimaryColor
9453
+ overrideColor: primaryColor
8971
9454
  });
8972
9455
  const { color: resolvedSecondaryColor } = getElementStyles({
8973
9456
  index: 1,
8974
- overrideColor: secondaryColor || settingsSecondaryColor
9457
+ overrideColor: secondaryColor
8975
9458
  });
8976
9459
  const legendItems = useLeaderboardLegendItems({
8977
9460
  data: data || [],
@@ -9027,7 +9510,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9027
9510
  legendPosition,
9028
9511
  legendElement: false,
9029
9512
  legendChildren,
9030
- className: clsx(leaderboard_chart_module_default.leaderboardChart, {
9513
+ className: clsx("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
9031
9514
  [leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
9032
9515
  [leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
9033
9516
  }, className),
@@ -9062,7 +9545,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9062
9545
  legendPosition,
9063
9546
  legendElement,
9064
9547
  legendChildren,
9065
- className: clsx(leaderboard_chart_module_default.leaderboardChart, {
9548
+ className: clsx("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
9066
9549
  [leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
9067
9550
  [leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
9068
9551
  }, className),
@@ -9084,8 +9567,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9084
9567
  children: getAllHiddenMessage(legendInteractive, "series")
9085
9568
  }) : /* @__PURE__ */ jsx(component_default, {
9086
9569
  templateColumns: "minmax(0, 1fr) auto",
9087
- rowGap,
9088
- columnGap,
9570
+ rowGap: ROW_GAP,
9571
+ columnGap: COLUMN_GAP,
9089
9572
  "data-leaderboard-grid": true,
9090
9573
  children: data.map((entry, rowIndex) => {
9091
9574
  const rowStyle = shouldFitRows && rowIndex >= fittedCount ? { visibility: "hidden" } : void 0;
@@ -9094,8 +9577,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9094
9577
  const hasDelta = hasDeltaValue(entry);
9095
9578
  const showComparisonValue = showComparisonColumn && hasDelta;
9096
9579
  const showComparisonPlaceholder = showComparisonColumn && !hasDelta;
9097
- const colorIndex = showComparisonValue ? Math.sign(entry.delta) + 1 : 1;
9098
- const deltaColor = deltaColors[colorIndex];
9580
+ const deltaTrendClass = leaderboard_chart_module_default[DELTA_TREND_CLASS[showComparisonValue ? Math.sign(entry.delta) + 1 : 1]];
9099
9581
  const rowCells = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Stack, {
9100
9582
  direction: "column",
9101
9583
  gap: labelSpacing,
@@ -9116,13 +9598,11 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9116
9598
  children: [
9117
9599
  isPrimaryVisible && /* @__PURE__ */ jsx(Text$1, { children: valueFormatter(entry.currentValue) }),
9118
9600
  showComparisonValue && /* @__PURE__ */ jsx(Text$1, {
9119
- className: leaderboard_chart_module_default.deltaValue,
9120
- style: { color: deltaColor },
9601
+ className: clsx(leaderboard_chart_module_default.deltaValue, deltaTrendClass),
9121
9602
  children: deltaFormatter(entry.delta)
9122
9603
  }),
9123
9604
  showComparisonPlaceholder && /* @__PURE__ */ jsxs(Text$1, {
9124
- className: clsx(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder),
9125
- style: { color: deltaColor },
9605
+ className: clsx(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder, deltaTrendClass),
9126
9606
  children: [/* @__PURE__ */ jsx("span", {
9127
9607
  "aria-hidden": "true",
9128
9608
  children: "—"
@@ -9206,7 +9686,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
9206
9686
  //#endregion
9207
9687
  //#region src/charts/pie-chart/pie-chart.module.scss
9208
9688
  var pie_chart_module_default = {
9209
- "pie-chart": "a8ccharts-gnszbG-pie-chart",
9689
+ "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
9690
+ "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
9691
+ "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
9210
9692
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
9211
9693
  };
9212
9694
  //#endregion
@@ -9261,11 +9743,8 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9261
9743
  const providerTheme = useGlobalChartsTheme();
9262
9744
  const chartId = useChartId(providedChartId);
9263
9745
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
9264
- const { containerRef, TooltipInPortal, containerBounds } = useTooltipInPortal({
9265
- detectBounds: true,
9266
- scroll: true,
9267
- debounce: 0
9268
- });
9746
+ const standaloneScopeClass = useStandaloneScopeClass();
9747
+ const containerRef = useRef(null);
9269
9748
  const onMouseLeave = useCallback(() => {
9270
9749
  if (!withTooltips) return;
9271
9750
  hideTooltip();
@@ -9348,19 +9827,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9348
9827
  width: propWidth || void 0,
9349
9828
  height: propHeight || void 0
9350
9829
  },
9351
- trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [
9352
- withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(TooltipInPortal, {
9353
- top: tooltipTop || 0,
9354
- left: tooltipLeft || 0,
9355
- children: /* @__PURE__ */ jsx("div", {
9356
- className: "a8c-charts-scope",
9357
- role: "tooltip",
9358
- children: renderTooltip({ tooltipData })
9359
- })
9360
- }),
9361
- htmlChildren,
9362
- otherChildren
9363
- ] }),
9830
+ trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [htmlChildren, otherChildren] }),
9364
9831
  children: ({ contentWidth, contentHeight }) => {
9365
9832
  const availableSize = Math.min(contentWidth > 0 ? contentWidth : 300, contentHeight > 0 ? contentHeight : 300);
9366
9833
  const actualSize = size ? Math.min(size, availableSize) : availableSize;
@@ -9373,9 +9840,10 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9373
9840
  const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
9374
9841
  const maxCornerRadius = (outerRadius - innerRadius) / 2;
9375
9842
  const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
9376
- return /* @__PURE__ */ jsx(Center, {
9843
+ return /* @__PURE__ */ jsxs(Center, {
9377
9844
  ref: containerRef,
9378
- children: /* @__PURE__ */ jsxs("svg", {
9845
+ className: pie_chart_module_default["pie-chart__plot"],
9846
+ children: [/* @__PURE__ */ jsxs("svg", {
9379
9847
  viewBox: `0 0 ${width} ${height}`,
9380
9848
  preserveAspectRatio: "xMidYMid meet",
9381
9849
  width,
@@ -9407,11 +9875,12 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9407
9875
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
9408
9876
  const handleMouseMove = (event) => {
9409
9877
  if (!withTooltips) return;
9410
- if (containerBounds.width === 0 || containerBounds.height === 0) return;
9878
+ const bounds = containerRef.current?.getBoundingClientRect();
9879
+ if (!bounds) return;
9411
9880
  showTooltip({
9412
9881
  tooltipData: arc.data,
9413
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
9414
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY
9882
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
9883
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
9415
9884
  });
9416
9885
  };
9417
9886
  const pathProps = {
@@ -9434,20 +9903,20 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9434
9903
  const backgroundHeight = fontSize + 12;
9435
9904
  return /* @__PURE__ */ jsxs("g", {
9436
9905
  ...groupProps,
9437
- children: [/* @__PURE__ */ jsx("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ jsxs("g", { children: [providerTheme.labelBackgroundColor && /* @__PURE__ */ jsx("rect", {
9906
+ children: [/* @__PURE__ */ jsx("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
9907
+ className: pie_chart_module_default["pie-chart__label-plate"],
9438
9908
  x: centroidX - backgroundWidth / 2,
9439
9909
  y: centroidY - backgroundHeight / 2,
9440
9910
  width: backgroundWidth,
9441
9911
  height: backgroundHeight,
9442
- fill: providerTheme.labelBackgroundColor,
9443
9912
  rx: 4,
9444
9913
  ry: 4,
9445
9914
  pointerEvents: "none"
9446
9915
  }), /* @__PURE__ */ jsx("text", {
9916
+ className: pie_chart_module_default["pie-chart__label-text"],
9447
9917
  x: centroidX,
9448
9918
  y: centroidY,
9449
9919
  dy: ".33em",
9450
- fill: providerTheme.labelTextColor || "#333",
9451
9920
  fontSize,
9452
9921
  textAnchor: "middle",
9453
9922
  pointerEvents: "none",
@@ -9458,7 +9927,15 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9458
9927
  }
9459
9928
  }), !allSegmentsHidden && svgChildren]
9460
9929
  })]
9461
- })
9930
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
9931
+ top: tooltipTop || 0,
9932
+ left: tooltipLeft || 0,
9933
+ children: /* @__PURE__ */ jsx("div", {
9934
+ className: standaloneScopeClass,
9935
+ role: "tooltip",
9936
+ children: renderTooltip({ tooltipData })
9937
+ })
9938
+ })]
9462
9939
  });
9463
9940
  }
9464
9941
  })
@@ -9485,6 +9962,7 @@ var pie_semi_circle_chart_module_default = {
9485
9962
  "label": "a8ccharts-YtTOxW-label",
9486
9963
  "note": "a8ccharts-YtTOxW-note",
9487
9964
  "pie-semi-circle-chart": "a8ccharts-YtTOxW-pie-semi-circle-chart",
9965
+ "pie-semi-circle-chart__plot": "a8ccharts-YtTOxW-pie-semi-circle-chart__plot",
9488
9966
  "pie-semi-circle-chart--responsive": "a8ccharts-YtTOxW-pie-semi-circle-chart--responsive"
9489
9967
  };
9490
9968
  //#endregion
@@ -9534,23 +10012,17 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9534
10012
  const legendPosition = legend.position ?? "bottom";
9535
10013
  const chartId = useChartId(providedChartId);
9536
10014
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip();
9537
- const { containerRef, TooltipInPortal, containerBounds } = useTooltipInPortal({
9538
- detectBounds: true,
9539
- scroll: true,
9540
- debounce: 0
9541
- });
10015
+ const standaloneScopeClass = useStandaloneScopeClass();
10016
+ const containerRef = useRef(null);
9542
10017
  const handleMouseMove = useCallback((event, arc) => {
9543
- if (containerBounds.width === 0 || containerBounds.height === 0) return;
10018
+ const bounds = containerRef.current?.getBoundingClientRect();
10019
+ if (!bounds) return;
9544
10020
  showTooltip({
9545
10021
  tooltipData: arc.data,
9546
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
9547
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY
10022
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
10023
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
9548
10024
  });
9549
10025
  }, [
9550
- containerBounds.width,
9551
- containerBounds.height,
9552
- containerBounds.left,
9553
- containerBounds.top,
9554
10026
  showTooltip,
9555
10027
  tooltipOffsetX,
9556
10028
  tooltipOffsetY
@@ -9599,7 +10071,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9599
10071
  const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
9600
10072
  const errorHeight = errorWidth / 2;
9601
10073
  return /* @__PURE__ */ jsx("div", {
9602
- className: pie_semi_circle_chart_module_default["pie-semi-circle-chart"],
10074
+ className: clsx("pie-semi-circle-chart", pie_semi_circle_chart_module_default["pie-semi-circle-chart"], className),
9603
10075
  children: /* @__PURE__ */ jsx("svg", {
9604
10076
  width: errorWidth,
9605
10077
  height: errorHeight,
@@ -9646,19 +10118,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9646
10118
  width: propWidth || void 0,
9647
10119
  height: propHeight || void 0
9648
10120
  },
9649
- trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [
9650
- withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(TooltipInPortal, {
9651
- top: tooltipTop || 0,
9652
- left: tooltipLeft || 0,
9653
- children: /* @__PURE__ */ jsx("div", {
9654
- className: "a8c-charts-scope",
9655
- role: "tooltip",
9656
- children: renderTooltip({ tooltipData })
9657
- })
9658
- }),
9659
- htmlChildren,
9660
- otherChildren
9661
- ] }),
10121
+ trailingContent: /* @__PURE__ */ jsxs(Fragment$1, { children: [htmlChildren, otherChildren] }),
9662
10122
  children: ({ contentWidth, contentHeight }) => {
9663
10123
  const availableWidth = contentWidth > 0 ? contentWidth : effectiveWidth;
9664
10124
  const availableHeight = contentHeight > 0 ? contentHeight : propHeight || effectiveWidth / 2;
@@ -9666,9 +10126,10 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9666
10126
  const height = width / 2;
9667
10127
  const radius = height;
9668
10128
  const innerRadius = radius * (1 - thickness);
9669
- return /* @__PURE__ */ jsx(Center, {
10129
+ return /* @__PURE__ */ jsxs(Center, {
9670
10130
  ref: containerRef,
9671
- children: /* @__PURE__ */ jsxs("svg", {
10131
+ className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
10132
+ children: [/* @__PURE__ */ jsxs("svg", {
9672
10133
  width,
9673
10134
  height,
9674
10135
  viewBox: `0 0 ${width} ${height}`,
@@ -9726,7 +10187,15 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9726
10187
  !allSegmentsHidden && svgChildren
9727
10188
  ] })
9728
10189
  })]
9729
- })
10190
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ jsx(BoundedTooltip, {
10191
+ top: tooltipTop || 0,
10192
+ left: tooltipLeft || 0,
10193
+ children: /* @__PURE__ */ jsx("div", {
10194
+ className: standaloneScopeClass,
10195
+ role: "tooltip",
10196
+ children: renderTooltip({ tooltipData })
10197
+ })
10198
+ })]
9730
10199
  });
9731
10200
  }
9732
10201
  })
@@ -9951,6 +10420,6 @@ function TrendIndicator({ direction, value, className, style, showIcon = true })
9951
10420
  });
9952
10421
  }
9953
10422
  //#endregion
9954
- export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, GoogleDataTableColumnRoleType, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, parseRgbString, prefersLightText, relativeLuminance, resolveCssVariable, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
10423
+ export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, GoogleDataTableColumnRoleType, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getBucketInfo, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, prefersLightText, relativeLuminance, resolveCssVariable, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
9955
10424
 
9956
10425
  //# sourceMappingURL=index.js.map