@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.cjs CHANGED
@@ -40,6 +40,7 @@ let _visx_scale = require("@visx/scale");
40
40
  let _visx_group = require("@visx/group");
41
41
  let _visx_legend = require("@visx/legend");
42
42
  let _wordpress_ui = require("@wordpress/ui");
43
+ let react_dom = require("react-dom");
43
44
  let _visx_gradient = require("@visx/gradient");
44
45
  let _visx_curve = require("@visx/curve");
45
46
  let _visx_responsive = require("@visx/responsive");
@@ -174,6 +175,39 @@ const parseAsLocalDate = (dateString) => {
174
175
  return /* @__PURE__ */ new Date(NaN);
175
176
  };
176
177
  //#endregion
178
+ //#region src/utils/bucket-info.ts
179
+ const getInstant = (point) => {
180
+ if (!point) return;
181
+ if ("date" in point && point.date) return point.date.getTime();
182
+ if ("dateString" in point && point.dateString) return parseAsLocalDate(point.dateString).getTime();
183
+ };
184
+ const getPointSpacingInHours = (data) => {
185
+ return data.reduce((spacing, datom) => {
186
+ const instants = [...new Set(datom.data.map((point) => getInstant(point)))].filter((time) => time !== void 0 && Number.isFinite(time)).sort((a, b) => a - b);
187
+ return instants.reduce((seriesSpacing, time, index) => index === 0 ? seriesSpacing : Math.min(seriesSpacing, Math.abs((0, date_fns.differenceInHours)(time, instants[index - 1]))), spacing);
188
+ }, Number.POSITIVE_INFINITY);
189
+ };
190
+ const SUB_DAILY_SPACING_HOURS = 23;
191
+ const MONTHLY_SPACING_HOURS = 672;
192
+ const getBucketResolution = (data, tickResolution) => {
193
+ if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
194
+ const spacingInHours = getPointSpacingInHours(data);
195
+ if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
196
+ if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
197
+ return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
198
+ };
199
+ /**
200
+ * How this data was classified, for consumers that render their own labels.
201
+ *
202
+ * @param data - The series the chart draws, in any order, dated by `date` or `dateString`.
203
+ * @param tickResolution - Caller-declared bucket resolution, when known.
204
+ * @return The declared or inferred bucket, and the resolution formats key on.
205
+ */
206
+ const getBucketInfo = (data, tickResolution) => ({
207
+ bucket: tickResolution ?? getBucketResolution(data),
208
+ displayResolution: getBucketResolution(data, tickResolution)
209
+ });
210
+ //#endregion
177
211
  //#region src/utils/format-metric-value.ts
178
212
  /**
179
213
  * Format a numeric metric value based on type, precision and scale.
@@ -386,20 +420,6 @@ const parseHslString = (hslString) => {
386
420
  ];
387
421
  };
388
422
  /**
389
- * Parse an RGB string like 'rgb(255, 0, 0)' into a hex color.
390
- *
391
- * @deprecated Use normalizeColorToHex() instead, which handles all color formats including rgb() and rgba().
392
- * @param rgbString - RGB color string (not RGBA)
393
- * @return hex color string or null if invalid
394
- */
395
- const parseRgbString = (rgbString) => {
396
- const lower = rgbString.toLowerCase().trim();
397
- if (!lower.startsWith("rgb(") || lower.startsWith("rgba(")) return null;
398
- const parsed = (0, _visx_vendor_d3_color.color)(lower);
399
- if (!parsed) return null;
400
- return parsed.formatHex();
401
- };
402
- /**
403
423
  * Normalize any CSS color value to a hex color string.
404
424
  * Handles hex, HSL, HSLA, RGB, RGBA, named CSS colors, and CSS variables.
405
425
  *
@@ -627,6 +647,112 @@ const resolveFontSize = (val) => {
627
647
  }
628
648
  };
629
649
  //#endregion
650
+ //#region src/utils/date-formatting.ts
651
+ const formatters = /* @__PURE__ */ new Map();
652
+ const getFormatter$1 = (options, { locale, timeZone }) => {
653
+ const key = `${locale ?? ""}|${timeZone ?? ""}|${JSON.stringify(options)}`;
654
+ let formatter = formatters.get(key);
655
+ if (!formatter) {
656
+ formatter = new Intl.DateTimeFormat(locale, {
657
+ ...options,
658
+ timeZone
659
+ });
660
+ formatters.set(key, formatter);
661
+ }
662
+ return formatter;
663
+ };
664
+ const warned = /* @__PURE__ */ new Set();
665
+ const warnOnce = (key, message) => {
666
+ if (warned.has(key) || process.env.NODE_ENV === "production") return;
667
+ warned.add(key);
668
+ console.warn(`[Charts] ${message}`);
669
+ };
670
+ const isUsable = (formatting) => {
671
+ try {
672
+ Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
673
+ return true;
674
+ } catch {
675
+ return false;
676
+ }
677
+ };
678
+ const toBcp47 = (locale) => locale?.replace(/_/g, "-");
679
+ /**
680
+ * Repairs a host locale for `Intl`, dropping either half it still cannot use.
681
+ *
682
+ * Every formatter below is built during render, so an unusable string would throw
683
+ * where React unmounts the whole host tree rather than one chart. WordPress hands
684
+ * out the likeliest one either way: `timezone_string` is `''` on a site set to a
685
+ * raw UTC offset, and has no repair.
686
+ *
687
+ * @param formatting - Locale and time zone as the host supplied them.
688
+ * @return The usable halves, each `undefined` where the host's value was not.
689
+ */
690
+ const sanitizeFormatting = (formatting) => {
691
+ const repaired = {
692
+ ...formatting,
693
+ locale: toBcp47(formatting.locale)
694
+ };
695
+ if (isUsable(repaired)) return repaired;
696
+ const locale = isUsable({ locale: repaired.locale }) ? repaired.locale : void 0;
697
+ const timeZone = isUsable({ timeZone: repaired.timeZone }) ? repaired.timeZone : void 0;
698
+ 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.`);
699
+ 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".`);
700
+ return {
701
+ locale,
702
+ timeZone
703
+ };
704
+ };
705
+ /**
706
+ * A date formatter bound to the host's locale and time zone.
707
+ *
708
+ * @param options - Date-part options, as for `Intl.DateTimeFormat`.
709
+ * @param formatting - Host locale and time zone; either half absent leaves the runtime's own.
710
+ * @return Formatter taking a timestamp or a `Date`.
711
+ */
712
+ const createDateFormatter = (options, formatting) => {
713
+ return (value) => {
714
+ if (!Number.isFinite(Number(value))) return new Date(Number(value)).toLocaleDateString();
715
+ return getFormatter$1(options, formatting).format(value);
716
+ };
717
+ };
718
+ const CLOCK_OPTIONS = {
719
+ month: "numeric",
720
+ hour: "numeric",
721
+ hourCycle: "h23"
722
+ };
723
+ const NO_FIELDS = {
724
+ month: NaN,
725
+ hour: NaN
726
+ };
727
+ /**
728
+ * A clock reading calendar fields in the host's time zone.
729
+ *
730
+ * `Date`'s own getters read the browser's zone, so a chart viewed from another
731
+ * country anchors its ticks to the wrong midnights and the wrong January
732
+ * without this.
733
+ *
734
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
735
+ * @return Reader taking a `Date`.
736
+ */
737
+ const createZonedClock = (timeZone) => {
738
+ if (!timeZone) return (date) => ({
739
+ month: date.getMonth() + 1,
740
+ hour: date.getHours()
741
+ });
742
+ return (date) => {
743
+ if (!Number.isFinite(Number(date))) return NO_FIELDS;
744
+ const parts = getFormatter$1(CLOCK_OPTIONS, {
745
+ locale: "en-US",
746
+ timeZone
747
+ }).formatToParts(date);
748
+ const read = (type) => Number(parts.find((part) => part.type === type)?.value);
749
+ return {
750
+ month: read("month"),
751
+ hour: read("hour")
752
+ };
753
+ };
754
+ };
755
+ //#endregion
630
756
  //#region src/providers/chart-scope/chart-scope-context.ts
631
757
  /**
632
758
  * The nearest chart element, published so JS token resolution reads the same CSS cascade that paints the chart's own colours.
@@ -774,150 +900,29 @@ const SERIES_SLOT_1_FALLBACK = "#3858e9";
774
900
  /** The catalog pointer for every slot, in slot order. */
775
901
  const SERIES_PALETTE_POINTERS = Array.from({ length: 5 }, (_, index) => index === 0 ? `var(${seriesRole(1)}, ${SERIES_SLOT_1_FALLBACK})` : `var(${seriesRole(index + 1)})`);
776
902
  //#endregion
777
- //#region src/providers/chart-context/private/theme-override-vars.ts
778
- const ROLE_FOR_FIELD = [
779
- ["--a8c-charts-color-background", (theme) => theme.backgroundColor],
780
- ["--a8c-charts-color-grid", (theme) => theme.gridStyles?.stroke],
781
- ["--a8c-charts-color-axis", (theme) => theme.xAxisLineStyles?.stroke],
782
- ["--a8c-charts-color-tick", (theme) => theme.xTickLineStyles?.stroke],
783
- ["--a8c-charts-color-label-axis", (theme) => theme.svgLabelSmall?.fill],
784
- ["--a8c-charts-color-label-background", (theme) => theme.labelBackgroundColor],
785
- ["--a8c-charts-color-label-on-fill", (theme) => theme.labelTextColor],
786
- ...Array.from({ length: 5 }, (_, index) => [seriesRole(index + 1), (theme) => theme.colors?.[index]])
787
- ];
788
- let warnedAboutExtraColors = false;
789
- /**
790
- * Warns once when a consumer passes more colors than there are slots to publish them into.
791
- *
792
- * @param count - How many entries `theme.colors` holds.
793
- */
794
- function warnOnceAboutExtraColors(count) {
795
- if (warnedAboutExtraColors || process.env.NODE_ENV === "production") return;
796
- warnedAboutExtraColors = true;
797
- 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.`);
798
- }
799
- /**
800
- * The variable a `theme` prop override is published as, one layer outside the role itself.
801
- *
802
- * `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.
803
- *
804
- * @param role - The catalog role being overridden.
805
- * @return The custom property carrying the consumer's value.
806
- */
807
- const themeLayerVar = (role) => `${role}-theme`;
808
- ROLE_FOR_FIELD.map(([role]) => role);
809
- /**
810
- * Whether a value reads the role it is being published for, which would make the catalog entry depend on itself.
811
- *
812
- * 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`.
813
- *
814
- * Three things this has to get right, none of which a plain substring test gets:
815
- *
816
- * `var()` permits whitespace after the opening paren, so testing for `var(<role>` misses `var( --a8c-charts-color-grid, … )`.
817
- *
818
- * 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.
819
- *
820
- * `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.
821
- *
822
- * @param value - The consumer's value for the field.
823
- * @param role - The catalog role this value would override.
824
- * @return True when the value reads `role`, so it must not be published.
825
- */
826
- const readsOwnRole = (value, role) => value.trim() === role || new RegExp(`var\\(\\s*${role.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w-])`).test(value);
827
- /**
828
- * 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.
829
- *
830
- * 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.
831
- *
832
- * `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.
833
- *
834
- * @param theme - The consumer's `theme` prop, before merging with the default theme.
835
- * @return The wrapper's custom properties, and the roles the consumer overrode.
836
- */
837
- const themeOverrideVars = (theme) => {
838
- if (!theme) return {
839
- vars: {},
840
- roles: []
841
- };
842
- if (Array.isArray(theme.colors) && theme.colors.length > 5) warnOnceAboutExtraColors(theme.colors.length);
843
- const vars = {};
844
- const roles = [];
845
- for (const [role, read] of ROLE_FOR_FIELD) {
846
- const value = read(theme);
847
- if (typeof value !== "string" || value === "") continue;
848
- roles.push(role);
849
- if (!readsOwnRole(value, role)) vars[themeLayerVar(role)] = value;
850
- }
851
- return {
852
- vars,
853
- roles
854
- };
855
- };
856
- //#endregion
857
903
  //#region src/providers/chart-context/themes.ts
858
904
  /**
859
- * Default theme configuration
905
+ * Default theme configuration: the shape and spacing a consumer can override.
906
+ *
907
+ * For a color, set the matching `--a8c-charts-color-*` role in CSS instead; the `var()`
908
+ * chains that deliver those live in `private/catalog-pointers.ts`.
860
909
  */
861
910
  const defaultTheme = {
862
- backgroundColor: "var(--a8c-charts-color-background, #fff)",
863
- labelBackgroundColor: "var(--a8c-charts-color-label-background, transparent)",
864
- labelTextColor: "var(--a8c-charts-color-label-on-fill, #FFFFFF)",
865
- colors: [...SERIES_PALETTE_POINTERS],
866
- gridStyles: {
867
- stroke: "var(--a8c-charts-color-grid, #dbdbdb)",
868
- strokeWidth: 1
869
- },
911
+ gridStyles: { strokeWidth: 1 },
870
912
  tickLength: 4,
871
- gridColor: "",
872
- gridColorDark: "",
873
- xTickLineStyles: {
874
- stroke: "var(--a8c-charts-color-tick, #dbdbdb)",
875
- strokeWidth: 1
876
- },
877
- xAxisLineStyles: {
878
- stroke: "var(--a8c-charts-color-axis, #dbdbdb)",
879
- strokeWidth: 1
880
- },
913
+ xTickLineStyles: { strokeWidth: 1 },
914
+ xAxisLineStyles: { strokeWidth: 1 },
881
915
  legend: {
882
- labelStyles: { color: "var(--a8c-charts-color-label, #1e1e1e)" },
916
+ labelStyles: {},
883
917
  containerStyles: {},
884
918
  shapeStyles: []
885
919
  },
886
920
  seriesLineStyles: [],
887
921
  glyphs: [],
888
- svgLabelSmall: {
889
- fill: "var(--a8c-charts-color-label-axis, #1e1e1e)",
890
- fontFamily: "inherit"
891
- },
922
+ svgLabelSmall: { fontFamily: "inherit" },
892
923
  svgLabelBig: { fontFamily: "inherit" },
893
- annotationStyles: {
894
- label: {
895
- anchorLineStroke: "var(--a8c-charts-color-annotation, #1e1e1e)",
896
- backgroundFill: "var(--a8c-charts-color-surface, #fff)"
897
- },
898
- connector: { stroke: "var(--a8c-charts-color-annotation, #1e1e1e)" },
899
- circleSubject: {
900
- stroke: "transparent",
901
- fill: "var(--a8c-charts-color-annotation, #1e1e1e)",
902
- radius: 5
903
- }
904
- },
905
- geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)" },
906
- leaderboardChart: {
907
- rowGap: 12,
908
- columnGap: 4,
909
- labelSpacing: "xs",
910
- deltaColors: [
911
- "var(--a8c-charts-color-trend-down, #cc1818)",
912
- "var(--a8c-charts-color-trend-neutral, #707070)",
913
- "var(--a8c-charts-color-trend-up, #008030)"
914
- ]
915
- },
916
- conversionFunnelChart: {
917
- backgroundColor: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
918
- positiveChangeColor: "var(--a8c-charts-color-trend-up, #008030)",
919
- negativeChangeColor: "var(--a8c-charts-color-trend-down, #cc1818)"
920
- },
924
+ annotationStyles: {},
925
+ leaderboardChart: { labelSpacing: "xs" },
921
926
  lineChart: { lineStyles: { comparison: {
922
927
  strokeDasharray: "4 4",
923
928
  strokeLinecap: "square"
@@ -941,70 +946,9 @@ const defaultTheme = {
941
946
  }
942
947
  };
943
948
  //#endregion
944
- //#region src/providers/chart-context/private/with-catalog-pointers.ts
945
- /**
946
- * The pointer one palette slot is restored to.
947
- *
948
- * 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`.
949
- *
950
- * @param index - The slot's zero-based index in `colors`.
951
- * @param value - The merged theme's entry for it.
952
- * @return The catalog pointer for that slot.
953
- */
954
- const seriesPointer = (index, value) => {
955
- const role = seriesRole(index + 1);
956
- if (typeof value === "string" && value.startsWith(`var(${role}`)) return value;
957
- if (typeof value !== "string" || value === "") return SERIES_PALETTE_POINTERS[index];
958
- return `var(${role}, ${value})`;
959
- };
960
- const CATALOG_RESTORE_FOR_ROLE = {
961
- ...Object.fromEntries(Array.from({ length: 5 }, (_, index) => [seriesRole(index + 1), (theme) => ({ colors: SERIES_PALETTE_POINTERS.map((_pointer, slot) => seriesPointer(slot, theme.colors?.[slot])) })])),
962
- "--a8c-charts-color-background": () => ({ backgroundColor: defaultTheme.backgroundColor }),
963
- "--a8c-charts-color-grid": (theme) => ({ gridStyles: {
964
- ...theme.gridStyles,
965
- stroke: defaultTheme.gridStyles.stroke
966
- } }),
967
- "--a8c-charts-color-axis": (theme) => ({ xAxisLineStyles: {
968
- ...theme.xAxisLineStyles,
969
- stroke: defaultTheme.xAxisLineStyles.stroke
970
- } }),
971
- "--a8c-charts-color-tick": (theme) => ({ xTickLineStyles: {
972
- ...theme.xTickLineStyles,
973
- stroke: defaultTheme.xTickLineStyles.stroke
974
- } }),
975
- "--a8c-charts-color-label-axis": (theme) => ({ svgLabelSmall: {
976
- ...theme.svgLabelSmall,
977
- fill: defaultTheme.svgLabelSmall.fill
978
- } }),
979
- "--a8c-charts-color-label-background": () => ({ labelBackgroundColor: defaultTheme.labelBackgroundColor }),
980
- "--a8c-charts-color-label-on-fill": () => ({ labelTextColor: defaultTheme.labelTextColor })
981
- };
982
- /**
983
- * 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.
984
- *
985
- * 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`.
986
- *
987
- * 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.
988
- *
989
- * @param merged - The consumer theme merged onto `defaultTheme` (`mergeThemes` output).
990
- * @param overriddenRoles - The catalog roles the consumer overrode, from `themeOverrideVars`.
991
- * @return A new theme object; `merged` is not mutated.
992
- */
993
- const withCatalogPointers = (merged, overriddenRoles) => {
994
- let result = merged;
995
- for (const role of overriddenRoles) {
996
- const restore = CATALOG_RESTORE_FOR_ROLE[role];
997
- if (restore) result = {
998
- ...result,
999
- ...restore(result)
1000
- };
1001
- }
1002
- return result;
1003
- };
1004
- //#endregion
1005
949
  //#region src/providers/chart-context/global-charts-provider.tsx
1006
950
  const GlobalChartsContext = (0, react.createContext)(null);
1007
- const GlobalChartsProvider = ({ children, theme }) => {
951
+ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
1008
952
  const [charts, setCharts] = (0, react.useState)(() => /* @__PURE__ */ new Map());
1009
953
  const [hiddenSeries, setHiddenSeries] = (0, react.useState)(() => /* @__PURE__ */ new Map());
1010
954
  const seededCharts = (0, react.useRef)(/* @__PURE__ */ new Set());
@@ -1014,11 +958,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1014
958
  wrapperRef.current = node;
1015
959
  setScopeNode(node);
1016
960
  }, []);
1017
- const { vars: overrideVars, roles: overriddenRoles } = (0, react.useMemo)(() => themeOverrideVars(theme), [theme]);
1018
- const providerTheme = (0, react.useMemo)(() => {
1019
- if (!theme) return defaultTheme;
1020
- return withCatalogPointers(mergeThemes(defaultTheme, theme), overriddenRoles);
1021
- }, [theme, overriddenRoles]);
961
+ const providerTheme = (0, react.useMemo)(() => theme ? mergeThemes(defaultTheme, theme) : defaultTheme, [theme]);
1022
962
  const [colorCache, setColorCache] = (0, react.useState)(() => ({
1023
963
  colors: [],
1024
964
  hues: [],
@@ -1034,7 +974,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1034
974
  const existingHslColors = [];
1035
975
  let minHue = 360;
1036
976
  let maxHue = 0;
1037
- for (const color of providerTheme.colors ?? SERIES_PALETTE_POINTERS) {
977
+ for (const color of SERIES_PALETTE_POINTERS) {
1038
978
  const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
1039
979
  if (normalizedColor.startsWith("#")) {
1040
980
  resolvedColors.push(normalizedColor);
@@ -1059,7 +999,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1059
999
  minHue,
1060
1000
  maxHue
1061
1001
  });
1062
- }, [providerTheme]);
1002
+ }, []);
1063
1003
  (0, react.useEffect)(() => {
1064
1004
  if (colorCache.colors.length > 0) setIsColorPaletteResolved(true);
1065
1005
  }, [colorCache]);
@@ -1151,12 +1091,17 @@ const GlobalChartsProvider = ({ children, theme }) => {
1151
1091
  const set = hiddenSeries.get(chartId);
1152
1092
  return set ? new Set(set) : /* @__PURE__ */ new Set();
1153
1093
  }, [hiddenSeries]);
1094
+ const formatting = (0, react.useMemo)(() => sanitizeFormatting({
1095
+ locale,
1096
+ timeZone
1097
+ }), [locale, timeZone]);
1154
1098
  const value = (0, react.useMemo)(() => ({
1155
1099
  charts,
1156
1100
  registerChart,
1157
1101
  unregisterChart,
1158
1102
  getChartData,
1159
1103
  theme: providerTheme,
1104
+ formatting,
1160
1105
  getElementStyles,
1161
1106
  toggleSeriesVisibility,
1162
1107
  setSeriesVisibility,
@@ -1172,6 +1117,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1172
1117
  unregisterChart,
1173
1118
  getChartData,
1174
1119
  providerTheme,
1120
+ formatting,
1175
1121
  getElementStyles,
1176
1122
  toggleSeriesVisibility,
1177
1123
  setSeriesVisibility,
@@ -1187,10 +1133,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1187
1133
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1188
1134
  ref: setWrapperNode,
1189
1135
  className: CHART_SCOPE_CLASS,
1190
- style: {
1191
- display: "contents",
1192
- ...overrideVars
1193
- },
1136
+ style: { display: "contents" },
1194
1137
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartScopeContext.Provider, {
1195
1138
  value: scopeNode,
1196
1139
  children
@@ -1254,6 +1197,39 @@ const useDeepMemo = (value) => {
1254
1197
  return ref.current;
1255
1198
  };
1256
1199
  //#endregion
1200
+ //#region src/providers/chart-context/private/catalog-pointers.ts
1201
+ const CATALOG_POINTERS = {
1202
+ background: "var(--a8c-charts-color-background, #fff)",
1203
+ labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
1204
+ grid: "var(--a8c-charts-color-grid, #dbdbdb)",
1205
+ axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
1206
+ tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
1207
+ axisY: "var(--a8c-charts-color-axis-y, none)",
1208
+ tickY: "var(--a8c-charts-color-tick-y, none)",
1209
+ annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
1210
+ surface: "var(--a8c-charts-color-surface, #fff)",
1211
+ surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
1212
+ trendUp: "var(--a8c-charts-color-trend-up, #008030)",
1213
+ trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
1214
+ series: SERIES_PALETTE_POINTERS
1215
+ };
1216
+ /**
1217
+ * The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
1218
+ * rides along as the base the theme and the per-datum styles merge onto.
1219
+ */
1220
+ const ANNOTATION_POINTERS = {
1221
+ label: {
1222
+ anchorLineStroke: CATALOG_POINTERS.annotation,
1223
+ backgroundFill: CATALOG_POINTERS.surface
1224
+ },
1225
+ connector: { stroke: CATALOG_POINTERS.annotation },
1226
+ circleSubject: {
1227
+ stroke: "transparent",
1228
+ fill: CATALOG_POINTERS.annotation,
1229
+ radius: 5
1230
+ }
1231
+ };
1232
+ //#endregion
1257
1233
  //#region src/providers/chart-scope/use-chart-scope-element.ts
1258
1234
  /**
1259
1235
  * Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
@@ -1280,13 +1256,34 @@ const useXYChartTheme = (data) => {
1280
1256
  return (0, react.useMemo)(() => {
1281
1257
  const resolve = createCssVariableResolver(scopeElement);
1282
1258
  const resolveColor = (value) => value ? resolve(value) ?? value : value;
1283
- const paletteColors = [...JSON.parse(seriesColorKey), ...theme.colors ?? []].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1284
- const htmlLabelColor = resolveColor(theme.svgLabelSmall?.fill);
1259
+ const paletteColors = [...JSON.parse(seriesColorKey), ...CATALOG_POINTERS.series].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1260
+ const resolvedLabelColor = resolveColor(CATALOG_POINTERS.labelAxis);
1261
+ const htmlLabelColor = resolvedLabelColor ? normalizeColorToHex(resolvedLabelColor) || resolvedLabelColor : resolvedLabelColor;
1285
1262
  return (0, _visx_xychart.buildChartTheme)({
1286
1263
  ...theme,
1264
+ gridColor: "",
1265
+ gridColorDark: "",
1287
1266
  colors: paletteColors,
1288
- backgroundColor: resolveColor(theme.backgroundColor),
1289
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0
1267
+ backgroundColor: resolveColor(CATALOG_POINTERS.background),
1268
+ htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1269
+ gridStyles: {
1270
+ ...theme.gridStyles,
1271
+ stroke: CATALOG_POINTERS.grid
1272
+ },
1273
+ xAxisLineStyles: {
1274
+ ...theme.xAxisLineStyles,
1275
+ stroke: CATALOG_POINTERS.axisX
1276
+ },
1277
+ xTickLineStyles: {
1278
+ ...theme.xTickLineStyles,
1279
+ stroke: CATALOG_POINTERS.tickX
1280
+ },
1281
+ yAxisLineStyles: { stroke: CATALOG_POINTERS.axisY },
1282
+ yTickLineStyles: { stroke: CATALOG_POINTERS.tickY },
1283
+ svgLabelSmall: {
1284
+ ...theme.svgLabelSmall,
1285
+ fill: CATALOG_POINTERS.labelAxis
1286
+ }
1290
1287
  });
1291
1288
  }, [
1292
1289
  theme,
@@ -1691,6 +1688,21 @@ const useGlobalChartsTheme = () => {
1691
1688
  return (0, react.useContext)(GlobalChartsContext)?.theme ?? defaultTheme;
1692
1689
  };
1693
1690
  //#endregion
1691
+ //#region src/providers/chart-context/hooks/use-chart-formatting.ts
1692
+ const RUNTIME_FORMATTING = {};
1693
+ /**
1694
+ * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
1695
+ *
1696
+ * Falls back to the runtime's own rather than throwing: charts render outside a
1697
+ * provider, and a host that sets neither gets exactly the browser-default
1698
+ * behavior this package had before the context existed.
1699
+ *
1700
+ * @return The host's formatting context.
1701
+ */
1702
+ const useChartFormatting = () => {
1703
+ return (0, react.useContext)(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
1704
+ };
1705
+ //#endregion
1694
1706
  //#region src/components/legend/utils/value-or-identity.ts
1695
1707
  /**
1696
1708
  * Returns an object's value if defined, or the object.
@@ -2128,15 +2140,311 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2128
2140
  });
2129
2141
  };
2130
2142
  //#endregion
2143
+ //#region src/components/tooltip/private/bounded-tooltip.tsx
2144
+ const DEFAULT_OFFSET = 10;
2145
+ const isClipping = (element) => {
2146
+ const { overflow, overflowX, overflowY } = getComputedStyle(element);
2147
+ return [
2148
+ overflow,
2149
+ overflowX,
2150
+ overflowY
2151
+ ].some((value) => value && value !== "visible");
2152
+ };
2153
+ const findClippingAncestor = (wrapper) => {
2154
+ let element = wrapper.parentElement;
2155
+ while (element && element !== document.body) {
2156
+ if (isClipping(element)) return element;
2157
+ element = element.parentElement;
2158
+ }
2159
+ return null;
2160
+ };
2161
+ /**
2162
+ * Where the box goes, in wrapper coordinates. Below and to the right of the
2163
+ * anchor; flipped to the other side when that side clips less, as visx's
2164
+ * `TooltipWithBounds` decides it; then clamped so the box stays inside
2165
+ * `bounds` whenever it fits at all.
2166
+ *
2167
+ * @param params - Anchor, offsets, the box size and the bounds to keep inside.
2168
+ * @param params.left - Anchor x, in wrapper coordinates.
2169
+ * @param params.top - Anchor y, in wrapper coordinates.
2170
+ * @param params.offsetLeft - Gap between the anchor and the box, horizontally.
2171
+ * @param params.offsetTop - Gap between the anchor and the box, vertically.
2172
+ * @param params.box - Rendered size of the box.
2173
+ * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2174
+ * @return The box's top-left corner, rounded to whole pixels.
2175
+ */
2176
+ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) => {
2177
+ const rightX = left + offsetLeft;
2178
+ const leftX = left - offsetLeft - box.width;
2179
+ const rightOverflow = rightX + box.width - bounds.right;
2180
+ const leftOverflow = bounds.left - leftX;
2181
+ let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
2182
+ const downY = top + offsetTop;
2183
+ const upY = top - offsetTop - box.height;
2184
+ const downOverflow = downY + box.height - bounds.bottom;
2185
+ const upOverflow = bounds.top - upY;
2186
+ let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2187
+ x = Math.min(Math.max(x, bounds.left), Math.max(bounds.left, bounds.right - box.width));
2188
+ y = Math.min(Math.max(y, bounds.top), Math.max(bounds.top, bounds.bottom - box.height));
2189
+ return {
2190
+ x: Math.round(x),
2191
+ y: Math.round(y)
2192
+ };
2193
+ };
2194
+ /**
2195
+ * visx's `Tooltip`, positioned like its `TooltipWithBounds` but kept inside the
2196
+ * nearest clipping ancestor — or the viewport when there is none — rather than
2197
+ * inside its own parent. Rendered in-tree, a tooltip's parent is the chart
2198
+ * wrapper, which is often narrower than the box; measuring against the parent
2199
+ * alone would let the box spill into an `overflow: hidden` card and be cut off.
2200
+ *
2201
+ * Re-measures on every render, so a box whose content changes width between
2202
+ * two hovers is placed for its current size.
2203
+ *
2204
+ * @param props - visx `Tooltip` props.
2205
+ * @param props.left - Anchor x, in wrapper coordinates.
2206
+ * @param props.top - Anchor y, in wrapper coordinates.
2207
+ * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2208
+ * @param props.offsetTop - Gap between the anchor and the box, vertically.
2209
+ * @param props.style - Box styles; visx's defaults unless `unstyled`.
2210
+ * @param props.unstyled - Skip `style` and leave the box bare.
2211
+ * @param props.children - Box content.
2212
+ * @return The tooltip box.
2213
+ */
2214
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, ...rest }) => {
2215
+ const nodeRef = (0, react.useRef)(null);
2216
+ const clipRef = (0, react.useRef)(null);
2217
+ const [position, setPosition] = (0, react.useState)(null);
2218
+ (0, react.useLayoutEffect)(() => {
2219
+ const node = nodeRef.current;
2220
+ const wrapper = node?.parentElement;
2221
+ if (!node || !wrapper) return;
2222
+ if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2223
+ wrapper,
2224
+ clip: findClippingAncestor(wrapper)
2225
+ };
2226
+ const own = node.getBoundingClientRect();
2227
+ const wrapperRect = wrapper.getBoundingClientRect();
2228
+ const clipRect = clipRef.current.clip?.getBoundingClientRect() ?? {
2229
+ left: 0,
2230
+ top: 0,
2231
+ right: window.innerWidth,
2232
+ bottom: window.innerHeight
2233
+ };
2234
+ const next = getBoundedPosition({
2235
+ left,
2236
+ top,
2237
+ offsetLeft,
2238
+ offsetTop,
2239
+ box: {
2240
+ width: own.width,
2241
+ height: own.height
2242
+ },
2243
+ bounds: {
2244
+ left: clipRect.left - wrapperRect.left,
2245
+ top: clipRect.top - wrapperRect.top,
2246
+ right: clipRect.right - wrapperRect.left,
2247
+ bottom: clipRect.bottom - wrapperRect.top
2248
+ }
2249
+ });
2250
+ setPosition((current) => current && current.x === next.x && current.y === next.y ? current : next);
2251
+ }, [
2252
+ left,
2253
+ top,
2254
+ offsetLeft,
2255
+ offsetTop,
2256
+ children
2257
+ ]);
2258
+ const x = position?.x ?? left + offsetLeft;
2259
+ const y = position?.y ?? top + offsetTop;
2260
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_tooltip.Tooltip, {
2261
+ ref: nodeRef,
2262
+ style: {
2263
+ position: "absolute",
2264
+ left: 0,
2265
+ top: 0,
2266
+ transform: `translate(${x}px, ${y}px)`,
2267
+ ...!unstyled && style
2268
+ },
2269
+ ...rest,
2270
+ children
2271
+ });
2272
+ };
2273
+ //#endregion
2274
+ //#region src/components/tooltip/xy-chart-tooltip.tsx
2275
+ const CROSSHAIR_STROKE_WIDTH = 1.5;
2276
+ const DEFAULT_GLYPH_RADIUS = 4;
2277
+ const FALLBACK_COLOR = "#222";
2278
+ const DEFAULT_TOOLTIP_Z_INDEX = 3;
2279
+ const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
2280
+ "scroll",
2281
+ "debounce",
2282
+ "resizeObserverPolyfill"
2283
+ ]);
2284
+ const isValidNumber = (value) => typeof value === "number" && Number.isFinite(value);
2285
+ const scaleBandwidth = (scale) => {
2286
+ const bandwidth = scale?.bandwidth;
2287
+ return typeof bandwidth === "function" ? Number(bandwidth()) : 0;
2288
+ };
2289
+ const DefaultGlyph$1 = ({ x, y, size, color, glyphStyle, seriesKey }) => {
2290
+ const { theme } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2291
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2292
+ cx: x,
2293
+ cy: y,
2294
+ r: size,
2295
+ fill: color,
2296
+ stroke: theme?.backgroundColor,
2297
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2298
+ paintOrder: "fill",
2299
+ ...glyphStyle
2300
+ });
2301
+ };
2302
+ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultGlyph$1, {
2303
+ seriesKey: key,
2304
+ ...props
2305
+ }, key);
2306
+ 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 }) => {
2307
+ const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2308
+ const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2309
+ const tooltipContent = renderTooltip ? renderTooltip({
2310
+ ...tooltipContext,
2311
+ colorScale
2312
+ }) : null;
2313
+ if (tooltipContent == null) return null;
2314
+ const scaleX = xScale;
2315
+ const scaleY = yScale;
2316
+ const getDatumLeftTop = (key, datum) => {
2317
+ const entry = dataRegistry?.get(key);
2318
+ return {
2319
+ left: scaleX && entry?.xAccessor ? Number(scaleX(entry.xAccessor(datum))) + scaleBandwidth(xScale) / 2 : void 0,
2320
+ top: scaleY && entry?.yAccessor ? Number(scaleY(entry.yAccessor(datum))) + scaleBandwidth(yScale) / 2 : void 0
2321
+ };
2322
+ };
2323
+ const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
2324
+ let { tooltipLeft, tooltipTop } = tooltipContext;
2325
+ if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY)) {
2326
+ const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
2327
+ if (snapTooltipToDatumX && isValidNumber(left)) tooltipLeft = left;
2328
+ if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
2329
+ }
2330
+ const glyphs = [];
2331
+ if (showDatumGlyph || showSeriesGlyphs) {
2332
+ const size = Number(glyphStyle?.radius ?? DEFAULT_GLYPH_RADIUS);
2333
+ const labelColor = theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2334
+ const fallbackColor = showSeriesGlyphs ? labelColor : theme?.gridStyles?.stroke ?? labelColor;
2335
+ let entries = [];
2336
+ if (showSeriesGlyphs) entries = Object.values(tooltipContext.tooltipData?.datumByKey ?? {});
2337
+ else if (nearestDatum) entries = [nearestDatum];
2338
+ for (const { key, datum, index } of entries) {
2339
+ const { left, top } = getDatumLeftTop(key, datum);
2340
+ if (!isValidNumber(left) || !isValidNumber(top)) continue;
2341
+ glyphs.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
2342
+ className: "visx-tooltip-glyph",
2343
+ transform: `translate(${left}, ${top})`,
2344
+ children: renderGlyph({
2345
+ key,
2346
+ color: colorScale?.(key) ?? fallbackColor,
2347
+ datum,
2348
+ index,
2349
+ size,
2350
+ x: 0,
2351
+ y: 0,
2352
+ glyphStyle,
2353
+ isNearestDatum: nearestDatum?.key === key
2354
+ })
2355
+ }, key));
2356
+ }
2357
+ }
2358
+ const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2359
+ const marginTop = margin?.top ?? 0;
2360
+ const marginLeft = margin?.left ?? 0;
2361
+ const TooltipComponent = detectBounds ? BoundedTooltip : _visx_tooltip.Tooltip;
2362
+ const boxStyle = {
2363
+ ..._visx_tooltip.defaultStyles,
2364
+ zIndex,
2365
+ background: theme?.backgroundColor ?? "white",
2366
+ boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2367
+ ...theme?.htmlLabel
2368
+ };
2369
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2370
+ className: "visx-tooltip-overlay",
2371
+ pointerEvents: "none",
2372
+ children: [
2373
+ showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2374
+ className: "visx-crosshair visx-crosshair-vertical",
2375
+ x1: tooltipLeft,
2376
+ x2: tooltipLeft,
2377
+ y1: marginTop,
2378
+ y2: marginTop + innerHeight,
2379
+ stroke: crosshairStroke,
2380
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2381
+ ...verticalCrosshairStyle
2382
+ }),
2383
+ showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2384
+ className: "visx-crosshair visx-crosshair-horizontal",
2385
+ x1: marginLeft,
2386
+ x2: marginLeft + innerWidth,
2387
+ y1: tooltipTop,
2388
+ y2: tooltipTop,
2389
+ stroke: crosshairStroke,
2390
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2391
+ ...horizontalCrosshairStyle
2392
+ }),
2393
+ glyphs
2394
+ ]
2395
+ }), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2396
+ left: tooltipLeft,
2397
+ top: tooltipTop,
2398
+ style: boxStyle,
2399
+ applyPositionStyle: true,
2400
+ ...tooltipProps,
2401
+ children: tooltipContent
2402
+ }), container)] });
2403
+ };
2404
+ /**
2405
+ * In-tree replacement for `@visx/xychart`'s `Tooltip`.
2406
+ *
2407
+ * visx renders its tooltip box, glyphs and crosshairs through portals appended
2408
+ * to `document.body`. That puts them in the page's root stacking context, where
2409
+ * they paint above every sticky or fixed element that lives inside a nested
2410
+ * stacking context, and no z-index on that element can change the order. This
2411
+ * component draws the glyphs and crosshairs straight into the chart SVG and
2412
+ * renders the tooltip box into the SVG's parent element, so all of them stack
2413
+ * as ordinary descendants of the chart.
2414
+ *
2415
+ * Render it as a child of `XYChart`. The element wrapping that `XYChart` must
2416
+ * be `position: relative` with the SVG at its origin, and `isolation: isolate`:
2417
+ * the box is placed with the SVG-local coordinates visx reports, and the
2418
+ * isolation keeps the box's `zIndex` from competing with page chrome. The box
2419
+ * flips and clamps to stay inside the nearest ancestor that clips its overflow
2420
+ * (or the viewport), so it may extend past the chart wrapper but is never cut
2421
+ * off unless that ancestor is smaller than the box itself.
2422
+ *
2423
+ * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2424
+ * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
2425
+ */
2426
+ const XyChartTooltip = (props) => {
2427
+ const tooltipContext = (0, react.useContext)(_visx_xychart.TooltipContext);
2428
+ const [container, setContainer] = (0, react.useState)(null);
2429
+ const anchorRef = (0, react.useCallback)((node) => {
2430
+ setContainer(node?.ownerSVGElement?.parentElement ?? null);
2431
+ }, []);
2432
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", { ref: anchorRef }), tooltipContext?.tooltipOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XyChartTooltipContent, {
2433
+ ...props,
2434
+ tooltipContext,
2435
+ container
2436
+ })] });
2437
+ };
2438
+ //#endregion
2131
2439
  //#region src/components/tooltip/accessible-tooltip.tsx
2132
2440
  const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2133
2441
  const tooltipContext = (0, react.useContext)(_visx_xychart.TooltipContext);
2134
2442
  const scopeElement = useChartScopeElement();
2135
- const gridStroke = useGlobalChartsTheme().gridStyles?.stroke;
2136
2443
  const crosshairStroke = (0, react.useMemo)(() => {
2137
- const stroke = gridStroke ? resolveCssVariable(gridStroke, scopeElement) : null;
2444
+ const stroke = resolveCssVariable(CATALOG_POINTERS.grid, scopeElement);
2138
2445
  return stroke ? { stroke } : void 0;
2139
- }, [gridStroke, scopeElement]);
2446
+ }, [scopeElement]);
2447
+ const standaloneScopeClass = useStandaloneScopeClass();
2140
2448
  const tooltipData = (0, react.useMemo)(() => {
2141
2449
  if (mode !== "individual") return [];
2142
2450
  if (series.length === 0) return [];
@@ -2197,11 +2505,11 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2197
2505
  tabIndex: -1,
2198
2506
  role: "tooltip",
2199
2507
  "aria-atomic": "true",
2200
- className: (0, clsx.default)(CHART_SCOPE_CLASS, keyboardFocusedClassName),
2508
+ className: (0, clsx.default)(standaloneScopeClass, keyboardFocusedClassName),
2201
2509
  children: tooltipContent
2202
2510
  }, `chart-tooltip-${selectedIndex}`);
2203
2511
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2204
- className: CHART_SCOPE_CLASS,
2512
+ className: standaloneScopeClass,
2205
2513
  role: "tooltip",
2206
2514
  "aria-live": "polite",
2207
2515
  children: tooltipContent
@@ -2211,9 +2519,10 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2211
2519
  renderTooltip,
2212
2520
  selectedIndex,
2213
2521
  tooltipRef,
2214
- keyboardFocusedClassName
2522
+ keyboardFocusedClassName,
2523
+ standaloneScopeClass
2215
2524
  ]);
2216
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Tooltip, {
2525
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XyChartTooltip, {
2217
2526
  ...props,
2218
2527
  verticalCrosshairStyle: {
2219
2528
  ...crosshairStroke,
@@ -2561,72 +2870,90 @@ const getCurveType = (type, smoothing) => {
2561
2870
  default: return _visx_curve.curveLinear;
2562
2871
  }
2563
2872
  };
2564
- const dateTimeFormat = (options) => {
2565
- let formatter;
2566
- return (timestamp) => {
2567
- if (!Number.isFinite(Number(timestamp))) return new Date(timestamp).toLocaleDateString();
2568
- formatter = formatter ?? new Intl.DateTimeFormat(void 0, options);
2569
- return formatter.format(timestamp);
2570
- };
2571
- };
2572
- const formatYearTick = dateTimeFormat({ year: "numeric" });
2573
- const formatDateTick = dateTimeFormat({
2873
+ const YEAR_TICK = { year: "numeric" };
2874
+ const DATE_TICK = {
2574
2875
  month: "short",
2575
2876
  day: "numeric"
2576
- });
2577
- const formatHourTick = dateTimeFormat({
2578
- hour: "numeric",
2579
- hour12: true
2580
- });
2581
- const formatMonthTick = dateTimeFormat({ month: "short" });
2877
+ };
2878
+ const HOUR_TICK = { hour: "numeric" };
2879
+ const MONTH_TICK = { month: "short" };
2582
2880
  const boundaryFormat = (isAnchor, atBoundary, between) => {
2583
2881
  const format = (timestamp) => isAnchor(new Date(timestamp)) ? atBoundary(timestamp) : between(timestamp);
2584
2882
  format.isAnchor = isAnchor;
2585
2883
  return format;
2586
2884
  };
2587
- const formatDateOrHourTick = boundaryFormat((date) => date.getHours() === 0 && date.getMinutes() === 0, formatDateTick, formatHourTick);
2588
- const formatMonthOrYearTick = boundaryFormat((date) => date.getMonth() === 0, formatYearTick, formatMonthTick);
2885
+ const tickFormats = (formatting) => {
2886
+ const clock = createZonedClock(formatting.timeZone);
2887
+ const year = createDateFormatter(YEAR_TICK, formatting);
2888
+ const date = createDateFormatter(DATE_TICK, formatting);
2889
+ const hour = createDateFormatter(HOUR_TICK, formatting);
2890
+ const month = createDateFormatter(MONTH_TICK, formatting);
2891
+ const datedTick = (timestamp) => date(timestamp);
2892
+ datedTick.anchorsAtLabelChange = true;
2893
+ return {
2894
+ year,
2895
+ date: datedTick,
2896
+ dateOrHour: boundaryFormat((value) => clock(value).hour === 0, date, hour),
2897
+ hour,
2898
+ monthOrYear: boundaryFormat((value) => clock(value).month === 1, year, month)
2899
+ };
2900
+ };
2589
2901
  const getSpan = (sortedData) => {
2590
- const bounds = sortedData.map((datom) => [datom.data.at(0)?.date, datom.data.at(-1)?.date]).filter(([first, last]) => first !== void 0 && last !== void 0);
2902
+ 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)));
2591
2903
  if (!bounds.length) return null;
2592
2904
  return {
2593
2905
  minX: Math.min(...bounds.map(([first]) => Number(first))),
2594
2906
  maxX: Math.max(...bounds.map(([, last]) => Number(last)))
2595
2907
  };
2596
2908
  };
2597
- const getPointSpacingInHours = (sortedData) => {
2598
- return sortedData.reduce((spacing, datom) => datom.data.reduce((seriesSpacing, point, index) => {
2599
- const previous = datom.data[index - 1];
2600
- if (previous?.date === void 0 || point?.date === void 0) return seriesSpacing;
2601
- return Math.min(seriesSpacing, Math.abs((0, date_fns.differenceInHours)(point.date, previous.date)));
2602
- }, spacing), Number.POSITIVE_INFINITY);
2603
- };
2604
- const SUB_DAILY_SPACING_HOURS = 23;
2605
- const MONTHLY_SPACING_HOURS = 672;
2606
- const getBucketResolution = (sortedData, tickResolution) => {
2607
- if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
2608
- const spacingInHours = getPointSpacingInHours(sortedData);
2609
- if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
2610
- if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
2611
- return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
2612
- };
2613
- const getFormatter = (sortedData, tickResolution) => {
2909
+ /**
2910
+ * The most informative tick format for a bucket resolution and the span on screen.
2911
+ *
2912
+ * @param sortedData - Series as returned by `useChartDataTransform`.
2913
+ * @param tickResolution - Caller-declared bucket resolution, when known.
2914
+ * @param formatting - Host locale and time zone.
2915
+ * @param domain - The effective scale domain, or undefined for the data's own extent.
2916
+ * @return The formatter the axis labels its ticks with.
2917
+ */
2918
+ const getFormatter = (sortedData, tickResolution, formatting = {}, domain) => {
2919
+ const format = tickFormats(formatting);
2614
2920
  const resolution = getBucketResolution(sortedData, tickResolution);
2615
- if (resolution === "year") return formatYearTick;
2616
- if (resolution === "month") return formatMonthOrYearTick;
2617
- const span = getSpan(sortedData);
2618
- if (!span) return formatDateTick;
2921
+ if (resolution === "year") return format.year;
2922
+ if (resolution === "month") return format.monthOrYear;
2923
+ const span = domain ? {
2924
+ minX: domain[0].getTime(),
2925
+ maxX: domain[1].getTime()
2926
+ } : getSpan(sortedData);
2927
+ if (!span) return format.date;
2619
2928
  const diffInHours = Math.abs((0, date_fns.differenceInHours)(span.maxX, span.minX));
2620
2929
  if (resolution === "hour") {
2621
- if (diffInHours <= 24) return formatHourTick;
2622
- if (diffInHours <= 168) return formatDateOrHourTick;
2930
+ if (diffInHours < 24) return format.hour;
2931
+ if (diffInHours <= 168) return format.dateOrHour;
2623
2932
  }
2624
- return Math.abs((0, date_fns.differenceInYears)(span.maxX, span.minX)) <= 1 ? formatDateTick : formatYearTick;
2933
+ return Math.abs((0, date_fns.differenceInYears)(span.maxX, span.minX)) <= 1 ? format.date : format.year;
2934
+ };
2935
+ /**
2936
+ * The instants a set of series spans, as a scale domain.
2937
+ *
2938
+ * @param sortedData - Series as returned by `useChartDataTransform`.
2939
+ * @return Earliest and latest dated point, or undefined when nothing is dated.
2940
+ */
2941
+ const getSeriesExtent = (sortedData) => {
2942
+ const span = getSpan(sortedData);
2943
+ return span ? [new Date(span.minX), new Date(span.maxX)] : void 0;
2944
+ };
2945
+ const getAnchorIndices = (domain, tickFormatter, labels) => {
2946
+ if (tickFormatter.anchorsAtLabelChange) return labels.reduce((indices, label, index) => {
2947
+ if (index === 0 || label !== labels[index - 1]) indices.push(index);
2948
+ return indices;
2949
+ }, []);
2950
+ const { isAnchor } = tickFormatter;
2951
+ if (!isAnchor) return [];
2952
+ return domain.reduce((indices, date, index) => {
2953
+ if (isAnchor(date)) indices.push(index);
2954
+ return indices;
2955
+ }, []);
2625
2956
  };
2626
- const getAnchorIndices = (domain, isAnchor) => domain.reduce((indices, date, index) => {
2627
- if (isAnchor(date)) indices.push(index);
2628
- return indices;
2629
- }, []);
2630
2957
  const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
2631
2958
  const steps = /* @__PURE__ */ new Set();
2632
2959
  for (let count = maxTicks; count > 1; count--) {
@@ -2641,26 +2968,25 @@ const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
2641
2968
  return steps;
2642
2969
  };
2643
2970
  /**
2644
- * Tick values for a band time axis.
2971
+ * Tick values sampled by index, for the bar chart's band axis and for evenly
2972
+ * spaced points on a continuous one.
2645
2973
  *
2646
- * visx samples a band domain by index from offset zero, blind to which labels
2647
- * carry a boundary and without collapsing repeats — so the tick that prints the
2648
- * year or the date often isn't sampled at all, and a long series can show the
2649
- * same label twice. Choose the values instead: sweep the evenly spaced
2650
- * candidates, including those that step from anchor to anchor, and keep the one
2651
- * that reaches the most anchors without thinning the axis or putting two
2652
- * identical labels side by side.
2974
+ * visx samples by index from offset zero, blind to which labels carry a boundary
2975
+ * and without collapsing repeats — so the tick that prints the year or the date
2976
+ * often isn't sampled at all, and a long series can show the same label twice.
2977
+ * Choose the values instead: sweep the evenly spaced candidates, including those
2978
+ * that step from anchor to anchor, and keep the one that reaches the most anchors
2979
+ * without thinning the axis or putting two identical labels side by side.
2653
2980
  *
2654
- * @param domain - Band domain, in axis order.
2981
+ * @param domain - Buckets in axis order.
2655
2982
  * @param tickFormatter - Formatter the axis will render these values with.
2656
2983
  * @param maxTicks - Most ticks the axis should carry.
2657
2984
  * @return Values to hand the axis as `tickValues`.
2658
2985
  */
2659
2986
  const getBandTickValues = (domain, tickFormatter, maxTicks) => {
2660
2987
  if (!domain.length) return [];
2661
- const { isAnchor } = tickFormatter;
2662
- const anchorIndices = isAnchor ? getAnchorIndices(domain, isAnchor) : [];
2663
2988
  const domainLabels = domain.map((date) => tickFormatter(date.getTime()));
2989
+ const anchorIndices = getAnchorIndices(domain, tickFormatter, domainLabels);
2664
2990
  const candidates = [];
2665
2991
  const consider = (indices) => {
2666
2992
  if (!indices.length || indices.length > maxTicks) return;
@@ -2672,15 +2998,108 @@ const getBandTickValues = (domain, tickFormatter, maxTicks) => {
2672
2998
  for (let index = offset; index < domain.length; index += step) indices.push(index);
2673
2999
  consider(indices);
2674
3000
  }
2675
- for (let stride = 1; stride <= anchorIndices.length; stride++) consider(anchorIndices.filter((_, position) => position % stride === 0));
3001
+ const minStride = Math.max(1, Math.ceil(anchorIndices.length / maxTicks));
3002
+ for (let stride = minStride; stride <= anchorIndices.length; stride++) {
3003
+ const stepped = [];
3004
+ for (let position = 0; position < anchorIndices.length; position += stride) stepped.push(anchorIndices[position]);
3005
+ consider(stepped);
3006
+ }
2676
3007
  if (!candidates.length) return [domain[0]];
2677
3008
  const densest = candidates.reduce((most, indices) => Math.max(most, indices.length), 0);
2678
- const anchorsIn = (indices) => isAnchor ? indices.filter((index) => isAnchor(domain[index])).length : 0;
3009
+ const anchorIndexSet = new Set(anchorIndices);
3010
+ const anchorsIn = (indices) => indices.filter((index) => anchorIndexSet.has(index)).length;
2679
3011
  return candidates.filter((indices) => indices.length >= densest - 1).reduce((chosen, indices) => {
2680
3012
  const gain = anchorsIn(indices) - anchorsIn(chosen);
2681
3013
  return gain > 0 || gain === 0 && indices.length > chosen.length ? indices : chosen;
2682
3014
  }).map((index) => domain[index]);
2683
3015
  };
3016
+ /**
3017
+ * The most x-axis ticks a chart of this width has room for.
3018
+ *
3019
+ * @param chartWidth - Chart width in pixels.
3020
+ * @return A tick budget of at least one.
3021
+ */
3022
+ const getMaxTicksForWidth = (chartWidth) => Math.max(1, Math.floor(chartWidth / X_TICK_WIDTH));
3023
+ const MIN_GAP_IN_LABELS = .9;
3024
+ const nearestValue = (sorted, target) => {
3025
+ let low = 0;
3026
+ let high = sorted.length - 1;
3027
+ while (low < high) {
3028
+ const middle = Math.floor((low + high) / 2);
3029
+ if (sorted[middle] < target) low = middle + 1;
3030
+ else high = middle;
3031
+ }
3032
+ const above = sorted[low];
3033
+ const below = low > 0 ? sorted[low - 1] : above;
3034
+ return Math.abs(above - target) <= Math.abs(below - target) ? above : below;
3035
+ };
3036
+ const getPositionTickValues = (points, tickFormatter, maxTicks, axis) => {
3037
+ const times = points.map((point) => point.getTime());
3038
+ const { first, last } = axis;
3039
+ if (maxTicks <= 1 || times.length === 1 || first === last) return [points[0]];
3040
+ const step = (last - first) / (maxTicks - 1);
3041
+ const labels = points.map((point) => tickFormatter(point.getTime()));
3042
+ const minGap = (last - first) / maxTicks * MIN_GAP_IN_LABELS;
3043
+ const anchors = getAnchorIndices(points, tickFormatter, labels).map((index) => times[index]);
3044
+ const chosen = [];
3045
+ let lastLabel = null;
3046
+ for (let index = 0; index < maxTicks; index++) {
3047
+ const target = first + index * step;
3048
+ const anchor = anchors.length ? nearestValue(anchors, target) : null;
3049
+ let tick = anchor !== null && Math.abs(anchor - target) <= step / 2 ? anchor : null;
3050
+ if (tick === null) {
3051
+ const point = nearestValue(times, target);
3052
+ tick = Math.abs(point - target) <= step / 2 ? point : target;
3053
+ }
3054
+ const previous = chosen[chosen.length - 1];
3055
+ if (previous !== void 0 && (tick <= previous || tick - previous < minGap)) continue;
3056
+ const label = tickFormatter(tick);
3057
+ if (label === lastLabel) continue;
3058
+ chosen.push(tick);
3059
+ lastLabel = label;
3060
+ }
3061
+ return chosen.map((timestamp) => new Date(timestamp));
3062
+ };
3063
+ const isIndexSamplingUnusable = (ticks, points, maxTicks, axis) => {
3064
+ if (ticks.length < 2 || points.length < 2) return false;
3065
+ const span = axis.last - axis.first;
3066
+ if (!span) return false;
3067
+ 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;
3068
+ };
3069
+ /**
3070
+ * Tick values for a continuous time axis, chosen from the points it draws.
3071
+ *
3072
+ * Sampled by index while the points are evenly spaced, which keeps the axis on
3073
+ * the calendar boundaries `getBandTickValues` steers towards, and by position
3074
+ * once they are not, where an index stride crowds one end of the scale.
3075
+ *
3076
+ * Values are taken from the data wherever there is one to take, so a local time
3077
+ * that does not exist cannot become a tick and DST needs no special case. Only a
3078
+ * target stranded in a gap is constructed, as a plain instant.
3079
+ *
3080
+ * @param sortedData - Series as returned by `useChartDataTransform`.
3081
+ * @param domain - The effective scale domain, or undefined for all data.
3082
+ * @param tickFormatter - The formatter the ticks will be labeled with.
3083
+ * @param maxTicks - The most ticks the axis has room for.
3084
+ * @return Tick dates, or null when no series carries a usable date.
3085
+ */
3086
+ const getTimeAxisTickValues = (sortedData, domain, tickFormatter, maxTicks) => {
3087
+ const byTimestamp = /* @__PURE__ */ new Map();
3088
+ for (const series of sortedData) for (const point of series.data) {
3089
+ const date = point.date;
3090
+ if (date instanceof Date && Number.isFinite(date.getTime())) byTimestamp.set(date.getTime(), date);
3091
+ }
3092
+ if (!byTimestamp.size) return null;
3093
+ const min = domain?.[0]?.getTime();
3094
+ const max = domain?.[1]?.getTime();
3095
+ 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));
3096
+ const axis = {
3097
+ first: min ?? visible[0].getTime(),
3098
+ last: max ?? visible[visible.length - 1].getTime()
3099
+ };
3100
+ const ticks = getBandTickValues(visible, tickFormatter, maxTicks);
3101
+ return isIndexSamplingUnusable(ticks, visible, maxTicks, axis) ? getPositionTickValues(visible, tickFormatter, maxTicks, axis) : ticks;
3102
+ };
2684
3103
  const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
2685
3104
  const span = getSpan(data);
2686
3105
  if (!span) return 1;
@@ -2695,7 +3114,43 @@ const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
2695
3114
  if (ticks.some((tick, idx) => idx > 0 && tick === ticks[idx - 1])) continue;
2696
3115
  return ticks.length;
2697
3116
  }
2698
- return secondBestGuess;
3117
+ return Math.max(1, secondBestGuess);
3118
+ };
3119
+ //#endregion
3120
+ //#region src/charts/private/time-axis-options.ts
3121
+ const toDateDomain = (domain) => domain ? [new Date(domain[0]), new Date(domain[1])] : void 0;
3122
+ /**
3123
+ * The x-axis options for a continuous time chart, shared by LineChart and AreaChart.
3124
+ *
3125
+ * Kept in one place so the tick-selection logic isn't duplicated between them.
3126
+ *
3127
+ * @param args - Named arguments.
3128
+ * @param args.dataSorted - Series as returned by `useChartDataTransform`.
3129
+ * @param args.width - Chart width in pixels, which bounds how many ticks fit.
3130
+ * @param args.axisOptions - The caller's `options.axis.x`.
3131
+ * @param args.scaleDomain - The caller's `options.xScale.domain`.
3132
+ * @param args.zoomDomain - The chart's current zoom window, when zoomed.
3133
+ * @param args.formatting - Host locale and time zone.
3134
+ * @param args.isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it. Defaults to all of them.
3135
+ * @return Options ready to spread into the chart's `axis.x`; `orientation` is always set.
3136
+ */
3137
+ const buildTimeAxisOptions = ({ dataSorted, width, axisOptions, scaleDomain, zoomDomain, formatting, isSeriesRendered }) => {
3138
+ const { tickResolution, tickFormat, tickValues: callerTickValues, numTicks: callerNumTicks, orientation: callerOrientation, display: callerDisplay, ...rest } = axisOptions ?? {};
3139
+ const rendered = isSeriesRendered ? dataSorted.filter(isSeriesRendered) : dataSorted;
3140
+ const effectiveDomain = zoomDomain ?? toDateDomain(scaleDomain) ?? getSeriesExtent(rendered);
3141
+ const ownFormatter = getFormatter(rendered, tickResolution, formatting, effectiveDomain);
3142
+ const formatter = tickFormat || ownFormatter;
3143
+ const ownTickValues = tickFormat || callerTickValues ? null : getTimeAxisTickValues(rendered, effectiveDomain, ownFormatter, callerNumTicks ?? getMaxTicksForWidth(width));
3144
+ const tickValues = callerTickValues ?? (ownTickValues?.length ? ownTickValues : void 0);
3145
+ const numTicks = callerNumTicks ?? (tickValues ? void 0 : guessOptimalNumTicks(rendered, width, formatter));
3146
+ return {
3147
+ orientation: callerOrientation ?? "bottom",
3148
+ tickFormat: formatter,
3149
+ display: callerDisplay ?? true,
3150
+ ...rest,
3151
+ ...tickValues ? { tickValues } : {},
3152
+ ...numTicks === void 0 ? {} : { numTicks }
3153
+ };
2699
3154
  };
2700
3155
  //#endregion
2701
3156
  //#region src/charts/private/with-responsive/with-responsive.module.scss
@@ -2976,6 +3431,9 @@ function ZoomResetButton({ onClick }) {
2976
3431
  });
2977
3432
  }
2978
3433
  //#endregion
3434
+ //#region src/charts/private/xy-plot/xy-plot.module.scss
3435
+ var xy_plot_module_default = { "xy-plot": "a8ccharts-bIyhgG-xy-plot" };
3436
+ //#endregion
2979
3437
  //#region src/charts/line-chart/line-chart.module.scss
2980
3438
  var line_chart_module_default = {
2981
3439
  "line-chart": "a8ccharts-inuQka-line-chart",
@@ -3216,12 +3674,14 @@ const getVerticalAnchor = (subjectType, isFlippedVertically, y, yMax, height) =>
3216
3674
  };
3217
3675
  const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", styles: datumStyles, testId, renderLabel, renderLabelPopover }) => {
3218
3676
  const providerTheme = useGlobalChartsTheme();
3219
- const scopeElement = useChartScopeElement();
3220
3677
  const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext) || {};
3221
3678
  const labelRef = (0, react.useRef)(null);
3222
3679
  const [height, setHeight] = (0, react.useState)(null);
3223
- const styles = (0, deepmerge.default)(providerTheme.annotationStyles ?? {}, datumStyles ?? {});
3224
- const resolveColor = (value) => value ? resolveCssVariable(value, scopeElement) ?? value : value;
3680
+ const styles = (0, react.useMemo)(() => deepmerge.default.all([
3681
+ ANNOTATION_POINTERS,
3682
+ providerTheme.annotationStyles ?? {},
3683
+ datumStyles ?? {}
3684
+ ]), [providerTheme.annotationStyles, datumStyles]);
3225
3685
  (0, react.useEffect)(() => {
3226
3686
  if (labelRef.current?.getBBox) {
3227
3687
  const bbox = labelRef.current.getBBox();
@@ -3307,15 +3767,8 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
3307
3767
  dx,
3308
3768
  dy,
3309
3769
  children: [
3310
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.Connector, {
3311
- ...styles?.connector,
3312
- stroke: resolveColor(styles?.connector?.stroke)
3313
- }),
3314
- subjectType === "circle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.CircleSubject, {
3315
- ...styles?.circleSubject,
3316
- fill: resolveColor(styles?.circleSubject?.fill),
3317
- stroke: resolveColor(styles?.circleSubject?.stroke)
3318
- }),
3770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.Connector, { ...styles?.connector }),
3771
+ subjectType === "circle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.CircleSubject, { ...styles?.circleSubject }),
3319
3772
  subjectType === "line-vertical" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.LineSubject, {
3320
3773
  min: yMax,
3321
3774
  max: yMin,
@@ -3349,8 +3802,6 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
3349
3802
  title,
3350
3803
  subtitle,
3351
3804
  ...styles?.label,
3352
- anchorLineStroke: resolveColor(styles?.label?.anchorLineStroke),
3353
- backgroundFill: resolveColor(styles?.label?.backgroundFill),
3354
3805
  ...labelPosition,
3355
3806
  horizontalAnchor: getHorizontalAnchor(subjectType, isFlippedHorizontally),
3356
3807
  verticalAnchor: getVerticalAnchor(subjectType, isFlippedVertically, y, yMax, height ?? ANNOTATION_INIT_HEIGHT)
@@ -3398,16 +3849,33 @@ const toNumber = (val) => {
3398
3849
  const num = typeof val === "number" ? val : parseFloat(val);
3399
3850
  return isNaN(num) ? void 0 : num;
3400
3851
  };
3852
+ const TOOLTIP_DATE = {};
3853
+ const TOOLTIP_FORMAT_BY_RESOLUTION$1 = {
3854
+ hour: {
3855
+ year: "numeric",
3856
+ month: "numeric",
3857
+ day: "numeric",
3858
+ hour: "numeric"
3859
+ },
3860
+ day: TOOLTIP_DATE,
3861
+ month: TOOLTIP_DATE,
3862
+ year: TOOLTIP_DATE
3863
+ };
3864
+ const TooltipDate = ({ date, displayResolution }) => {
3865
+ const formatting = useChartFormatting();
3866
+ const format = (0, react.useMemo)(() => createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION$1[displayResolution], formatting), [displayResolution, formatting]);
3867
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: date ? format(date) : null });
3868
+ };
3401
3869
  /**
3402
3870
  * Default visx-tooltip render that prints the hovered date as a heading and
3403
3871
  * one row per visible series (label + formatted value), sorted descending by
3404
3872
  * value. Reused by AreaChart, which has the same multi-series shape.
3405
3873
  *
3406
- * @param params - visx `RenderTooltipParams< DataPointDate >`.
3874
+ * @param params - visx `RenderTooltipParams< DataPointDate >`, plus the chart's optional `bucketInfo`.
3407
3875
  * @return Tooltip JSX, or `null` when no datum is hovered.
3408
3876
  */
3409
3877
  const renderDefaultTooltip = (params) => {
3410
- const { tooltipData } = params;
3878
+ const { tooltipData, bucketInfo } = params;
3411
3879
  const nearestDatum = tooltipData?.nearestDatum?.datum;
3412
3880
  if (!nearestDatum) return null;
3413
3881
  const tooltipPoints = Object.entries(tooltipData?.datumByKey || {}).map(([key, { datum }]) => ({
@@ -3418,7 +3886,10 @@ const renderDefaultTooltip = (params) => {
3418
3886
  className: line_chart_module_default["line-chart__tooltip"],
3419
3887
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3420
3888
  className: line_chart_module_default["line-chart__tooltip-date"],
3421
- children: nearestDatum.date?.toLocaleDateString()
3889
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
3890
+ date: nearestDatum.date,
3891
+ displayResolution: bucketInfo?.displayResolution ?? "day"
3892
+ })
3422
3893
  }), tooltipPoints.map((point) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
3423
3894
  direction: "row",
3424
3895
  align: "center",
@@ -3467,9 +3938,9 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3467
3938
  const legendCollapseGroups = legend.collapseGroups ?? false;
3468
3939
  const legendShape = legend.shape ?? "line";
3469
3940
  const legendPosition = legend.position ?? "bottom";
3470
- const providerTheme = useGlobalChartsTheme();
3941
+ const formatting = useChartFormatting();
3471
3942
  const theme = useXYChartTheme(data);
3472
- const resolvedBackgroundColor = theme.backgroundColor ?? providerTheme.backgroundColor;
3943
+ const resolvedBackgroundColor = theme.backgroundColor;
3473
3944
  const chartId = useChartId(providedChartId);
3474
3945
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
3475
3946
  const isSeriesVisible = (0, react.useCallback)((seriesLabel) => !hiddenSeries.has(seriesLabel), [hiddenSeries]);
@@ -3543,17 +4014,17 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3543
4014
  onActivate: activateSelectedPoint
3544
4015
  });
3545
4016
  const chartOptions = (0, react.useMemo)(() => {
3546
- const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
3547
- const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
3548
4017
  return {
3549
4018
  axis: {
3550
- x: {
3551
- orientation: "bottom",
3552
- numTicks: guessOptimalNumTicks(dataSorted, width, formatter),
3553
- tickFormat: formatter,
3554
- display: true,
3555
- ...xAxisOptions
3556
- },
4019
+ x: buildTimeAxisOptions({
4020
+ dataSorted,
4021
+ width,
4022
+ axisOptions: options?.axis?.x,
4023
+ scaleDomain: options?.xScale?.domain,
4024
+ zoomDomain: zoom.domain,
4025
+ formatting,
4026
+ isSeriesRendered: (series) => isSeriesVisible(series.label)
4027
+ }),
3557
4028
  y: {
3558
4029
  orientation: "left",
3559
4030
  numTicks: 4,
@@ -3580,7 +4051,14 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3580
4051
  dataSorted,
3581
4052
  width,
3582
4053
  zoom.domain,
3583
- stableYDomain
4054
+ stableYDomain,
4055
+ formatting,
4056
+ isSeriesVisible
4057
+ ]);
4058
+ const bucketInfo = (0, react.useMemo)(() => getBucketInfo(dataSorted.filter((series) => isSeriesVisible(series.label)), options?.axis?.x?.tickResolution), [
4059
+ dataSorted,
4060
+ isSeriesVisible,
4061
+ options?.axis?.x?.tickResolution
3584
4062
  ]);
3585
4063
  const tooltipRenderGlyph = (0, react.useMemo)(() => {
3586
4064
  return (props) => {
@@ -3642,6 +4120,10 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3642
4120
  xAccessor: (d) => d?.date,
3643
4121
  yAccessor: (d) => d?.value
3644
4122
  };
4123
+ const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip({
4124
+ ...params,
4125
+ bucketInfo
4126
+ }), [renderTooltip, bucketInfo]);
3645
4127
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3646
4128
  className: (0, clsx.default)("line-chart", line_chart_module_default["line-chart"]),
3647
4129
  children: error
@@ -3690,7 +4172,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3690
4172
  onBlur: onChartBlur,
3691
4173
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3692
4174
  ref: chartRef,
3693
- style: { position: "relative" },
4175
+ className: xy_plot_module_default["xy-plot"],
3694
4176
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
3695
4177
  theme,
3696
4178
  width,
@@ -3782,7 +4264,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3782
4264
  snapTooltipToDatumX: true,
3783
4265
  snapTooltipToDatumY: true,
3784
4266
  showSeriesGlyphs: true,
3785
- renderTooltip,
4267
+ renderTooltip: tooltipRenderer,
3786
4268
  renderGlyph: tooltipRenderGlyph,
3787
4269
  glyphStyle,
3788
4270
  showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
@@ -3920,12 +4402,11 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
3920
4402
  };
3921
4403
  //#endregion
3922
4404
  //#region src/charts/area-chart/area-chart.tsx
3923
- const AreaChartInternal = (0, react.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) => {
4405
+ const AreaChartInternal = (0, react.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) => {
3924
4406
  const legendInteractive = legend.interactive ?? false;
3925
4407
  const legendShape = legend.shape ?? "rect";
3926
4408
  const legendPosition = legend.position ?? "bottom";
3927
- const rescaleYOnVisibility = rescaleYOnVisibilityChange ?? rescaleYOnLegendToggle ?? true;
3928
- const providerTheme = useGlobalChartsTheme();
4409
+ const formatting = useChartFormatting();
3929
4410
  const theme = useXYChartTheme(data);
3930
4411
  const chartId = useChartId(providedChartId);
3931
4412
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -3975,7 +4456,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3975
4456
  totalPoints: dataSorted[0]?.data.length || 0
3976
4457
  });
3977
4458
  const fixedYDomain = (0, react.useMemo)(() => {
3978
- if (rescaleYOnVisibility || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
4459
+ if (rescaleYOnVisibilityChange || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
3979
4460
  if (stacked) {
3980
4461
  const numPoints = Math.max(...dataSorted.map((s) => s.data.length));
3981
4462
  let posMax = 0;
@@ -4009,20 +4490,19 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4009
4490
  dataSorted,
4010
4491
  stacked,
4011
4492
  stackOffset,
4012
- rescaleYOnVisibility
4493
+ rescaleYOnVisibilityChange
4013
4494
  ]);
4014
4495
  const chartOptions = (0, react.useMemo)(() => {
4015
- const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
4016
- const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
4017
4496
  return {
4018
4497
  axis: {
4019
- x: {
4020
- orientation: "bottom",
4021
- numTicks: guessOptimalNumTicks(dataSorted, width, formatter),
4022
- tickFormat: formatter,
4023
- display: true,
4024
- ...xAxisOptions
4025
- },
4498
+ x: buildTimeAxisOptions({
4499
+ dataSorted,
4500
+ width,
4501
+ axisOptions: options?.axis?.x,
4502
+ scaleDomain: options?.xScale?.domain,
4503
+ zoomDomain: zoom.domain,
4504
+ formatting
4505
+ }),
4026
4506
  y: {
4027
4507
  orientation: "left",
4028
4508
  numTicks: 4,
@@ -4050,7 +4530,8 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4050
4530
  width,
4051
4531
  stacked,
4052
4532
  fixedYDomain,
4053
- zoom.domain
4533
+ zoom.domain,
4534
+ formatting
4054
4535
  ]);
4055
4536
  const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme);
4056
4537
  const error = validateData$3(dataSorted);
@@ -4086,9 +4567,17 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4086
4567
  };
4087
4568
  const zeroYAccessor = (0, react.useCallback)(() => 0, []);
4088
4569
  const visibleLabels = (0, react.useMemo)(() => new Set(seriesWithVisibility.filter((s) => s.isVisible).map((s) => s.series.label)), [seriesWithVisibility]);
4570
+ const bucketInfo = (0, react.useMemo)(() => getBucketInfo(dataSorted.filter((series) => visibleLabels.has(series.label)), options?.axis?.x?.tickResolution), [
4571
+ dataSorted,
4572
+ visibleLabels,
4573
+ options?.axis?.x?.tickResolution
4574
+ ]);
4089
4575
  const filteredRenderTooltip = (0, react.useCallback)((params) => {
4090
4576
  const datumByKey = params?.tooltipData?.datumByKey;
4091
- if (!datumByKey) return renderTooltip(params);
4577
+ if (!datumByKey) return renderTooltip({
4578
+ ...params,
4579
+ bucketInfo
4580
+ });
4092
4581
  const filtered = Object.fromEntries(Object.entries(datumByKey).filter(([key]) => visibleLabels.has(key)));
4093
4582
  if (Object.keys(filtered).length === 0) return null;
4094
4583
  const nearestDatum = params?.tooltipData?.nearestDatum;
@@ -4098,13 +4587,18 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4098
4587
  };
4099
4588
  return renderTooltip({
4100
4589
  ...params,
4590
+ bucketInfo,
4101
4591
  tooltipData: {
4102
4592
  ...params.tooltipData,
4103
4593
  datumByKey: filtered,
4104
4594
  nearestDatum: nextNearest
4105
4595
  }
4106
4596
  });
4107
- }, [renderTooltip, visibleLabels]);
4597
+ }, [
4598
+ renderTooltip,
4599
+ visibleLabels,
4600
+ bucketInfo
4601
+ ]);
4108
4602
  const resolvedFillOpacity = fillOpacity ?? (stacked ? .85 : .4);
4109
4603
  const resolvedWithStroke = withStroke ?? !stacked;
4110
4604
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4179,7 +4673,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4179
4673
  onBlur: onChartBlur,
4180
4674
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4181
4675
  ref: chartRef,
4182
- style: { position: "relative" },
4676
+ className: xy_plot_module_default["xy-plot"],
4183
4677
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
4184
4678
  theme,
4185
4679
  width,
@@ -4235,7 +4729,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4235
4729
  stacked,
4236
4730
  stackOffset,
4237
4731
  getElementStyles,
4238
- strokeColor: theme.backgroundColor ?? providerTheme.backgroundColor
4732
+ strokeColor: theme.backgroundColor
4239
4733
  })] }),
4240
4734
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AreaChartScalesRef, {
4241
4735
  chartRef: internalChartRef,
@@ -4403,8 +4897,7 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
4403
4897
  year: "numeric",
4404
4898
  month: "long",
4405
4899
  day: "numeric",
4406
- hour: "numeric",
4407
- hour12: true
4900
+ hour: "numeric"
4408
4901
  },
4409
4902
  day: {
4410
4903
  year: "numeric",
@@ -4423,16 +4916,16 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
4423
4916
  *
4424
4917
  * @param data - Date-based series, already parsed and sorted by `useChartDataTransform`.
4425
4918
  * @param tickResolution - Caller-declared bucket resolution, when known.
4919
+ * @param formatting - Host locale and time zone.
4426
4920
  * @return Tooltip label formatter.
4427
4921
  */
4428
- const getTooltipFormatter = (data, tickResolution) => {
4429
- if (tickResolution === "week") return (timestamp) => (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("Week of %s", "jetpack-charts"), new Date(timestamp).toLocaleDateString(void 0, {
4430
- year: "numeric",
4431
- month: "long",
4432
- day: "numeric"
4433
- }));
4922
+ const getTooltipFormatter = (data, tickResolution, formatting) => {
4923
+ if (tickResolution === "week") {
4924
+ const formatDay = createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION.day, formatting);
4925
+ return (timestamp) => (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("Week of %s", "jetpack-charts"), formatDay(timestamp));
4926
+ }
4434
4927
  const format = TOOLTIP_FORMAT_BY_RESOLUTION[getBucketResolution(data, tickResolution)] ?? TOOLTIP_FORMAT_BY_RESOLUTION.day;
4435
- return (timestamp) => new Date(timestamp).toLocaleString(void 0, format);
4928
+ return createDateFormatter(format, formatting);
4436
4929
  };
4437
4930
  const identity = (label) => label;
4438
4931
  const byBucket = (format) => (bucket) => typeof bucket === "string" ? bucket : format(Number(bucket));
@@ -4483,6 +4976,7 @@ const getGroupPadding = (scale) => {
4483
4976
  */
4484
4977
  function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = ALL_RENDERED) {
4485
4978
  const stableOptions = useDeepMemo(options);
4979
+ const formatting = useChartFormatting();
4486
4980
  const axisConfig = (0, react.useMemo)(() => {
4487
4981
  const { labelOverflow: xLabelOverflow, tickResolution: xTickResolution, tickFormat: xTickFormat, ...xAxisOptions } = stableOptions.axis?.x || {};
4488
4982
  const { labelOverflow: yLabelOverflow, tickResolution: yTickResolution, tickFormat: yTickFormat, ...yAxisOptions } = stableOptions.axis?.y || {};
@@ -4509,9 +5003,9 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
4509
5003
  zero: false
4510
5004
  };
4511
5005
  const hasLabels = Boolean(data?.[0]?.data?.[0]?.label);
4512
- const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution);
5006
+ const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution, formatting);
4513
5007
  const labelFormatter = timeTickFormatter ? byBucket(timeTickFormatter) : identity;
4514
- const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution));
5008
+ const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution, formatting));
4515
5009
  const valueFormatter = _automattic_number_formatters.formatNumberCompact;
4516
5010
  const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
4517
5011
  const valueAccessor = (d) => {
@@ -4547,7 +5041,8 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
4547
5041
  }, [
4548
5042
  data,
4549
5043
  tickResolution,
4550
- isSeriesRendered
5044
+ isSeriesRendered,
5045
+ formatting
4551
5046
  ]);
4552
5047
  return (0, react.useMemo)(() => {
4553
5048
  const { xTickFormat: defaultXTickFormat, yTickFormat: defaultYTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
@@ -5056,6 +5551,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5056
5551
  onBlur: onChartBlur,
5057
5552
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5058
5553
  ref: chartRef,
5554
+ className: xy_plot_module_default["xy-plot"],
5059
5555
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5060
5556
  theme,
5061
5557
  width,
@@ -5223,7 +5719,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
5223
5719
  right: 20,
5224
5720
  bottom: 0,
5225
5721
  top: 0
5226
- }, ...rest }) => {
5722
+ }, className, ...rest }) => {
5227
5723
  const { getElementStyles } = useGlobalChartsContext();
5228
5724
  const tintedData = (0, react.useMemo)(() => {
5229
5725
  if (data.length > 1) return data;
@@ -5275,6 +5771,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
5275
5771
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarChart, {
5276
5772
  orientation: "horizontal",
5277
5773
  gridVisibility: "none",
5774
+ className: (0, clsx.default)("bar-list-chart", className),
5278
5775
  data: tintedData,
5279
5776
  width,
5280
5777
  height,
@@ -5311,6 +5808,9 @@ const BarListChartResponsive = withResponsive(BarListChart);
5311
5808
  var conversion_funnel_chart_module_default = {
5312
5809
  "bar-container": "a8ccharts-mGEVca-bar-container",
5313
5810
  "change-indicator": "a8ccharts-mGEVca-change-indicator",
5811
+ "change-indicator--negative": "a8ccharts-mGEVca-change-indicator--negative",
5812
+ "change-indicator--positive": "a8ccharts-mGEVca-change-indicator--positive",
5813
+ "conversion-funnel-chart": "a8ccharts-mGEVca-conversion-funnel-chart",
5314
5814
  "conversion-funnel-chart--loading": "a8ccharts-mGEVca-conversion-funnel-chart--loading",
5315
5815
  "empty-state": "a8ccharts-mGEVca-empty-state",
5316
5816
  "funnel-bar": "a8ccharts-mGEVca-funnel-bar",
@@ -5391,23 +5891,14 @@ const useFunnelSelection = (hideTooltip) => {
5391
5891
  */
5392
5892
  const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
5393
5893
  const chartId = useChartId(providedChartId);
5394
- const { conversionFunnelChart: conversionFunnelChartSettings } = useGlobalChartsTheme();
5395
5894
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
5396
5895
  const chartRef = (0, react.useRef)(null);
5397
5896
  const selectedBarRef = (0, react.useRef)(null);
5398
5897
  const [scopeNode, setScopeNode] = (0, react.useState)(null);
5399
5898
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
5400
5899
  const { handleBarClick, handleBarKeyDown, clearSelection, getStepState } = useFunnelSelection(hideTooltip);
5401
- const { containerRef: portalContainerRef, TooltipInPortal, containerBounds } = (0, _visx_tooltip.useTooltipInPortal)({
5402
- detectBounds: true,
5403
- scroll: true
5404
- });
5405
- const portalContainerRefRef = (0, react.useRef)(portalContainerRef);
5406
- (0, react.useEffect)(() => {
5407
- portalContainerRefRef.current = portalContainerRef;
5408
- }, [portalContainerRef]);
5900
+ const standaloneScopeClass = useStandaloneScopeClass();
5409
5901
  const setChartRef = (0, react.useCallback)((node) => {
5410
- portalContainerRefRef.current(node);
5411
5902
  chartRef.current = node;
5412
5903
  setScopeNode(node);
5413
5904
  }, []);
@@ -5424,30 +5915,22 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5424
5915
  });
5425
5916
  }, [showTooltip]);
5426
5917
  const getMouseTooltipCoords = (0, react.useCallback)((event) => {
5427
- if (containerBounds.width === 0 || containerBounds.height === 0) return null;
5918
+ const bounds = chartRef.current?.getBoundingClientRect();
5919
+ if (!bounds) return null;
5428
5920
  return {
5429
- x: event.clientX - containerBounds.left,
5430
- y: event.clientY - containerBounds.top
5921
+ x: event.clientX - bounds.left,
5922
+ y: event.clientY - bounds.top
5431
5923
  };
5432
- }, [
5433
- containerBounds.width,
5434
- containerBounds.height,
5435
- containerBounds.left,
5436
- containerBounds.top
5437
- ]);
5924
+ }, []);
5438
5925
  const getKeyboardTooltipCoords = (0, react.useCallback)((event) => {
5439
- if (containerBounds.width === 0 || containerBounds.height === 0) return null;
5926
+ const bounds = chartRef.current?.getBoundingClientRect();
5927
+ if (!bounds) return null;
5440
5928
  const rect = event.currentTarget.getBoundingClientRect();
5441
5929
  return {
5442
- x: rect.left + rect.width / 2 - containerBounds.left,
5443
- y: rect.top - containerBounds.top
5930
+ x: rect.left + rect.width / 2 - bounds.left,
5931
+ y: rect.top - bounds.top
5444
5932
  };
5445
- }, [
5446
- containerBounds.width,
5447
- containerBounds.height,
5448
- containerBounds.left,
5449
- containerBounds.top
5450
- ]);
5933
+ }, []);
5451
5934
  const handleStepInteraction = (0, react.useCallback)((step, event, interactionType) => {
5452
5935
  selectedBarRef.current = event.currentTarget;
5453
5936
  const { isClicked } = getStepState(step.id);
@@ -5508,19 +5991,14 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5508
5991
  };
5509
5992
  }, [clearSelectionAndRef]);
5510
5993
  const resolvedHeight = height ?? style?.height ?? "100%";
5511
- const { primaryColor, backgroundColor, positiveChangeColor, negativeChangeColor } = conversionFunnelChartSettings;
5512
- const { color: barColor } = getElementStyles ? getElementStyles({
5513
- index: 0,
5514
- overrideColor: primaryColor
5515
- }) : { color: primaryColor || "#000000" };
5516
- const changeColor = changeIndicator?.startsWith("+") ? positiveChangeColor : negativeChangeColor;
5517
- const barBackgroundColor = backgroundColor || hexToRgba(barColor, .08) || "rgba(0, 0, 0, 0.08)";
5994
+ const { color: barColor } = getElementStyles ? getElementStyles({ index: 0 }) : { color: "#000000" };
5995
+ const isPositiveChange = changeIndicator?.startsWith("+");
5996
+ const changeColor = isPositiveChange ? CATALOG_POINTERS.trendUp : CATALOG_POINTERS.trendDown;
5518
5997
  const renderDefaultMainMetric = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5519
5998
  className: conversion_funnel_chart_module_default["main-rate"],
5520
5999
  children: formatPercentage(mainRate)
5521
6000
  }), changeIndicator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5522
- className: conversion_funnel_chart_module_default["change-indicator"],
5523
- style: { color: changeColor },
6001
+ className: (0, clsx.default)(conversion_funnel_chart_module_default["change-indicator"], conversion_funnel_chart_module_default[isPositiveChange ? "change-indicator--positive" : "change-indicator--negative"]),
5524
6002
  children: changeIndicator
5525
6003
  })] });
5526
6004
  const renderDefaultTooltip = (step) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
@@ -5558,7 +6036,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5558
6036
  align: "center",
5559
6037
  justify: "center",
5560
6038
  ref: setScopeNode,
5561
- className: (0, clsx.default)(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
6039
+ className: (0, clsx.default)("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
5562
6040
  style: {
5563
6041
  ...style,
5564
6042
  height: resolvedHeight
@@ -5569,16 +6047,16 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5569
6047
  })
5570
6048
  });
5571
6049
  const maxRate = Math.max(...steps.map((step) => step.rate));
5572
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
6050
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
5573
6051
  direction: "column",
5574
6052
  gap: "xl",
5575
6053
  ref: setChartRef,
5576
- className: (0, clsx.default)(conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
6054
+ className: (0, clsx.default)("conversion-funnel-chart", conversion_funnel_chart_module_default["conversion-funnel-chart"], loading && conversion_funnel_chart_module_default["conversion-funnel-chart--loading"], className),
5577
6055
  style: {
5578
6056
  ...style,
5579
6057
  height: resolvedHeight
5580
6058
  },
5581
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartScopeContext.Provider, {
6059
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartScopeContext.Provider, {
5582
6060
  value: scopeNode,
5583
6061
  children: [renderMainMetric ? renderMainMetric({
5584
6062
  mainRate,
@@ -5630,7 +6108,6 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5630
6108
  role: "button",
5631
6109
  tabIndex: isBlurred ? -1 : 0,
5632
6110
  "aria-label": step.label,
5633
- style: { backgroundColor: barBackgroundColor },
5634
6111
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5635
6112
  className: (0, clsx.default)(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
5636
6113
  style: {
@@ -5642,23 +6119,23 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5642
6119
  }, step.id);
5643
6120
  })
5644
6121
  })]
5645
- })
5646
- }), tooltipOpen && tooltipData && (() => {
5647
- const tooltipContent = renderTooltip ? renderTooltip({
5648
- step: tooltipData,
5649
- index: steps.findIndex((s) => s.id === tooltipData.id),
5650
- top: tooltipTop,
5651
- left: tooltipLeft,
5652
- className: conversion_funnel_chart_module_default["tooltip-wrapper"]
5653
- }) : renderDefaultTooltip(tooltipData);
5654
- if (!tooltipContent) return null;
5655
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipInPortal, {
5656
- top: tooltipTop,
5657
- left: tooltipLeft,
5658
- className: (0, clsx.default)("a8c-charts-scope", conversion_funnel_chart_module_default["tooltip-wrapper"]),
5659
- children: tooltipContent
5660
- }, Math.random());
5661
- })()] });
6122
+ }), tooltipOpen && tooltipData && (() => {
6123
+ const tooltipContent = renderTooltip ? renderTooltip({
6124
+ step: tooltipData,
6125
+ index: steps.findIndex((s) => s.id === tooltipData.id),
6126
+ top: tooltipTop,
6127
+ left: tooltipLeft,
6128
+ className: conversion_funnel_chart_module_default["tooltip-wrapper"]
6129
+ }) : renderDefaultTooltip(tooltipData);
6130
+ if (!tooltipContent) return null;
6131
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
6132
+ top: tooltipTop,
6133
+ left: tooltipLeft,
6134
+ className: (0, clsx.default)(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
6135
+ children: tooltipContent
6136
+ });
6137
+ })()]
6138
+ });
5662
6139
  };
5663
6140
  /**
5664
6141
  * ConversionFunnelChart component with provider wrapper
@@ -5804,7 +6281,7 @@ function normalizeGeoChartError(eventArgs) {
5804
6281
  * @return A React component displaying an interactive map with data visualization
5805
6282
  */
5806
6283
  const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
5807
- const { getElementStyles, theme: { geoChart: { featureFillColor }, backgroundColor } } = useGlobalChartsContext();
6284
+ const { getElementStyles } = useGlobalChartsContext();
5808
6285
  const scopeElement = useChartScopeElement();
5809
6286
  const containerRef = (0, react.useRef)(null);
5810
6287
  const reportedErrorIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
@@ -5838,8 +6315,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
5838
6315
  });
5839
6316
  const fullColorHex = getElementStyles({ index: 0 }).color;
5840
6317
  const lightColorHex = lightenHexColor(fullColorHex, .8);
5841
- const backgroundColorHex = normalizeColorToHex(backgroundColor, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
5842
- const defaultFillColorHex = normalizeColorToHex(featureFillColor, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
6318
+ const backgroundColorHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
6319
+ const defaultFillColorHex = normalizeColorToHex(CATALOG_POINTERS.surfaceSecondary, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
5843
6320
  const sanitizedData = (0, react.useMemo)(() => {
5844
6321
  if (data.length === 0) return {
5845
6322
  data,
@@ -5900,8 +6377,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
5900
6377
  className: (0, clsx.default)("geo-chart", geo_chart_module_default.container, className),
5901
6378
  style: {
5902
6379
  width,
5903
- height,
5904
- backgroundColor
6380
+ height
5905
6381
  },
5906
6382
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_google_charts.Chart, {
5907
6383
  chartType: "GeoChart",
@@ -5951,6 +6427,7 @@ var heatmap_chart_module_default = {
5951
6427
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
5952
6428
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
5953
6429
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
6430
+ "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
5954
6431
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
5955
6432
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
5956
6433
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
@@ -6093,7 +6570,7 @@ const HeatmapContext = (0, react.createContext)(null);
6093
6570
  //#region src/charts/heatmap-chart/private/heatmap-legend.tsx
6094
6571
  const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6095
6572
  const context = (0, react.useContext)(HeatmapContext);
6096
- const { legend, backgroundColor } = useGlobalChartsTheme();
6573
+ const { legend } = useGlobalChartsTheme();
6097
6574
  if (!context) return null;
6098
6575
  const { primaryColorHex } = context;
6099
6576
  const labelStyle = legend.labelStyles;
@@ -6104,6 +6581,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6104
6581
  children: [
6105
6582
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6106
6583
  variant: "body-sm",
6584
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6107
6585
  style: labelStyle,
6108
6586
  children: lessLabel ?? (0, _wordpress_i18n.__)("Less", "jetpack-charts")
6109
6587
  }),
@@ -6117,7 +6595,6 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6117
6595
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6118
6596
  style: {
6119
6597
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6120
- "--a8c-charts-color-heatmap-background": backgroundColor,
6121
6598
  "--a8c-charts-heatmap-cell-intensity": intensity
6122
6599
  }
6123
6600
  }, index);
@@ -6125,6 +6602,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6125
6602
  }),
6126
6603
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6127
6604
  variant: "body-sm",
6605
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6128
6606
  style: labelStyle,
6129
6607
  children: moreLabel ?? (0, _wordpress_i18n.__)("More", "jetpack-charts")
6130
6608
  })
@@ -6142,17 +6620,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6142
6620
  const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
6143
6621
  const [selectedIndex, setSelectedIndex] = (0, react.useState)();
6144
6622
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
6145
- const { containerRef, containerBounds, TooltipInPortal } = (0, _visx_tooltip.useTooltipInPortal)({
6146
- detectBounds: true,
6147
- scroll: true
6148
- });
6149
- const containerBoundsRef = (0, react.useRef)(containerBounds);
6150
- containerBoundsRef.current = containerBounds;
6623
+ const standaloneScopeClass = useStandaloneScopeClass();
6624
+ const containerRef = (0, react.useRef)(null);
6625
+ const getTooltipOrigin = (0, react.useCallback)(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
6151
6626
  const { color: primaryColorHex } = getElementStyles({
6152
6627
  index: 0,
6153
- overrideColor: primaryColor || heatmapChartSettings.primaryColor
6628
+ overrideColor: primaryColor
6154
6629
  });
6155
- const chartBackgroundHex = normalizeColorToHex(theme.backgroundColor, scopeElement, resolveCssVariable);
6630
+ const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
6156
6631
  const primaryHex = normalizeColorToHex(primaryColorHex);
6157
6632
  const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
6158
6633
  const extent = (0, react.useMemo)(() => getValueExtent(data), [data]);
@@ -6228,19 +6703,21 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6228
6703
  ]);
6229
6704
  const handleCellMouseMove = (0, react.useCallback)((event) => {
6230
6705
  if (!withTooltips) return;
6706
+ const origin = getTooltipOrigin();
6707
+ if (!origin) return;
6231
6708
  const target = event.currentTarget;
6232
6709
  const columnIndex = Number(target.dataset.column);
6233
6710
  const rowIndex = Number(target.dataset.row);
6234
- const bounds = containerBoundsRef.current;
6235
6711
  showTooltip({
6236
- tooltipLeft: event.clientX - bounds.left,
6237
- tooltipTop: event.clientY - bounds.top,
6712
+ tooltipLeft: event.clientX - origin.left,
6713
+ tooltipTop: event.clientY - origin.top,
6238
6714
  tooltipData: buildTooltipData(columnIndex, rowIndex)
6239
6715
  });
6240
6716
  }, [
6241
6717
  withTooltips,
6242
6718
  showTooltip,
6243
- buildTooltipData
6719
+ buildTooltipData,
6720
+ getTooltipOrigin
6244
6721
  ]);
6245
6722
  const handleCellMouseLeave = (0, react.useCallback)(() => {
6246
6723
  if (withTooltips && selectedIndex === void 0) hideTooltip();
@@ -6251,13 +6728,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6251
6728
  ]);
6252
6729
  (0, react.useEffect)(() => {
6253
6730
  if (!withTooltips || selectedIndex === void 0) return;
6731
+ const origin = getTooltipOrigin();
6732
+ if (!origin) return;
6254
6733
  const col = Math.floor(selectedIndex / rows);
6255
6734
  const row = selectedIndex % rows;
6256
6735
  const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
6257
- const bounds = containerBoundsRef.current;
6258
6736
  showTooltip({
6259
- tooltipLeft: rect ? rect.left + rect.width / 2 - bounds.left : 0,
6260
- tooltipTop: rect ? rect.top + rect.height / 2 - bounds.top : 0,
6737
+ tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
6738
+ tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
6261
6739
  tooltipData: buildTooltipData(col, row)
6262
6740
  });
6263
6741
  }, [
@@ -6266,7 +6744,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6266
6744
  rows,
6267
6745
  chartId,
6268
6746
  buildTooltipData,
6269
- showTooltip
6747
+ showTooltip,
6748
+ getTooltipOrigin
6270
6749
  ]);
6271
6750
  const defaultRenderTooltip = (0, react.useCallback)((info) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim() }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value) })] }), []);
6272
6751
  if (!columns || !rows) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
@@ -6284,7 +6763,6 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6284
6763
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6285
6764
  const gridStyle = {
6286
6765
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6287
- "--a8c-charts-color-heatmap-background": theme.backgroundColor,
6288
6766
  gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
6289
6767
  gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
6290
6768
  };
@@ -6382,11 +6860,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6382
6860
  })]
6383
6861
  }, `row-${rowIndex}`);
6384
6862
  })]
6385
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipInPortal, {
6863
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
6386
6864
  top: tooltipTop,
6387
6865
  left: tooltipLeft,
6388
6866
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6389
- className: "a8c-charts-scope",
6867
+ className: standaloneScopeClass,
6390
6868
  role: "tooltip",
6391
6869
  tabIndex: -1,
6392
6870
  children: (renderTooltip ?? defaultRenderTooltip)(tooltipData)
@@ -6410,7 +6888,7 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ (0, react_jsx_run
6410
6888
  HeatmapChartResponsiveInner.displayName = "HeatmapChart";
6411
6889
  const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
6412
6890
  //#endregion
6413
- //#region ../../../node_modules/.pnpm/is-plain-object@5.0.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6891
+ //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6414
6892
  /*!
6415
6893
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
6416
6894
  *
@@ -6773,7 +7251,7 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
6773
7251
  tslib_1.__exportStar(require_dist$1(), exports);
6774
7252
  }));
6775
7253
  //#endregion
6776
- //#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
7254
+ //#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
6777
7255
  function useUpdateEffect(effect, deps) {
6778
7256
  const mountedRef = (0, react.useRef)(false);
6779
7257
  (0, react.useEffect)(() => {
@@ -8411,7 +8889,7 @@ _createEmotion.css;
8411
8889
  _createEmotion.sheet;
8412
8890
  _createEmotion.cache;
8413
8891
  //#endregion
8414
- //#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
8892
+ //#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
8415
8893
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
8416
8894
  var useCx = () => {
8417
8895
  const cache = __unsafe_useEmotionCache();
@@ -8518,7 +8996,7 @@ function memize(fn, options) {
8518
8996
  return memoized;
8519
8997
  }
8520
8998
  //#endregion
8521
- //#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
8999
+ //#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
8522
9000
  var white = "#fff";
8523
9001
  var GRAY = {
8524
9002
  900: "#1e1e1e",
@@ -8597,7 +9075,7 @@ var COLORS = Object.freeze({
8597
9075
  ui: UI
8598
9076
  });
8599
9077
  //#endregion
8600
- //#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
9078
+ //#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
8601
9079
  var CONTROL_HEIGHT = "36px";
8602
9080
  var CONTROL_PROPS = {
8603
9081
  controlPaddingX: 12,
@@ -8612,9 +9090,6 @@ var CONTROL_PROPS = {
8612
9090
  };
8613
9091
  var config_values_default = Object.assign({}, CONTROL_PROPS, {
8614
9092
  colorDivider: "rgba(0, 0, 0, 0.1)",
8615
- colorScrollbarThumb: "rgba(0, 0, 0, 0.2)",
8616
- colorScrollbarThumbHover: "rgba(0, 0, 0, 0.5)",
8617
- colorScrollbarTrack: "rgba(0, 0, 0, 0.04)",
8618
9093
  elevationIntensity: 1,
8619
9094
  radiusXSmall: "1px",
8620
9095
  radiusSmall: "2px",
@@ -8625,7 +9100,6 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
8625
9100
  borderWidth: "1px",
8626
9101
  borderWidthFocus: "1.5px",
8627
9102
  borderWidthTab: "4px",
8628
- spinnerSize: 16,
8629
9103
  fontSize: "13px",
8630
9104
  fontSizeH1: "calc(2.44 * 13px)",
8631
9105
  fontSizeH2: "calc(1.95 * 13px)",
@@ -8702,10 +9176,10 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8702
9176
  };
8703
9177
  }));
8704
9178
  //#endregion
8705
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.53.0/node_modules/@wordpress/warning/build-module/utils.mjs
9179
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
8706
9180
  var logged = /* @__PURE__ */ new Set();
8707
9181
  //#endregion
8708
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.53.0/node_modules/@wordpress/warning/build-module/index.mjs
9182
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
8709
9183
  function isDev() {
8710
9184
  return globalThis.SCRIPT_DEBUG === true;
8711
9185
  }
@@ -8719,7 +9193,7 @@ function warning(message) {
8719
9193
  logged.add(message);
8720
9194
  }
8721
9195
  //#endregion
8722
- //#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
9196
+ //#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
8723
9197
  var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
8724
9198
  var ComponentsContext = (0, react.createContext)(
8725
9199
  /** @type {Record<string, any>} */
@@ -8746,19 +9220,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
8746
9220
  };
8747
9221
  (0, react.memo)(BaseContextSystemProvider);
8748
9222
  //#endregion
8749
- //#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
9223
+ //#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
8750
9224
  var COMPONENT_NAMESPACE = "data-wp-component";
8751
9225
  var CONNECTED_NAMESPACE = "data-wp-c16t";
8752
9226
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
8753
9227
  //#endregion
8754
- //#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
9228
+ //#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
8755
9229
  var import_dist = require_dist();
8756
9230
  function getStyledClassName(namespace) {
8757
9231
  return `components-${(0, import_dist.paramCase)(namespace)}`;
8758
9232
  }
8759
9233
  var getStyledClassNameFromKey = memize(getStyledClassName);
8760
9234
  //#endregion
8761
- //#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
9235
+ //#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
8762
9236
  function contextConnect(Component, namespace) {
8763
9237
  return _contextConnect(Component, namespace, { forwardsRef: true });
8764
9238
  }
@@ -8775,7 +9249,7 @@ function _contextConnect(Component, namespace, options) {
8775
9249
  });
8776
9250
  }
8777
9251
  //#endregion
8778
- //#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
9252
+ //#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
8779
9253
  function getNamespace(componentName) {
8780
9254
  return { [COMPONENT_NAMESPACE]: componentName };
8781
9255
  }
@@ -8783,7 +9257,7 @@ function getConnectedNamespace() {
8783
9257
  return { [CONNECTED_NAMESPACE]: true };
8784
9258
  }
8785
9259
  //#endregion
8786
- //#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
9260
+ //#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
8787
9261
  function useContextSystem(props, namespace) {
8788
9262
  const contextSystemProps = useComponentsContext();
8789
9263
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -8803,7 +9277,7 @@ function useContextSystem(props, namespace) {
8803
9277
  return finalComponentProps;
8804
9278
  }
8805
9279
  //#endregion
8806
- //#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
9280
+ //#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
8807
9281
  var visuallyHidden = {
8808
9282
  border: 0,
8809
9283
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -8819,7 +9293,7 @@ var visuallyHidden = {
8819
9293
  wordBreak: "normal"
8820
9294
  };
8821
9295
  //#endregion
8822
- //#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
9296
+ //#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
8823
9297
  var customAttributeRegExp = /^(data|aria|x)-/i;
8824
9298
  var eventHandlerRegExp = /^on[A-Z]/;
8825
9299
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -8909,7 +9383,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
8909
9383
  }
8910
9384
  var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
8911
9385
  //#endregion
8912
- //#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
9386
+ //#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
8913
9387
  function UnforwardedView({ css, ...restProps }, ref) {
8914
9388
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
8915
9389
  ref,
@@ -8918,7 +9392,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
8918
9392
  }
8919
9393
  var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
8920
9394
  //#endregion
8921
- //#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
9395
+ //#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
8922
9396
  function UnconnectedVisuallyHidden(props, forwardedRef) {
8923
9397
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
8924
9398
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -8933,7 +9407,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
8933
9407
  }
8934
9408
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
8935
9409
  //#endregion
8936
- //#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
9410
+ //#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
8937
9411
  var breakpoints = [
8938
9412
  "40em",
8939
9413
  "52em",
@@ -8967,7 +9441,7 @@ function useResponsiveValue(values, options = {}) {
8967
9441
  return array[index >= array.length ? array.length - 1 : index];
8968
9442
  }
8969
9443
  //#endregion
8970
- //#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
9444
+ //#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
8971
9445
  var ALIGNMENTS = {
8972
9446
  bottom: {
8973
9447
  alignItems: "flex-end",
@@ -9015,7 +9489,7 @@ function getAlignmentProps(alignment) {
9015
9489
  return alignment ? ALIGNMENTS[alignment] : {};
9016
9490
  }
9017
9491
  //#endregion
9018
- //#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
9492
+ //#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
9019
9493
  function useGrid(props) {
9020
9494
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
9021
9495
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -9036,7 +9510,7 @@ function useGrid(props) {
9036
9510
  justifyContent: justify,
9037
9511
  verticalAlign: isInline ? "middle" : void 0,
9038
9512
  ...alignmentProps
9039
- }, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBdURzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBFeHRlcm5hbCBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuXG4vKipcbiAqIFdvcmRQcmVzcyBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5cbi8qKlxuICogSW50ZXJuYWwgZGVwZW5kZW5jaWVzXG4gKi9cbmltcG9ydCB0eXBlIHsgV29yZFByZXNzQ29tcG9uZW50UHJvcHMgfSBmcm9tICcuLi9jb250ZXh0JztcbmltcG9ydCB7IHVzZUNvbnRleHRTeXN0ZW0gfSBmcm9tICcuLi9jb250ZXh0JztcbmltcG9ydCB7IGdldEFsaWdubWVudFByb3BzIH0gZnJvbSAnLi91dGlscyc7XG5pbXBvcnQgeyB1c2VSZXNwb25zaXZlVmFsdWUgfSBmcm9tICcuLi91dGlscy91c2UtcmVzcG9uc2l2ZS12YWx1ZSc7XG5pbXBvcnQgQ09ORklHIGZyb20gJy4uL3V0aWxzL2NvbmZpZy12YWx1ZXMnO1xuaW1wb3J0IHsgdXNlQ3ggfSBmcm9tICcuLi91dGlscy9ob29rcy91c2UtY3gnO1xuaW1wb3J0IHR5cGUgeyBHcmlkUHJvcHMgfSBmcm9tICcuL3R5cGVzJztcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlR3JpZChcblx0cHJvcHM6IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzPCBHcmlkUHJvcHMsICdkaXYnID5cbikge1xuXHRjb25zdCB7XG5cdFx0YWxpZ24sXG5cdFx0YWxpZ25tZW50LFxuXHRcdGNsYXNzTmFtZSxcblx0XHRjb2x1bW5HYXAsXG5cdFx0Y29sdW1ucyA9IDIsXG5cdFx0Z2FwID0gMyxcblx0XHRpc0lubGluZSA9IGZhbHNlLFxuXHRcdGp1c3RpZnksXG5cdFx0cm93R2FwLFxuXHRcdHJvd3MsXG5cdFx0dGVtcGxhdGVDb2x1bW5zLFxuXHRcdHRlbXBsYXRlUm93cyxcblx0XHQuLi5vdGhlclByb3BzXG5cdH0gPSB1c2VDb250ZXh0U3lzdGVtKCBwcm9wcywgJ0dyaWQnICk7XG5cblx0Y29uc3QgY29sdW1uc0FzQXJyYXkgPSBBcnJheS5pc0FycmF5KCBjb2x1bW5zICkgPyBjb2x1bW5zIDogWyBjb2x1bW5zIF07XG5cdGNvbnN0IGNvbHVtbiA9IHVzZVJlc3BvbnNpdmVWYWx1ZSggY29sdW1uc0FzQXJyYXkgKTtcblx0Y29uc3Qgcm93c0FzQXJyYXkgPSBBcnJheS5pc0FycmF5KCByb3dzICkgPyByb3dzIDogWyByb3dzIF07XG5cdGNvbnN0IHJvdyA9IHVzZVJlc3BvbnNpdmVWYWx1ZSggcm93c0FzQXJyYXkgKTtcblxuXHRjb25zdCBncmlkVGVtcGxhdGVDb2x1bW5zID1cblx0XHR0ZW1wbGF0ZUNvbHVtbnMgfHwgKCAhISBjb2x1bW5zICYmIGByZXBlYXQoICR7IGNvbHVtbiB9LCAxZnIgKWAgKTtcblx0Y29uc3QgZ3JpZFRlbXBsYXRlUm93cyA9XG5cdFx0dGVtcGxhdGVSb3dzIHx8ICggISEgcm93cyAmJiBgcmVwZWF0KCAkeyByb3cgfSwgMWZyIClgICk7XG5cblx0Y29uc3QgY3ggPSB1c2VDeCgpO1xuXG5cdGNvbnN0IGNsYXNzZXMgPSB1c2VNZW1vKCAoKSA9PiB7XG5cdFx0Y29uc3QgYWxpZ25tZW50UHJvcHMgPSBnZXRBbGlnbm1lbnRQcm9wcyggYWxpZ25tZW50ICk7XG5cblx0XHRjb25zdCBncmlkQ2xhc3NlcyA9IGNzcygge1xuXHRcdFx0YWxpZ25JdGVtczogYWxpZ24sXG5cdFx0XHRkaXNwbGF5OiBpc0lubGluZSA/ICdpbmxpbmUtZ3JpZCcgOiAnZ3JpZCcsXG5cdFx0XHRnYXA6IGBjYWxjKCAkeyBDT05GSUcuZ3JpZEJhc2UgfSAqICR7IGdhcCB9IClgLFxuXHRcdFx0Z3JpZFRlbXBsYXRlQ29sdW1uczogZ3JpZFRlbXBsYXRlQ29sdW1ucyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkVGVtcGxhdGVSb3dzOiBncmlkVGVtcGxhdGVSb3dzIHx8IHVuZGVmaW5lZCxcblx0XHRcdGdyaWRSb3dHYXA6IHJvd0dhcCxcblx0XHRcdGdyaWRDb2x1bW5HYXA6IGNvbHVtbkdhcCxcblx0XHRcdGp1c3RpZnlDb250ZW50OiBqdXN0aWZ5LFxuXHRcdFx0dmVydGljYWxBbGlnbjogaXNJbmxpbmUgPyAnbWlkZGxlJyA6IHVuZGVmaW5lZCxcblx0XHRcdC4uLmFsaWdubWVudFByb3BzLFxuXHRcdH0gKTtcblxuXHRcdHJldHVybiBjeCggZ3JpZENsYXNzZXMsIGNsYXNzTmFtZSApO1xuXHR9LCBbXG5cdFx0YWxpZ24sXG5cdFx0YWxpZ25tZW50LFxuXHRcdGNsYXNzTmFtZSxcblx0XHRjb2x1bW5HYXAsXG5cdFx0Y3gsXG5cdFx0Z2FwLFxuXHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnMsXG5cdFx0Z3JpZFRlbXBsYXRlUm93cyxcblx0XHRpc0lubGluZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XSApO1xuXG5cdHJldHVybiB7IC4uLm90aGVyUHJvcHMsIGNsYXNzTmFtZTogY2xhc3NlcyB9O1xufVxuIl19 */");
9513
+ }, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBNENzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5pbXBvcnQgdHlwZSB7IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyB1c2VDb250ZXh0U3lzdGVtIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyBnZXRBbGlnbm1lbnRQcm9wcyB9IGZyb20gJy4vdXRpbHMnO1xuaW1wb3J0IHsgdXNlUmVzcG9uc2l2ZVZhbHVlIH0gZnJvbSAnLi4vdXRpbHMvdXNlLXJlc3BvbnNpdmUtdmFsdWUnO1xuaW1wb3J0IENPTkZJRyBmcm9tICcuLi91dGlscy9jb25maWctdmFsdWVzJztcbmltcG9ydCB7IHVzZUN4IH0gZnJvbSAnLi4vdXRpbHMvaG9va3MvdXNlLWN4JztcbmltcG9ydCB0eXBlIHsgR3JpZFByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIHVzZUdyaWQoXG5cdHByb3BzOiBXb3JkUHJlc3NDb21wb25lbnRQcm9wczwgR3JpZFByb3BzLCAnZGl2JyA+XG4pIHtcblx0Y29uc3Qge1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGNvbHVtbnMgPSAyLFxuXHRcdGdhcCA9IDMsXG5cdFx0aXNJbmxpbmUgPSBmYWxzZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XHRyb3dzLFxuXHRcdHRlbXBsYXRlQ29sdW1ucyxcblx0XHR0ZW1wbGF0ZVJvd3MsXG5cdFx0Li4ub3RoZXJQcm9wc1xuXHR9ID0gdXNlQ29udGV4dFN5c3RlbSggcHJvcHMsICdHcmlkJyApO1xuXG5cdGNvbnN0IGNvbHVtbnNBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggY29sdW1ucyApID8gY29sdW1ucyA6IFsgY29sdW1ucyBdO1xuXHRjb25zdCBjb2x1bW4gPSB1c2VSZXNwb25zaXZlVmFsdWUoIGNvbHVtbnNBc0FycmF5ICk7XG5cdGNvbnN0IHJvd3NBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggcm93cyApID8gcm93cyA6IFsgcm93cyBdO1xuXHRjb25zdCByb3cgPSB1c2VSZXNwb25zaXZlVmFsdWUoIHJvd3NBc0FycmF5ICk7XG5cblx0Y29uc3QgZ3JpZFRlbXBsYXRlQ29sdW1ucyA9XG5cdFx0dGVtcGxhdGVDb2x1bW5zIHx8ICggISEgY29sdW1ucyAmJiBgcmVwZWF0KCAkeyBjb2x1bW4gfSwgMWZyIClgICk7XG5cdGNvbnN0IGdyaWRUZW1wbGF0ZVJvd3MgPVxuXHRcdHRlbXBsYXRlUm93cyB8fCAoICEhIHJvd3MgJiYgYHJlcGVhdCggJHsgcm93IH0sIDFmciApYCApO1xuXG5cdGNvbnN0IGN4ID0gdXNlQ3goKTtcblxuXHRjb25zdCBjbGFzc2VzID0gdXNlTWVtbyggKCkgPT4ge1xuXHRcdGNvbnN0IGFsaWdubWVudFByb3BzID0gZ2V0QWxpZ25tZW50UHJvcHMoIGFsaWdubWVudCApO1xuXG5cdFx0Y29uc3QgZ3JpZENsYXNzZXMgPSBjc3MoIHtcblx0XHRcdGFsaWduSXRlbXM6IGFsaWduLFxuXHRcdFx0ZGlzcGxheTogaXNJbmxpbmUgPyAnaW5saW5lLWdyaWQnIDogJ2dyaWQnLFxuXHRcdFx0Z2FwOiBgY2FsYyggJHsgQ09ORklHLmdyaWRCYXNlIH0gKiAkeyBnYXAgfSApYCxcblx0XHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnM6IGdyaWRUZW1wbGF0ZUNvbHVtbnMgfHwgdW5kZWZpbmVkLFxuXHRcdFx0Z3JpZFRlbXBsYXRlUm93czogZ3JpZFRlbXBsYXRlUm93cyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkUm93R2FwOiByb3dHYXAsXG5cdFx0XHRncmlkQ29sdW1uR2FwOiBjb2x1bW5HYXAsXG5cdFx0XHRqdXN0aWZ5Q29udGVudDoganVzdGlmeSxcblx0XHRcdHZlcnRpY2FsQWxpZ246IGlzSW5saW5lID8gJ21pZGRsZScgOiB1bmRlZmluZWQsXG5cdFx0XHQuLi5hbGlnbm1lbnRQcm9wcyxcblx0XHR9ICk7XG5cblx0XHRyZXR1cm4gY3goIGdyaWRDbGFzc2VzLCBjbGFzc05hbWUgKTtcblx0fSwgW1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGN4LFxuXHRcdGdhcCxcblx0XHRncmlkVGVtcGxhdGVDb2x1bW5zLFxuXHRcdGdyaWRUZW1wbGF0ZVJvd3MsXG5cdFx0aXNJbmxpbmUsXG5cdFx0anVzdGlmeSxcblx0XHRyb3dHYXAsXG5cdF0gKTtcblxuXHRyZXR1cm4geyAuLi5vdGhlclByb3BzLCBjbGFzc05hbWU6IGNsYXNzZXMgfTtcbn1cbiJdfQ== */");
9040
9514
  return cx(gridClasses, className);
9041
9515
  }, [
9042
9516
  align,
@@ -9057,7 +9531,7 @@ function useGrid(props) {
9057
9531
  };
9058
9532
  }
9059
9533
  //#endregion
9060
- //#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
9534
+ //#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
9061
9535
  function UnconnectedGrid(props, forwardedRef) {
9062
9536
  const gridProps = useGrid(props);
9063
9537
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -9080,8 +9554,8 @@ const SUBPIXEL_TOLERANCE = .5;
9080
9554
  * Counts how many leading rows fit inside the content container.
9081
9555
  *
9082
9556
  * Rows are read from the DOM rather than derived from constants: row height
9083
- * depends on the theme's row gap, label wrapping, and whatever a caller renders
9084
- * as a label, so any restated number would drift.
9557
+ * depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
9558
+ * whatever a caller renders as a label, so any restated number would drift.
9085
9559
  *
9086
9560
  * Every row is a direct grid child marked with `data-row-index`; interactive rows
9087
9561
  * use a button wrapper and non-interactive rows use a div wrapper.
@@ -9166,14 +9640,13 @@ function useFittedRowCount(enabled, rowCount, data) {
9166
9640
  * @return Array of legend items for the leaderboard chart
9167
9641
  */
9168
9642
  function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withComparison = false, withOverlayLabel = false, legendLabels }) {
9169
- const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
9170
9643
  const { getElementStyles } = useGlobalChartsContext();
9171
9644
  return (0, react.useMemo)(() => {
9172
9645
  if (!data || data.length === 0) return [];
9173
9646
  const items = [];
9174
9647
  const { color: resolvedPrimaryColor } = getElementStyles({
9175
9648
  index: 0,
9176
- overrideColor: primaryColor || leaderboardChartSettings.primaryColor
9649
+ overrideColor: primaryColor
9177
9650
  });
9178
9651
  items.push({
9179
9652
  label: legendLabels?.primary || (0, _wordpress_i18n.__)("Current period", "jetpack-charts"),
@@ -9182,7 +9655,7 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
9182
9655
  if (withComparison && !withOverlayLabel) {
9183
9656
  const { color: resolvedSecondaryColor } = getElementStyles({
9184
9657
  index: 1,
9185
- overrideColor: secondaryColor || leaderboardChartSettings.secondaryColor
9658
+ overrideColor: secondaryColor
9186
9659
  });
9187
9660
  items.push({
9188
9661
  label: legendLabels?.comparison || (0, _wordpress_i18n.__)("Previous period", "jetpack-charts"),
@@ -9196,7 +9669,6 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
9196
9669
  secondaryColor,
9197
9670
  withComparison,
9198
9671
  legendLabels,
9199
- leaderboardChartSettings,
9200
9672
  getElementStyles,
9201
9673
  withOverlayLabel
9202
9674
  ]);
@@ -9210,6 +9682,9 @@ var leaderboard_chart_module_default = {
9210
9682
  "chevron": "a8ccharts-GovfoW-chevron",
9211
9683
  "deltaPlaceholder": "a8ccharts-GovfoW-deltaPlaceholder",
9212
9684
  "deltaValue": "a8ccharts-GovfoW-deltaValue",
9685
+ "deltaValue--down": "a8ccharts-GovfoW-deltaValue--down",
9686
+ "deltaValue--neutral": "a8ccharts-GovfoW-deltaValue--neutral",
9687
+ "deltaValue--up": "a8ccharts-GovfoW-deltaValue--up",
9213
9688
  "emptyState": "a8ccharts-GovfoW-emptyState",
9214
9689
  "fitEmptyState": "a8ccharts-GovfoW-fitEmptyState",
9215
9690
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
@@ -9228,6 +9703,14 @@ var leaderboard_chart_module_default = {
9228
9703
  };
9229
9704
  //#endregion
9230
9705
  //#region src/charts/leaderboard-chart/leaderboard-chart.tsx
9706
+ const ROW_GAP = "var(--a8c-charts-dimension-leaderboard-row-gap, 12px)";
9707
+ const COLUMN_GAP = "var(--a8c-charts-dimension-leaderboard-column-gap, 4px)";
9708
+ /** Indexed by `Math.sign( delta ) + 1`, so [negative, neutral, positive]. */
9709
+ const DELTA_TREND_CLASS = [
9710
+ "deltaValue--down",
9711
+ "deltaValue--neutral",
9712
+ "deltaValue--up"
9713
+ ];
9231
9714
  /**
9232
9715
  * Default value formatter using formatMetricValue
9233
9716
  *
@@ -9330,15 +9813,15 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9330
9813
  ...legend.shapeStyles
9331
9814
  };
9332
9815
  const { legendChildren, nonLegendChildren } = useChartChildren(children, "LeaderboardChart");
9333
- const { labelSpacing, rowGap, columnGap, primaryColor: settingsPrimaryColor, secondaryColor: settingsSecondaryColor, deltaColors } = leaderboardChartSettings;
9816
+ const { labelSpacing } = leaderboardChartSettings;
9334
9817
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
9335
9818
  const { color: resolvedPrimaryColor } = getElementStyles({
9336
9819
  index: 0,
9337
- overrideColor: primaryColor || settingsPrimaryColor
9820
+ overrideColor: primaryColor
9338
9821
  });
9339
9822
  const { color: resolvedSecondaryColor } = getElementStyles({
9340
9823
  index: 1,
9341
- overrideColor: secondaryColor || settingsSecondaryColor
9824
+ overrideColor: secondaryColor
9342
9825
  });
9343
9826
  const legendItems = useLeaderboardLegendItems({
9344
9827
  data: data || [],
@@ -9394,7 +9877,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9394
9877
  legendPosition,
9395
9878
  legendElement: false,
9396
9879
  legendChildren,
9397
- className: (0, clsx.default)(leaderboard_chart_module_default.leaderboardChart, {
9880
+ className: (0, clsx.default)("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
9398
9881
  [leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
9399
9882
  [leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
9400
9883
  }, className),
@@ -9429,7 +9912,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9429
9912
  legendPosition,
9430
9913
  legendElement,
9431
9914
  legendChildren,
9432
- className: (0, clsx.default)(leaderboard_chart_module_default.leaderboardChart, {
9915
+ className: (0, clsx.default)("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
9433
9916
  [leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
9434
9917
  [leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
9435
9918
  }, className),
@@ -9451,8 +9934,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9451
9934
  children: getAllHiddenMessage(legendInteractive, "series")
9452
9935
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default, {
9453
9936
  templateColumns: "minmax(0, 1fr) auto",
9454
- rowGap,
9455
- columnGap,
9937
+ rowGap: ROW_GAP,
9938
+ columnGap: COLUMN_GAP,
9456
9939
  "data-leaderboard-grid": true,
9457
9940
  children: data.map((entry, rowIndex) => {
9458
9941
  const rowStyle = shouldFitRows && rowIndex >= fittedCount ? { visibility: "hidden" } : void 0;
@@ -9461,8 +9944,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9461
9944
  const hasDelta = hasDeltaValue(entry);
9462
9945
  const showComparisonValue = showComparisonColumn && hasDelta;
9463
9946
  const showComparisonPlaceholder = showComparisonColumn && !hasDelta;
9464
- const colorIndex = showComparisonValue ? Math.sign(entry.delta) + 1 : 1;
9465
- const deltaColor = deltaColors[colorIndex];
9947
+ const deltaTrendClass = leaderboard_chart_module_default[DELTA_TREND_CLASS[showComparisonValue ? Math.sign(entry.delta) + 1 : 1]];
9466
9948
  const rowCells = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
9467
9949
  direction: "column",
9468
9950
  gap: labelSpacing,
@@ -9483,13 +9965,11 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9483
9965
  children: [
9484
9966
  isPrimaryVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, { children: valueFormatter(entry.currentValue) }),
9485
9967
  showComparisonValue && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
9486
- className: leaderboard_chart_module_default.deltaValue,
9487
- style: { color: deltaColor },
9968
+ className: (0, clsx.default)(leaderboard_chart_module_default.deltaValue, deltaTrendClass),
9488
9969
  children: deltaFormatter(entry.delta)
9489
9970
  }),
9490
9971
  showComparisonPlaceholder && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Text, {
9491
- className: (0, clsx.default)(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder),
9492
- style: { color: deltaColor },
9972
+ className: (0, clsx.default)(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder, deltaTrendClass),
9493
9973
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9494
9974
  "aria-hidden": "true",
9495
9975
  children: "—"
@@ -9573,7 +10053,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
9573
10053
  //#endregion
9574
10054
  //#region src/charts/pie-chart/pie-chart.module.scss
9575
10055
  var pie_chart_module_default = {
9576
- "pie-chart": "a8ccharts-gnszbG-pie-chart",
10056
+ "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
10057
+ "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
10058
+ "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
9577
10059
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
9578
10060
  };
9579
10061
  //#endregion
@@ -9628,11 +10110,8 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9628
10110
  const providerTheme = useGlobalChartsTheme();
9629
10111
  const chartId = useChartId(providedChartId);
9630
10112
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
9631
- const { containerRef, TooltipInPortal, containerBounds } = (0, _visx_tooltip.useTooltipInPortal)({
9632
- detectBounds: true,
9633
- scroll: true,
9634
- debounce: 0
9635
- });
10113
+ const standaloneScopeClass = useStandaloneScopeClass();
10114
+ const containerRef = (0, react.useRef)(null);
9636
10115
  const onMouseLeave = (0, react.useCallback)(() => {
9637
10116
  if (!withTooltips) return;
9638
10117
  hideTooltip();
@@ -9715,19 +10194,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9715
10194
  width: propWidth || void 0,
9716
10195
  height: propHeight || void 0
9717
10196
  },
9718
- trailingContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9719
- withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipInPortal, {
9720
- top: tooltipTop || 0,
9721
- left: tooltipLeft || 0,
9722
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9723
- className: "a8c-charts-scope",
9724
- role: "tooltip",
9725
- children: renderTooltip({ tooltipData })
9726
- })
9727
- }),
9728
- htmlChildren,
9729
- otherChildren
9730
- ] }),
10197
+ trailingContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [htmlChildren, otherChildren] }),
9731
10198
  children: ({ contentWidth, contentHeight }) => {
9732
10199
  const availableSize = Math.min(contentWidth > 0 ? contentWidth : 300, contentHeight > 0 ? contentHeight : 300);
9733
10200
  const actualSize = size ? Math.min(size, availableSize) : availableSize;
@@ -9740,9 +10207,10 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9740
10207
  const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
9741
10208
  const maxCornerRadius = (outerRadius - innerRadius) / 2;
9742
10209
  const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
9743
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
10210
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
9744
10211
  ref: containerRef,
9745
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10212
+ className: pie_chart_module_default["pie-chart__plot"],
10213
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
9746
10214
  viewBox: `0 0 ${width} ${height}`,
9747
10215
  preserveAspectRatio: "xMidYMid meet",
9748
10216
  width,
@@ -9774,11 +10242,12 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9774
10242
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
9775
10243
  const handleMouseMove = (event) => {
9776
10244
  if (!withTooltips) return;
9777
- if (containerBounds.width === 0 || containerBounds.height === 0) return;
10245
+ const bounds = containerRef.current?.getBoundingClientRect();
10246
+ if (!bounds) return;
9778
10247
  showTooltip({
9779
10248
  tooltipData: arc.data,
9780
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
9781
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY
10249
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
10250
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
9782
10251
  });
9783
10252
  };
9784
10253
  const pathProps = {
@@ -9801,20 +10270,20 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9801
10270
  const backgroundHeight = fontSize + 12;
9802
10271
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
9803
10272
  ...groupProps,
9804
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [providerTheme.labelBackgroundColor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
10273
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { ...pathProps }), showLabels && hasSpaceForLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
10274
+ className: pie_chart_module_default["pie-chart__label-plate"],
9805
10275
  x: centroidX - backgroundWidth / 2,
9806
10276
  y: centroidY - backgroundHeight / 2,
9807
10277
  width: backgroundWidth,
9808
10278
  height: backgroundHeight,
9809
- fill: providerTheme.labelBackgroundColor,
9810
10279
  rx: 4,
9811
10280
  ry: 4,
9812
10281
  pointerEvents: "none"
9813
10282
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
10283
+ className: pie_chart_module_default["pie-chart__label-text"],
9814
10284
  x: centroidX,
9815
10285
  y: centroidY,
9816
10286
  dy: ".33em",
9817
- fill: providerTheme.labelTextColor || "#333",
9818
10287
  fontSize,
9819
10288
  textAnchor: "middle",
9820
10289
  pointerEvents: "none",
@@ -9825,7 +10294,15 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9825
10294
  }
9826
10295
  }), !allSegmentsHidden && svgChildren]
9827
10296
  })]
9828
- })
10297
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
10298
+ top: tooltipTop || 0,
10299
+ left: tooltipLeft || 0,
10300
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10301
+ className: standaloneScopeClass,
10302
+ role: "tooltip",
10303
+ children: renderTooltip({ tooltipData })
10304
+ })
10305
+ })]
9829
10306
  });
9830
10307
  }
9831
10308
  })
@@ -9852,6 +10329,7 @@ var pie_semi_circle_chart_module_default = {
9852
10329
  "label": "a8ccharts-YtTOxW-label",
9853
10330
  "note": "a8ccharts-YtTOxW-note",
9854
10331
  "pie-semi-circle-chart": "a8ccharts-YtTOxW-pie-semi-circle-chart",
10332
+ "pie-semi-circle-chart__plot": "a8ccharts-YtTOxW-pie-semi-circle-chart__plot",
9855
10333
  "pie-semi-circle-chart--responsive": "a8ccharts-YtTOxW-pie-semi-circle-chart--responsive"
9856
10334
  };
9857
10335
  //#endregion
@@ -9901,23 +10379,17 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9901
10379
  const legendPosition = legend.position ?? "bottom";
9902
10380
  const chartId = useChartId(providedChartId);
9903
10381
  const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = (0, _visx_tooltip.useTooltip)();
9904
- const { containerRef, TooltipInPortal, containerBounds } = (0, _visx_tooltip.useTooltipInPortal)({
9905
- detectBounds: true,
9906
- scroll: true,
9907
- debounce: 0
9908
- });
10382
+ const standaloneScopeClass = useStandaloneScopeClass();
10383
+ const containerRef = (0, react.useRef)(null);
9909
10384
  const handleMouseMove = (0, react.useCallback)((event, arc) => {
9910
- if (containerBounds.width === 0 || containerBounds.height === 0) return;
10385
+ const bounds = containerRef.current?.getBoundingClientRect();
10386
+ if (!bounds) return;
9911
10387
  showTooltip({
9912
10388
  tooltipData: arc.data,
9913
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
9914
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY
10389
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
10390
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
9915
10391
  });
9916
10392
  }, [
9917
- containerBounds.width,
9918
- containerBounds.height,
9919
- containerBounds.left,
9920
- containerBounds.top,
9921
10393
  showTooltip,
9922
10394
  tooltipOffsetX,
9923
10395
  tooltipOffsetY
@@ -9966,7 +10438,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9966
10438
  const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
9967
10439
  const errorHeight = errorWidth / 2;
9968
10440
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9969
- className: pie_semi_circle_chart_module_default["pie-semi-circle-chart"],
10441
+ className: (0, clsx.default)("pie-semi-circle-chart", pie_semi_circle_chart_module_default["pie-semi-circle-chart"], className),
9970
10442
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
9971
10443
  width: errorWidth,
9972
10444
  height: errorHeight,
@@ -10013,19 +10485,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
10013
10485
  width: propWidth || void 0,
10014
10486
  height: propHeight || void 0
10015
10487
  },
10016
- trailingContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
10017
- withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipInPortal, {
10018
- top: tooltipTop || 0,
10019
- left: tooltipLeft || 0,
10020
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10021
- className: "a8c-charts-scope",
10022
- role: "tooltip",
10023
- children: renderTooltip({ tooltipData })
10024
- })
10025
- }),
10026
- htmlChildren,
10027
- otherChildren
10028
- ] }),
10488
+ trailingContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [htmlChildren, otherChildren] }),
10029
10489
  children: ({ contentWidth, contentHeight }) => {
10030
10490
  const availableWidth = contentWidth > 0 ? contentWidth : effectiveWidth;
10031
10491
  const availableHeight = contentHeight > 0 ? contentHeight : propHeight || effectiveWidth / 2;
@@ -10033,9 +10493,10 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
10033
10493
  const height = width / 2;
10034
10494
  const radius = height;
10035
10495
  const innerRadius = radius * (1 - thickness);
10036
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
10496
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
10037
10497
  ref: containerRef,
10038
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10498
+ className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
10499
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10039
10500
  width,
10040
10501
  height,
10041
10502
  viewBox: `0 0 ${width} ${height}`,
@@ -10093,7 +10554,15 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
10093
10554
  !allSegmentsHidden && svgChildren
10094
10555
  ] })
10095
10556
  })]
10096
- })
10557
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
10558
+ top: tooltipTop || 0,
10559
+ left: tooltipLeft || 0,
10560
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10561
+ className: standaloneScopeClass,
10562
+ role: "tooltip",
10563
+ children: renderTooltip({ tooltipData })
10564
+ })
10565
+ })]
10097
10566
  });
10098
10567
  }
10099
10568
  })
@@ -10351,6 +10820,7 @@ exports.buildCalendarHeatmapData = buildCalendarHeatmapData;
10351
10820
  exports.defaultTheme = defaultTheme;
10352
10821
  exports.formatMetricValue = formatMetricValue;
10353
10822
  exports.formatPercentage = formatPercentage;
10823
+ exports.getBucketInfo = getBucketInfo;
10354
10824
  exports.getColorDistance = getColorDistance;
10355
10825
  exports.hexToRgba = hexToRgba;
10356
10826
  exports.isValidHexColor = isValidHexColor;
@@ -10360,10 +10830,10 @@ exports.mixHexColors = mixHexColors;
10360
10830
  exports.normalizeColorToHex = normalizeColorToHex;
10361
10831
  exports.parseAsLocalDate = parseAsLocalDate;
10362
10832
  exports.parseHslString = parseHslString;
10363
- exports.parseRgbString = parseRgbString;
10364
10833
  exports.prefersLightText = prefersLightText;
10365
10834
  exports.relativeLuminance = relativeLuminance;
10366
10835
  exports.resolveCssVariable = resolveCssVariable;
10836
+ exports.useChartFormatting = useChartFormatting;
10367
10837
  exports.useChartLegendItems = useChartLegendItems;
10368
10838
  exports.useChartRegistration = useChartRegistration;
10369
10839
  exports.useChartScopeElement = useChartScopeElement;