@automattic/charts 3.2.0 → 4.1.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 (135) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/SECURITY.md +1 -0
  3. package/TOKENS.md +53 -58
  4. package/dist/index.cjs +1483 -664
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.css +93 -29
  7. package/dist/index.d.cts +233 -247
  8. package/dist/index.d.ts +233 -247
  9. package/dist/index.js +1484 -667
  10. package/dist/index.js.map +1 -1
  11. package/package.json +7 -6
  12. package/src/charts/area-chart/area-chart.tsx +39 -25
  13. package/src/charts/area-chart/test/area-chart.test.tsx +4 -33
  14. package/src/charts/area-chart/test/tick-placement.test.tsx +55 -0
  15. package/src/charts/area-chart/test/tooltip-resolution-branches.test.tsx +86 -0
  16. package/src/charts/area-chart/test/tooltip-resolution.test.tsx +69 -0
  17. package/src/charts/area-chart/types.ts +4 -7
  18. package/src/charts/bar-chart/bar-chart.tsx +2 -1
  19. package/src/charts/bar-chart/private/test/use-bar-chart-options-formatting.test.tsx +69 -0
  20. package/src/charts/bar-chart/private/use-bar-chart-options.ts +27 -15
  21. package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -0
  22. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +33 -23
  23. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +138 -182
  24. package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +46 -0
  25. package/src/charts/geo-chart/geo-chart.module.scss +1 -0
  26. package/src/charts/geo-chart/geo-chart.tsx +5 -10
  27. package/src/charts/geo-chart/test/geo-chart.test.tsx +2 -2
  28. package/src/charts/heatmap-chart/heatmap-chart.module.scss +52 -16
  29. package/src/charts/heatmap-chart/heatmap-chart.tsx +89 -37
  30. package/src/charts/heatmap-chart/index.ts +2 -1
  31. package/src/charts/heatmap-chart/private/build-calendar-data.ts +126 -89
  32. package/src/charts/heatmap-chart/private/civil-day.ts +174 -0
  33. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +11 -4
  34. package/src/charts/heatmap-chart/private/index.ts +1 -1
  35. package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +5 -1
  36. package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +3 -0
  37. package/src/charts/heatmap-chart/test/civil-day.test.ts +70 -0
  38. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +142 -12
  39. package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +118 -0
  40. package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +8 -0
  41. package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +248 -0
  42. package/src/charts/heatmap-chart/types.ts +8 -2
  43. package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +42 -0
  44. package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +2 -2
  45. package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +3 -5
  46. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +31 -19
  47. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +27 -17
  48. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +14 -0
  49. package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -9
  50. package/src/charts/leaderboard-chart/types.ts +2 -2
  51. package/src/charts/line-chart/line-chart.module.scss +10 -18
  52. package/src/charts/line-chart/line-chart.tsx +128 -26
  53. package/src/charts/line-chart/private/line-chart-annotation.tsx +18 -19
  54. package/src/charts/line-chart/test/crosshair-style-types.test.ts +31 -0
  55. package/src/charts/line-chart/test/default-tooltip-formatting.test.tsx +74 -0
  56. package/src/charts/line-chart/test/line-chart.test.tsx +174 -41
  57. package/src/charts/line-chart/test/tick-placement.test.tsx +84 -0
  58. package/src/charts/line-chart/test/tooltip-resolution.test.tsx +140 -0
  59. package/src/charts/line-chart/test/zoned-date-string.test.tsx +37 -0
  60. package/src/charts/line-chart/types.ts +17 -3
  61. package/src/charts/pie-chart/pie-chart.module.scss +13 -1
  62. package/src/charts/pie-chart/pie-chart.tsx +30 -40
  63. package/src/charts/pie-chart/test/pie-chart.test.tsx +59 -12
  64. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +8 -3
  65. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +23 -37
  66. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +50 -0
  67. package/src/charts/private/chart-layout/chart-layout.module.scss +3 -2
  68. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
  69. package/src/charts/private/test/axis-formatting.test.tsx +85 -0
  70. package/src/charts/private/test/time-axis-formatting.test.ts +109 -0
  71. package/src/charts/private/test/time-axis-options.test.ts +267 -0
  72. package/src/charts/private/test/time-axis-tick-invariants.test.ts +153 -0
  73. package/src/charts/private/test/time-axis-tick-values.test.ts +280 -0
  74. package/src/charts/private/test/time-axis.test.ts +9 -69
  75. package/src/charts/private/time-axis-options.ts +106 -0
  76. package/src/charts/private/time-axis.ts +318 -136
  77. package/src/charts/private/x-zoom/x-zoom.module.scss +3 -3
  78. package/src/charts/private/xy-plot/xy-plot.module.scss +8 -0
  79. package/src/charts/sparkline/test/sparkline.test.tsx +29 -56
  80. package/src/charts/sparkline/types.ts +1 -1
  81. package/src/components/legend/private/base-legend.module.scss +9 -5
  82. package/src/components/tooltip/accessible-tooltip.tsx +21 -17
  83. package/src/components/tooltip/base-tooltip.module.scss +3 -3
  84. package/src/components/tooltip/private/bounded-tooltip.tsx +227 -0
  85. package/src/components/tooltip/test/accessible-tooltip.test.tsx +30 -4
  86. package/src/components/tooltip/test/bounded-tooltip.test.tsx +225 -0
  87. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +379 -0
  88. package/src/components/tooltip/xy-chart-tooltip.tsx +326 -0
  89. package/src/components/trend-indicator/trend-indicator.module.scss +2 -2
  90. package/src/hooks/test/use-chart-data-transform-zone.test.tsx +36 -0
  91. package/src/hooks/test/use-chart-margin.test.tsx +173 -3
  92. package/src/hooks/test/use-xychart-theme.test.tsx +16 -3
  93. package/src/hooks/use-chart-data-transform.ts +10 -3
  94. package/src/hooks/use-chart-margin.tsx +54 -9
  95. package/src/hooks/use-xychart-theme.ts +25 -9
  96. package/src/index.ts +8 -0
  97. package/src/providers/chart-context/global-charts-provider.tsx +37 -38
  98. package/src/providers/chart-context/hooks/use-chart-formatting.ts +22 -0
  99. package/src/providers/chart-context/index.ts +1 -0
  100. package/src/providers/chart-context/private/catalog-pointers.ts +42 -0
  101. package/src/providers/chart-context/private/series-palette.ts +3 -13
  102. package/src/providers/chart-context/test/{sparse-theme.test.ts → catalog-pointers.test.ts} +22 -10
  103. package/src/providers/chart-context/test/chart-context.test.tsx +177 -792
  104. package/src/providers/chart-context/test/use-global-charts-theme.test.tsx +7 -25
  105. package/src/providers/chart-context/themes.ts +7 -52
  106. package/src/providers/chart-context/types.ts +3 -0
  107. package/src/styles/chart-scope.scss +39 -62
  108. package/src/styles/test/chart-scope.test.ts +19 -47
  109. package/src/test-utils/series-fixtures.ts +31 -0
  110. package/src/test-utils/zoned-parsing-suite.ts +90 -0
  111. package/src/types.ts +79 -166
  112. package/src/utils/bucket-info.ts +87 -0
  113. package/src/utils/color-utils.ts +0 -27
  114. package/src/utils/date-formatting.ts +157 -0
  115. package/src/utils/date-parsing.ts +103 -52
  116. package/src/utils/get-edge-tick-widths.ts +33 -0
  117. package/src/utils/index.ts +4 -0
  118. package/src/utils/test/bucket-info.test.ts +185 -0
  119. package/src/utils/test/color-utils.test.ts +1 -48
  120. package/src/utils/test/date-formatting.test.ts +89 -0
  121. package/src/utils/test/date-parsing.auckland.test.ts +6 -0
  122. package/src/utils/test/date-parsing.santiago.test.ts +6 -0
  123. package/src/utils/test/date-parsing.test.ts +16 -1
  124. package/src/utils/test/get-edge-tick-widths.test.ts +55 -0
  125. package/src/utils/test/get-styles.test.ts +10 -7
  126. package/src/utils/test/merge-themes.test.ts +18 -46
  127. package/src/utils/warn-once.ts +23 -0
  128. package/src/visx/types.ts +56 -5
  129. package/src/hooks/test/use-xychart-theme-provider-override.test.tsx +0 -28
  130. package/src/providers/chart-context/private/theme-override-vars.ts +0 -131
  131. package/src/providers/chart-context/private/with-catalog-pointers.ts +0 -93
  132. package/src/providers/chart-context/test/theme-override-vars-guard.test.tsx +0 -29
  133. package/src/providers/chart-context/test/theme-override-vars.test.ts +0 -183
  134. package/src/providers/chart-context/test/with-catalog-pointers.test.ts +0 -104
  135. package/src/styles/test/wpds-fallbacks.test.ts +0 -131
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ let react = require("react");
31
31
  react = __toESM(react);
32
32
  let _visx_vendor_d3_color = require("@visx/vendor/d3-color");
33
33
  let date_fns = require("date-fns");
34
+ let _date_fns_tz = require("@date-fns/tz");
34
35
  let _visx_text = require("@visx/text");
35
36
  let deepmerge = require("deepmerge");
36
37
  deepmerge = __toESM(deepmerge, 1);
@@ -40,6 +41,7 @@ let _visx_scale = require("@visx/scale");
40
41
  let _visx_group = require("@visx/group");
41
42
  let _visx_legend = require("@visx/legend");
42
43
  let _wordpress_ui = require("@wordpress/ui");
44
+ let react_dom = require("react-dom");
43
45
  let _visx_gradient = require("@visx/gradient");
44
46
  let _visx_curve = require("@visx/curve");
45
47
  let _visx_responsive = require("@visx/responsive");
@@ -85,46 +87,35 @@ function attachSubComponents(Chart, subComponents) {
85
87
  return Object.assign(Chart, subComponents);
86
88
  }
87
89
  //#endregion
88
- //#region src/utils/date-parsing.ts
90
+ //#region src/utils/warn-once.ts
91
+ const warned = /* @__PURE__ */ new Set();
89
92
  /**
90
- * @file Date parsing utilities using date-fns for local timezone handling
91
- *
92
- * This module provides utilities for parsing various date string formats and converting
93
- * them to local timezone dates using the battle-tested date-fns library. For formats
94
- * without timezone info, they're treated as local. For formats with timezone info,
95
- * they're converted to the equivalent local time.
93
+ * Warns about a host-supplied value once per process, and never in production.
96
94
  *
97
- * Note: And specifically it prevents format `YYYY-MM-DD` being parsed as UTC date.
98
- *
99
- * Key Features:
100
- * - All parsed dates are in local timezone
101
- * - Converts timezone-aware strings to local equivalent
102
- * - Robust input validation and error handling using date-fns
103
- * - TypeScript type safety
104
- * - Much smaller codebase than custom parsing
95
+ * @param key - Identifies the value, so a bad prop on every point warns once.
96
+ * @param message - What the host got wrong and what happens instead.
97
+ */
98
+ const warnOnce = (key, message) => {
99
+ if (warned.has(key) || process.env.NODE_ENV === "production") return;
100
+ warned.add(key);
101
+ console.warn(`[Charts] ${message}`);
102
+ };
103
+ //#endregion
104
+ //#region src/utils/date-parsing.ts
105
+ /**
106
+ * @file Date parsing: a naive string is dated in a supplied IANA zone, or the runtime's own
105
107
  *
106
- * Supported Formats:
107
- * - YYYY-MM-DD (treated as local)
108
- * - YYYY-MM-DD HH:mm:ss (treated as local)
109
- * - YYYY-MM-DD HH:mm (treated as local)
110
- * - YYYY-MM-DDTHH:mm:ss (treated as local)
111
- * - YYYY-MM-DDTHH:mm:ss.SSS (treated as local)
112
- * - YYYY-MM-DDTHH:mm (treated as local)
113
- * - YYYY-MM-DDTHH:mm:ssZ (converted to local)
114
- * - YYYY-MM-DDTHH:mm:ss±HH:mm (converted to local)
108
+ * A string carrying an offset is already an instant and parses the same everywhere. A string
109
+ * without one is only a wall-clock reading, so it means nothing until a zone is named. See
110
+ * `parseAsLocalDate` for the supported formats.
115
111
  *
116
- * @example
117
- * ```typescript
118
- * parseAsLocalDate("2025-01-01"); // Local timezone
119
- * parseAsLocalDate("2025-01-01 14:30:00"); // Local timezone
120
- * parseAsLocalDate("2025-01-01 14:30"); // Local timezone
121
- * parseAsLocalDate("2025-01-01T14:30:45.123"); // Local timezone
122
- * parseAsLocalDate("2025-01-01T14:30:00Z"); // UTC 14:30 → Local equivalent
123
- * parseAsLocalDate("2025-01-01T14:30:00+05:00"); // +05:00 14:30 → Local equivalent
124
- * ```
112
+ * Note: this specifically avoids date-fns's default of parsing `YYYY-MM-DD` as a UTC date.
125
113
  */
126
114
  /**
127
115
  * Checks if a date string contains timezone information
116
+ *
117
+ * The minutes are optional because ISO 8601 allows a bare `±hh`, which `parseISO` reads.
118
+ *
128
119
  * @param {string} dateString - The date string to check for timezone information
129
120
  * @return {boolean} True if the date string contains timezone information, false otherwise
130
121
  */
@@ -132,28 +123,57 @@ const hasTimezone = (dateString) => {
132
123
  const tIndex = dateString.indexOf("T");
133
124
  if (tIndex === -1) return false;
134
125
  if (dateString.endsWith("Z")) return true;
135
- return /[+-]\d{2}:?\d{2}$/.test(dateString.slice(tIndex + 1));
126
+ return /[+-]\d{2}(?::?\d{2})?$/.test(dateString.slice(tIndex + 1));
127
+ };
128
+ const NAIVE = /^(\d{1,4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,3}))?)?)?$/;
129
+ const asUtcMs = ([, ...fields]) => {
130
+ const [year, month, day, hour, minute, second, ms] = fields.map((field) => Number(field ?? 0));
131
+ const date = /* @__PURE__ */ new Date(0);
132
+ date.setUTCFullYear(year, month - 1, day);
133
+ date.setUTCHours(hour, minute, second, ms);
134
+ return date.getTime();
136
135
  };
137
136
  /**
138
- * Parses any supported date string format and returns a local timezone date
137
+ * The instant a wall-clock reading names in a given zone.
139
138
  *
140
- * Uses date-fns for robust parsing and validation. For strings without timezone
141
- * info, treats as local timezone. For strings with timezone info, converts to
142
- * local timezone equivalent.
139
+ * A reading a spring-forward gap deleted moves forward past the gap, and one a fall-back
140
+ * repeated resolves to a single instant. Neither is configurable: see CHARTS-268.
143
141
  *
144
- * Supports:
145
- * - YYYY-MM-DD (local)
146
- * - YYYY-MM-DD HH:mm:ss (local)
147
- * - YYYY-MM-DD HH:mm (local)
148
- * - YYYY-MM-DDTHH:mm:ss (local)
149
- * - YYYY-MM-DDTHH:mm:ss.SSS (local)
150
- * - YYYY-MM-DDTHH:mm (local)
151
- * - YYYY-MM-DDTHH:mm:ssZ (UTC → local)
152
- * - YYYY-MM-DDTHH:mm:ss±HH:mm (offset → local)
153
- * @param {string} dateString - The date string to parse into a local timezone date
154
- * @return {Date} A Date object representing the parsed date in local timezone, or an invalid Date if parsing fails
142
+ * @param wallClock - The reading, as the milliseconds it would be if it were UTC.
143
+ * @param timeZone - IANA zone the reading is dated in.
144
+ * @return The instant, or `null` where `timeZone` is not a zone Intl accepts.
155
145
  */
156
- const parseAsLocalDate = (dateString) => {
146
+ const wallClockToInstant = (wallClock, timeZone) => {
147
+ const first = (0, _date_fns_tz.tzOffset)(timeZone, new Date(wallClock));
148
+ if (Number.isNaN(first)) {
149
+ warnOnce(`parse:timeZone:${timeZone}`, `timeZone ${JSON.stringify(timeZone)} is not a zone Intl accepts, so dates are read in the browser's zone. Pass an IANA name or a UTC offset such as "+05:30".`);
150
+ return null;
151
+ }
152
+ const guess = wallClock - first * 6e4;
153
+ const second = (0, _date_fns_tz.tzOffset)(timeZone, new Date(guess));
154
+ if (first === second) return new Date(guess);
155
+ const corrected = wallClock - second * 6e4;
156
+ const third = (0, _date_fns_tz.tzOffset)(timeZone, new Date(corrected));
157
+ if (second === third) return new Date(corrected);
158
+ return /* @__PURE__ */ new Date(wallClock - Math.min(second, third) * 6e4);
159
+ };
160
+ /**
161
+ * Parses any supported date string format into an instant, dated in `timeZone` or the runtime's own
162
+ *
163
+ * Uses date-fns for robust parsing and validation. A string carrying timezone info
164
+ * is already an instant and is returned as one, whatever `timeZone` says. A string
165
+ * without it is a wall-clock reading, dated in `timeZone` when one is supplied and
166
+ * in the runtime's own zone when none is.
167
+ *
168
+ * A wall clock a DST gap deleted is moved forward past the gap; one a fall-back repeated
169
+ * resolves to a single instant. Neither policy is selectable. A zone `Intl` rejects falls
170
+ * back to the runtime's own, and warns once outside production.
171
+ *
172
+ * @param {string} dateString - The date string to parse into a date
173
+ * @param {string} [timeZone] - IANA zone a naive string is read in; the runtime's own when absent, and ignored by a string that carries its own offset
174
+ * @return {Date} A Date object representing the parsed instant, or an invalid Date if parsing fails
175
+ */
176
+ const parseAsLocalDate = (dateString, timeZone) => {
157
177
  const trimmedString = dateString.trim();
158
178
  if (hasTimezone(trimmedString)) {
159
179
  const isoDate = (0, date_fns.parseISO)(trimmedString);
@@ -169,11 +189,47 @@ const parseAsLocalDate = (dateString) => {
169
189
  "yyyy-MM-dd'T'HH:mm"
170
190
  ]) {
171
191
  const result = (0, date_fns.parse)(trimmedString, format, /* @__PURE__ */ new Date());
172
- if ((0, date_fns.isValid)(result)) return result;
192
+ if (!(0, date_fns.isValid)(result)) continue;
193
+ if (!timeZone) return result;
194
+ const fields = NAIVE.exec(trimmedString);
195
+ return (fields && wallClockToInstant(asUtcMs(fields), timeZone)) ?? result;
173
196
  }
174
197
  return /* @__PURE__ */ new Date(NaN);
175
198
  };
176
199
  //#endregion
200
+ //#region src/utils/bucket-info.ts
201
+ const getInstant = (point) => {
202
+ if (!point) return;
203
+ if ("date" in point && point.date) return point.date.getTime();
204
+ if ("dateString" in point && point.dateString) return parseAsLocalDate(point.dateString).getTime();
205
+ };
206
+ const getPointSpacingInHours = (data) => {
207
+ return data.reduce((spacing, datom) => {
208
+ const instants = [...new Set(datom.data.map((point) => getInstant(point)))].filter((time) => time !== void 0 && Number.isFinite(time)).sort((a, b) => a - b);
209
+ return instants.reduce((seriesSpacing, time, index) => index === 0 ? seriesSpacing : Math.min(seriesSpacing, Math.abs((0, date_fns.differenceInHours)(time, instants[index - 1]))), spacing);
210
+ }, Number.POSITIVE_INFINITY);
211
+ };
212
+ const SUB_DAILY_SPACING_HOURS = 23;
213
+ const MONTHLY_SPACING_HOURS = 672;
214
+ const getBucketResolution = (data, tickResolution) => {
215
+ if (tickResolution) return tickResolution === "week" ? "day" : tickResolution;
216
+ const spacingInHours = getPointSpacingInHours(data);
217
+ if (spacingInHours < SUB_DAILY_SPACING_HOURS) return "hour";
218
+ if (!Number.isFinite(spacingInHours) || spacingInHours < MONTHLY_SPACING_HOURS) return "day";
219
+ return spacingInHours < 12 * MONTHLY_SPACING_HOURS ? "month" : "year";
220
+ };
221
+ /**
222
+ * How this data was classified, for consumers that render their own labels.
223
+ *
224
+ * @param data - The series the chart draws, in any order, dated by `date` or `dateString`.
225
+ * @param tickResolution - Caller-declared bucket resolution, when known.
226
+ * @return The declared or inferred bucket, and the resolution formats key on.
227
+ */
228
+ const getBucketInfo = (data, tickResolution) => ({
229
+ bucket: tickResolution ?? getBucketResolution(data),
230
+ displayResolution: getBucketResolution(data, tickResolution)
231
+ });
232
+ //#endregion
177
233
  //#region src/utils/format-metric-value.ts
178
234
  /**
179
235
  * Format a numeric metric value based on type, precision and scale.
@@ -241,6 +297,31 @@ const formatPercentage = (value) => {
241
297
  } });
242
298
  };
243
299
  //#endregion
300
+ //#region src/utils/get-edge-tick-widths.ts
301
+ /**
302
+ * Rendered widths of the first and last tick labels on an axis.
303
+ *
304
+ * An unmeasurable label reserves nothing, which is what a width of 0 already
305
+ * means to every caller, so it is reported as 0 rather than as its own case.
306
+ *
307
+ * @param ticks - Tick values, in axis order.
308
+ * @param formatTick - Function to format a tick.
309
+ * @param {object} labelStyle - Style object for the label.
310
+ * @return {object} - Widths in pixels.
311
+ */
312
+ const getEdgeTickWidths = (ticks, formatTick, labelStyle) => {
313
+ if (!ticks.length) return {
314
+ first: 0,
315
+ last: 0
316
+ };
317
+ const lastIndex = ticks.length - 1;
318
+ const label = (tick, index) => String(formatTick ? formatTick(tick, index, []) ?? "" : tick);
319
+ return {
320
+ first: (0, _visx_text.getStringWidth)(label(ticks[0], 0), labelStyle) ?? 0,
321
+ last: (0, _visx_text.getStringWidth)(label(ticks[lastIndex], lastIndex), labelStyle) ?? 0
322
+ };
323
+ };
324
+ //#endregion
244
325
  //#region src/utils/get-longest-tick-width.ts
245
326
  /**
246
327
  * Returns the width of the longest tick.
@@ -386,20 +467,6 @@ const parseHslString = (hslString) => {
386
467
  ];
387
468
  };
388
469
  /**
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
470
  * Normalize any CSS color value to a hex color string.
404
471
  * Handles hex, HSL, HSLA, RGB, RGBA, named CSS colors, and CSS variables.
405
472
  *
@@ -627,6 +694,106 @@ const resolveFontSize = (val) => {
627
694
  }
628
695
  };
629
696
  //#endregion
697
+ //#region src/utils/date-formatting.ts
698
+ const formatters = /* @__PURE__ */ new Map();
699
+ const getFormatter$1 = (options, { locale, timeZone }) => {
700
+ const key = `${locale ?? ""}|${timeZone ?? ""}|${JSON.stringify(options)}`;
701
+ let formatter = formatters.get(key);
702
+ if (!formatter) {
703
+ formatter = new Intl.DateTimeFormat(locale, {
704
+ ...options,
705
+ timeZone
706
+ });
707
+ formatters.set(key, formatter);
708
+ }
709
+ return formatter;
710
+ };
711
+ const isUsable = (formatting) => {
712
+ try {
713
+ Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
714
+ return true;
715
+ } catch {
716
+ return false;
717
+ }
718
+ };
719
+ const toBcp47 = (locale) => locale?.replace(/_/g, "-");
720
+ /**
721
+ * Repairs a host locale for `Intl`, dropping either half it still cannot use.
722
+ *
723
+ * Every formatter below is built during render, so an unusable string would throw
724
+ * where React unmounts the whole host tree rather than one chart. WordPress hands
725
+ * out the likeliest one either way: `timezone_string` is `''` on a site set to a
726
+ * raw UTC offset, and has no repair.
727
+ *
728
+ * @param formatting - Locale and time zone as the host supplied them.
729
+ * @return The usable halves, each `undefined` where the host's value was not.
730
+ */
731
+ const sanitizeFormatting = (formatting) => {
732
+ const repaired = {
733
+ ...formatting,
734
+ locale: toBcp47(formatting.locale)
735
+ };
736
+ if (isUsable(repaired)) return repaired;
737
+ const locale = isUsable({ locale: repaired.locale }) ? repaired.locale : void 0;
738
+ const timeZone = isUsable({ timeZone: repaired.timeZone }) ? repaired.timeZone : void 0;
739
+ 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.`);
740
+ 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".`);
741
+ return {
742
+ locale,
743
+ timeZone
744
+ };
745
+ };
746
+ /**
747
+ * A date formatter bound to the host's locale and time zone.
748
+ *
749
+ * @param options - Date-part options, as for `Intl.DateTimeFormat`.
750
+ * @param formatting - Host locale and time zone; either half absent leaves the runtime's own.
751
+ * @return Formatter taking a timestamp or a `Date`.
752
+ */
753
+ const createDateFormatter = (options, formatting) => {
754
+ return (value) => {
755
+ if (!Number.isFinite(Number(value))) return new Date(Number(value)).toLocaleDateString();
756
+ return getFormatter$1(options, formatting).format(value);
757
+ };
758
+ };
759
+ const CLOCK_OPTIONS = {
760
+ month: "numeric",
761
+ hour: "numeric",
762
+ hourCycle: "h23"
763
+ };
764
+ const NO_FIELDS = {
765
+ month: NaN,
766
+ hour: NaN
767
+ };
768
+ /**
769
+ * A clock reading calendar fields in the host's time zone.
770
+ *
771
+ * `Date`'s own getters read the browser's zone, so a chart viewed from another
772
+ * country anchors its ticks to the wrong midnights and the wrong January
773
+ * without this.
774
+ *
775
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
776
+ * @return Reader taking a `Date`.
777
+ */
778
+ const createZonedClock = (timeZone) => {
779
+ if (!timeZone) return (date) => ({
780
+ month: date.getMonth() + 1,
781
+ hour: date.getHours()
782
+ });
783
+ return (date) => {
784
+ if (!Number.isFinite(Number(date))) return NO_FIELDS;
785
+ const parts = getFormatter$1(CLOCK_OPTIONS, {
786
+ locale: "en-US",
787
+ timeZone
788
+ }).formatToParts(date);
789
+ const read = (type) => Number(parts.find((part) => part.type === type)?.value);
790
+ return {
791
+ month: read("month"),
792
+ hour: read("hour")
793
+ };
794
+ };
795
+ };
796
+ //#endregion
630
797
  //#region src/providers/chart-scope/chart-scope-context.ts
631
798
  /**
632
799
  * The nearest chart element, published so JS token resolution reads the same CSS cascade that paints the chart's own colours.
@@ -774,150 +941,29 @@ const SERIES_SLOT_1_FALLBACK = "#3858e9";
774
941
  /** The catalog pointer for every slot, in slot order. */
775
942
  const SERIES_PALETTE_POINTERS = Array.from({ length: 5 }, (_, index) => index === 0 ? `var(${seriesRole(1)}, ${SERIES_SLOT_1_FALLBACK})` : `var(${seriesRole(index + 1)})`);
776
943
  //#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
944
  //#region src/providers/chart-context/themes.ts
858
945
  /**
859
- * Default theme configuration
946
+ * Default theme configuration: the shape and spacing a consumer can override.
947
+ *
948
+ * For a color, set the matching `--a8c-charts-color-*` role in CSS instead; the `var()`
949
+ * chains that deliver those live in `private/catalog-pointers.ts`.
860
950
  */
861
951
  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
- },
952
+ gridStyles: { strokeWidth: 1 },
870
953
  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
- },
954
+ xTickLineStyles: { strokeWidth: 1 },
955
+ xAxisLineStyles: { strokeWidth: 1 },
881
956
  legend: {
882
- labelStyles: { color: "var(--a8c-charts-color-label, #1e1e1e)" },
957
+ labelStyles: {},
883
958
  containerStyles: {},
884
959
  shapeStyles: []
885
960
  },
886
961
  seriesLineStyles: [],
887
962
  glyphs: [],
888
- svgLabelSmall: {
889
- fill: "var(--a8c-charts-color-label-axis, #1e1e1e)",
890
- fontFamily: "inherit"
891
- },
963
+ svgLabelSmall: { fontFamily: "inherit" },
892
964
  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
- },
965
+ annotationStyles: {},
966
+ leaderboardChart: { labelSpacing: "xs" },
921
967
  lineChart: { lineStyles: { comparison: {
922
968
  strokeDasharray: "4 4",
923
969
  strokeLinecap: "square"
@@ -941,70 +987,9 @@ const defaultTheme = {
941
987
  }
942
988
  };
943
989
  //#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
990
  //#region src/providers/chart-context/global-charts-provider.tsx
1006
991
  const GlobalChartsContext = (0, react.createContext)(null);
1007
- const GlobalChartsProvider = ({ children, theme }) => {
992
+ const GlobalChartsProvider = ({ children, theme, locale, timeZone }) => {
1008
993
  const [charts, setCharts] = (0, react.useState)(() => /* @__PURE__ */ new Map());
1009
994
  const [hiddenSeries, setHiddenSeries] = (0, react.useState)(() => /* @__PURE__ */ new Map());
1010
995
  const seededCharts = (0, react.useRef)(/* @__PURE__ */ new Set());
@@ -1014,11 +999,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1014
999
  wrapperRef.current = node;
1015
1000
  setScopeNode(node);
1016
1001
  }, []);
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]);
1002
+ const providerTheme = (0, react.useMemo)(() => theme ? mergeThemes(defaultTheme, theme) : defaultTheme, [theme]);
1022
1003
  const [colorCache, setColorCache] = (0, react.useState)(() => ({
1023
1004
  colors: [],
1024
1005
  hues: [],
@@ -1034,7 +1015,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1034
1015
  const existingHslColors = [];
1035
1016
  let minHue = 360;
1036
1017
  let maxHue = 0;
1037
- for (const color of providerTheme.colors ?? SERIES_PALETTE_POINTERS) {
1018
+ for (const color of SERIES_PALETTE_POINTERS) {
1038
1019
  const normalizedColor = normalizeColorToHex(color, wrapperRef.current, resolveCssVariable);
1039
1020
  if (normalizedColor.startsWith("#")) {
1040
1021
  resolvedColors.push(normalizedColor);
@@ -1059,7 +1040,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1059
1040
  minHue,
1060
1041
  maxHue
1061
1042
  });
1062
- }, [providerTheme]);
1043
+ }, []);
1063
1044
  (0, react.useEffect)(() => {
1064
1045
  if (colorCache.colors.length > 0) setIsColorPaletteResolved(true);
1065
1046
  }, [colorCache]);
@@ -1151,12 +1132,17 @@ const GlobalChartsProvider = ({ children, theme }) => {
1151
1132
  const set = hiddenSeries.get(chartId);
1152
1133
  return set ? new Set(set) : /* @__PURE__ */ new Set();
1153
1134
  }, [hiddenSeries]);
1135
+ const formatting = (0, react.useMemo)(() => sanitizeFormatting({
1136
+ locale,
1137
+ timeZone
1138
+ }), [locale, timeZone]);
1154
1139
  const value = (0, react.useMemo)(() => ({
1155
1140
  charts,
1156
1141
  registerChart,
1157
1142
  unregisterChart,
1158
1143
  getChartData,
1159
1144
  theme: providerTheme,
1145
+ formatting,
1160
1146
  getElementStyles,
1161
1147
  toggleSeriesVisibility,
1162
1148
  setSeriesVisibility,
@@ -1172,6 +1158,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1172
1158
  unregisterChart,
1173
1159
  getChartData,
1174
1160
  providerTheme,
1161
+ formatting,
1175
1162
  getElementStyles,
1176
1163
  toggleSeriesVisibility,
1177
1164
  setSeriesVisibility,
@@ -1187,10 +1174,7 @@ const GlobalChartsProvider = ({ children, theme }) => {
1187
1174
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1188
1175
  ref: setWrapperNode,
1189
1176
  className: CHART_SCOPE_CLASS,
1190
- style: {
1191
- display: "contents",
1192
- ...overrideVars
1193
- },
1177
+ style: { display: "contents" },
1194
1178
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartScopeContext.Provider, {
1195
1179
  value: scopeNode,
1196
1180
  children
@@ -1254,6 +1238,39 @@ const useDeepMemo = (value) => {
1254
1238
  return ref.current;
1255
1239
  };
1256
1240
  //#endregion
1241
+ //#region src/providers/chart-context/private/catalog-pointers.ts
1242
+ const CATALOG_POINTERS = {
1243
+ background: "var(--a8c-charts-color-background, #fff)",
1244
+ labelAxis: "var(--a8c-charts-color-label-axis, #1e1e1e)",
1245
+ grid: "var(--a8c-charts-color-grid, #dbdbdb)",
1246
+ axisX: "var(--a8c-charts-color-axis-x, #dbdbdb)",
1247
+ tickX: "var(--a8c-charts-color-tick-x, #dbdbdb)",
1248
+ axisY: "var(--a8c-charts-color-axis-y, none)",
1249
+ tickY: "var(--a8c-charts-color-tick-y, none)",
1250
+ annotation: "var(--a8c-charts-color-annotation, #1e1e1e)",
1251
+ surface: "var(--a8c-charts-color-surface, #fff)",
1252
+ surfaceSecondary: "var(--a8c-charts-color-surface-secondary, #f4f4f4)",
1253
+ trendUp: "var(--a8c-charts-color-trend-up, #008030)",
1254
+ trendDown: "var(--a8c-charts-color-trend-down, #cc1818)",
1255
+ series: SERIES_PALETTE_POINTERS
1256
+ };
1257
+ /**
1258
+ * The annotation parts visx paints, in the shape `@visx/annotation` takes. `radius`
1259
+ * rides along as the base the theme and the per-datum styles merge onto.
1260
+ */
1261
+ const ANNOTATION_POINTERS = {
1262
+ label: {
1263
+ anchorLineStroke: CATALOG_POINTERS.annotation,
1264
+ backgroundFill: CATALOG_POINTERS.surface
1265
+ },
1266
+ connector: { stroke: CATALOG_POINTERS.annotation },
1267
+ circleSubject: {
1268
+ stroke: "transparent",
1269
+ fill: CATALOG_POINTERS.annotation,
1270
+ radius: 5
1271
+ }
1272
+ };
1273
+ //#endregion
1257
1274
  //#region src/providers/chart-scope/use-chart-scope-element.ts
1258
1275
  /**
1259
1276
  * Returns the nearest chart scope element, or `null` when rendered outside any scope (and during SSR, before the ref is attached).
@@ -1280,13 +1297,34 @@ const useXYChartTheme = (data) => {
1280
1297
  return (0, react.useMemo)(() => {
1281
1298
  const resolve = createCssVariableResolver(scopeElement);
1282
1299
  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);
1300
+ const paletteColors = [...JSON.parse(seriesColorKey), ...CATALOG_POINTERS.series].map((color) => resolveColor(color)).filter((color) => Boolean(color) && !color.includes("var("));
1301
+ const resolvedLabelColor = resolveColor(CATALOG_POINTERS.labelAxis);
1302
+ const htmlLabelColor = resolvedLabelColor ? normalizeColorToHex(resolvedLabelColor) || resolvedLabelColor : resolvedLabelColor;
1285
1303
  return (0, _visx_xychart.buildChartTheme)({
1286
1304
  ...theme,
1305
+ gridColor: "",
1306
+ gridColorDark: "",
1287
1307
  colors: paletteColors,
1288
- backgroundColor: resolveColor(theme.backgroundColor),
1289
- htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0
1308
+ backgroundColor: resolveColor(CATALOG_POINTERS.background),
1309
+ htmlLabel: htmlLabelColor ? { color: htmlLabelColor } : void 0,
1310
+ gridStyles: {
1311
+ ...theme.gridStyles,
1312
+ stroke: CATALOG_POINTERS.grid
1313
+ },
1314
+ xAxisLineStyles: {
1315
+ ...theme.xAxisLineStyles,
1316
+ stroke: CATALOG_POINTERS.axisX
1317
+ },
1318
+ xTickLineStyles: {
1319
+ ...theme.xTickLineStyles,
1320
+ stroke: CATALOG_POINTERS.tickX
1321
+ },
1322
+ yAxisLineStyles: { stroke: CATALOG_POINTERS.axisY },
1323
+ yTickLineStyles: { stroke: CATALOG_POINTERS.tickY },
1324
+ svgLabelSmall: {
1325
+ ...theme.svgLabelSmall,
1326
+ fill: CATALOG_POINTERS.labelAxis
1327
+ }
1290
1328
  });
1291
1329
  }, [
1292
1330
  theme,
@@ -1295,13 +1333,28 @@ const useXYChartTheme = (data) => {
1295
1333
  ]);
1296
1334
  };
1297
1335
  //#endregion
1336
+ //#region src/providers/chart-context/hooks/use-chart-formatting.ts
1337
+ const RUNTIME_FORMATTING = {};
1338
+ /**
1339
+ * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
1340
+ *
1341
+ * Falls back to the runtime's own rather than throwing: charts render outside a
1342
+ * provider, and a host that sets neither gets exactly the browser-default
1343
+ * behavior this package had before the context existed.
1344
+ *
1345
+ * @return The host's formatting context.
1346
+ */
1347
+ const useChartFormatting = () => {
1348
+ return (0, react.useContext)(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
1349
+ };
1350
+ //#endregion
1298
1351
  //#region src/hooks/use-chart-data-transform.ts
1299
1352
  /**
1300
1353
  * Hook that transforms and sorts chart data, handling date parsing and sorting
1301
1354
  *
1302
1355
  * This hook extracts the common data transformation logic used in both line-chart
1303
1356
  * and bar-chart components. It:
1304
- * 1. Parses date strings into Date objects using parseAsLocalDate
1357
+ * 1. Parses date strings into Date objects, dated in the provider's time zone
1305
1358
  * 2. Sorts data points by date when date properties are present
1306
1359
  * 3. Returns the original data unchanged when no date properties are found
1307
1360
  *
@@ -1309,6 +1362,7 @@ const useXYChartTheme = (data) => {
1309
1362
  * @return {SeriesData[]} The transformed and sorted data
1310
1363
  */
1311
1364
  const useChartDataTransform = (data) => {
1365
+ const { timeZone } = useChartFormatting();
1312
1366
  return (0, react.useMemo)(() => {
1313
1367
  const firstPoint = data?.[0]?.data?.[0];
1314
1368
  if (!(firstPoint && ("date" in firstPoint || "dateString" in firstPoint))) return data;
@@ -1317,7 +1371,7 @@ const useChartDataTransform = (data) => {
1317
1371
  data: series.data.map((point) => {
1318
1372
  let date;
1319
1373
  if ("date" in point && point.date) date = point.date;
1320
- else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString);
1374
+ else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString, timeZone);
1321
1375
  return {
1322
1376
  ...point,
1323
1377
  date
@@ -1327,7 +1381,7 @@ const useChartDataTransform = (data) => {
1327
1381
  return a.date.getTime() - b.date.getTime();
1328
1382
  })
1329
1383
  }));
1330
- }, [data]);
1384
+ }, [data, timeZone]);
1331
1385
  };
1332
1386
  //#endregion
1333
1387
  //#region src/hooks/use-chart-margin.tsx
@@ -1369,11 +1423,32 @@ const DEFAULT_TICK_LENGTH = 8;
1369
1423
  * measure them via getLongestTickWidth.
1370
1424
  */
1371
1425
  const DEFAULT_Y_TICK_WIDTH = 40;
1426
+ /**
1427
+ * Copy a label style with its lengths spelled out in px.
1428
+ *
1429
+ * `getStringWidth` applies the style through CSSOM. Blink resolves a bare number
1430
+ * on an SVG `<text>`, but that is its own leniency rather than the CSS rule, and
1431
+ * `buildChartTheme` hands us bare numbers.
1432
+ *
1433
+ * @param style - Raw label style from the theme.
1434
+ * @return The same style with px-qualified lengths.
1435
+ */
1436
+ const toMeasurableStyle = (style) => {
1437
+ if (!style) return style;
1438
+ const fontSize = resolveFontSize(style.fontSize);
1439
+ const { letterSpacing } = style;
1440
+ return {
1441
+ ...style,
1442
+ ...fontSize === void 0 ? {} : { fontSize: `${fontSize}px` },
1443
+ ...typeof letterSpacing === "number" ? { letterSpacing: `${letterSpacing}px` } : {}
1444
+ };
1445
+ };
1372
1446
  const getXAxisLabelMetrics = (theme, orientation) => {
1373
1447
  const xAxisStyles = orientation === "top" ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
1374
1448
  return {
1375
1449
  fontSize: resolveFontSize(xAxisStyles?.axisLabel?.fontSize) || resolveFontSize(theme.svgLabelSmall?.fontSize) || DEFAULT_FONT_SIZE,
1376
- tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH
1450
+ tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH,
1451
+ tickLabelStyle: toMeasurableStyle(xAxisStyles?.tickLabel)
1377
1452
  };
1378
1453
  };
1379
1454
  const useChartMargin = (height, options, data, theme, horizontal = false) => {
@@ -1385,7 +1460,7 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1385
1460
  const maxY = Math.max(...allDataPoints.map((d) => d.value));
1386
1461
  const yScale = (0, _visx_scale.createScale)({
1387
1462
  ...options.yScale,
1388
- domain: [minY, maxY],
1463
+ domain: options.yScale?.domain ?? [minY, maxY],
1389
1464
  range: [height, 0]
1390
1465
  });
1391
1466
  return (0, _visx_scale.getTicks)(yScale, options.axis?.y?.numTicks);
@@ -1404,18 +1479,25 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1404
1479
  };
1405
1480
  const yAxisOrientation = options.axis?.y?.orientation;
1406
1481
  const yAxisStyles = yAxisOrientation === "right" ? theme.axisStyles.y.right : theme.axisStyles.y.left;
1407
- const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, yAxisStyles.axisLabel);
1482
+ const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, toMeasurableStyle(yAxisStyles.axisLabel));
1408
1483
  const yTickLabelFontSize = resolveFontSize(yAxisStyles?.tickLabel?.fontSize) || DEFAULT_FONT_SIZE;
1409
1484
  const yMarginValue = (yTickWidth ?? DEFAULT_Y_TICK_WIDTH) + (yAxisStyles?.tickLength ?? 0) + Math.ceil(yTickLabelFontSize * .25);
1410
- if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1411
- else defaultMargin.left = yMarginValue;
1485
+ if (options.axis?.y?.display !== false) {
1486
+ if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1487
+ else defaultMargin.left = yMarginValue;
1488
+ }
1412
1489
  const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1413
- const { fontSize, tickLength } = getXAxisLabelMetrics(theme, xOrientation);
1490
+ const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1414
1491
  const computedXMargin = fontSize + tickLength;
1415
1492
  if (xOrientation === "top") {
1416
1493
  defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1417
1494
  defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
1418
1495
  } else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
1496
+ if (options.axis?.x?.display !== false) {
1497
+ const { first, last } = getEdgeTickWidths(options.axis?.x?.tickValues ?? [], options.axis?.x?.tickFormat, tickLabelStyle);
1498
+ defaultMargin.left = Math.max(defaultMargin.left, Math.ceil(first / 2));
1499
+ defaultMargin.right = Math.max(defaultMargin.right, Math.ceil(last / 2));
1500
+ }
1419
1501
  return defaultMargin;
1420
1502
  }, [
1421
1503
  options,
@@ -2128,15 +2210,370 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2128
2210
  });
2129
2211
  };
2130
2212
  //#endregion
2213
+ //#region src/components/tooltip/private/bounded-tooltip.tsx
2214
+ const DEFAULT_OFFSET = 10;
2215
+ const POINTER_HEIGHT = 6;
2216
+ const clamp = (position, min, max, size) => Math.min(Math.max(position, min), Math.max(min, max - size));
2217
+ const isClipping = (element) => {
2218
+ const { overflow, overflowX, overflowY } = getComputedStyle(element);
2219
+ return [
2220
+ overflow,
2221
+ overflowX,
2222
+ overflowY
2223
+ ].some((value) => value && value !== "visible");
2224
+ };
2225
+ const findClippingAncestor = (wrapper) => {
2226
+ let element = wrapper.parentElement;
2227
+ while (element && element !== document.body) {
2228
+ if (isClipping(element)) return element;
2229
+ element = element.parentElement;
2230
+ }
2231
+ return null;
2232
+ };
2233
+ /**
2234
+ * Where the box goes, in wrapper coordinates. Automatic placement flips to
2235
+ * the side that clips less, then clamps inside `bounds`; below-axis placement
2236
+ * is centered on the anchor and clamped horizontally.
2237
+ *
2238
+ * The flip measures against the visible part of the wrapper, its intersection
2239
+ * with `bounds`. Only the clamp is bound by the page: a box that fits inside the
2240
+ * chart on one side stays there, so it does not reach past the chart into
2241
+ * siblings that paint over it.
2242
+ *
2243
+ * @param params - Anchor, offsets, the box size and the rects to keep inside.
2244
+ * @param params.left - Anchor x, in wrapper coordinates.
2245
+ * @param params.top - Anchor y, in wrapper coordinates.
2246
+ * @param params.offsetLeft - Gap between the anchor and the box, horizontally.
2247
+ * @param params.offsetTop - Gap between the anchor and the box, vertically.
2248
+ * @param params.box - Rendered size of the box.
2249
+ * @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
2250
+ * @param params.placement - Fixed below-axis placement or automatic flipping.
2251
+ * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2252
+ * @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
2253
+ */
2254
+ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bounds, placement = "auto" }) => {
2255
+ if (placement === "below-axis") return {
2256
+ x: Math.round(clamp(left - box.width / 2, bounds.left, bounds.right, box.width)),
2257
+ y: top + POINTER_HEIGHT
2258
+ };
2259
+ const fit = {
2260
+ left: Math.max(wrapper.left, bounds.left),
2261
+ top: Math.max(wrapper.top, bounds.top),
2262
+ right: Math.min(wrapper.right, bounds.right),
2263
+ bottom: Math.min(wrapper.bottom, bounds.bottom)
2264
+ };
2265
+ const rightX = left + offsetLeft;
2266
+ const leftX = left - offsetLeft - box.width;
2267
+ const rightOverflow = rightX + box.width - fit.right;
2268
+ const leftOverflow = fit.left - leftX;
2269
+ let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
2270
+ const downY = top + offsetTop;
2271
+ const upY = top - offsetTop - box.height;
2272
+ const downOverflow = downY + box.height - fit.bottom;
2273
+ const upOverflow = fit.top - upY;
2274
+ let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2275
+ x = clamp(x, bounds.left, bounds.right, box.width);
2276
+ y = clamp(y, bounds.top, bounds.bottom, box.height);
2277
+ return {
2278
+ x: Math.round(x),
2279
+ y: Math.round(y)
2280
+ };
2281
+ };
2282
+ /**
2283
+ * visx's `Tooltip`, positioned like its `TooltipWithBounds` but kept inside the
2284
+ * nearest clipping ancestor — or the viewport when there is none — rather than
2285
+ * inside its own parent. Rendered in-tree, a tooltip's parent is the chart
2286
+ * wrapper, which is often narrower than the box; measuring against the parent
2287
+ * alone would let the box spill into an `overflow: hidden` card and be cut off.
2288
+ *
2289
+ * Re-measures on every render, so a box whose content changes width between
2290
+ * two hovers is placed for its current size. See `getBoundedPosition` for the
2291
+ * below-axis exception.
2292
+ *
2293
+ * @param props - visx `Tooltip` props.
2294
+ * @param props.left - Anchor x, in wrapper coordinates.
2295
+ * @param props.top - Anchor y, in wrapper coordinates.
2296
+ * @param props.offsetLeft - Gap between the anchor and the box, horizontally.
2297
+ * @param props.offsetTop - Gap between the anchor and the box, vertically.
2298
+ * @param props.style - Box styles; visx's defaults unless `unstyled`.
2299
+ * @param props.unstyled - Skip `style` and leave the box bare.
2300
+ * @param props.children - Box content.
2301
+ * @param props.placement - Fixed below-axis placement or automatic flipping.
2302
+ * @return The tooltip box.
2303
+ */
2304
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2305
+ const nodeRef = (0, react.useRef)(null);
2306
+ const clipRef = (0, react.useRef)(null);
2307
+ const [position, setPosition] = (0, react.useState)(null);
2308
+ (0, react.useLayoutEffect)(() => {
2309
+ const node = nodeRef.current;
2310
+ const wrapper = node?.parentElement;
2311
+ if (!node || !wrapper) return;
2312
+ if (clipRef.current?.wrapper !== wrapper) clipRef.current = {
2313
+ wrapper,
2314
+ clip: findClippingAncestor(wrapper)
2315
+ };
2316
+ const own = node.getBoundingClientRect();
2317
+ const wrapperRect = wrapper.getBoundingClientRect();
2318
+ const clipRect = clipRef.current.clip?.getBoundingClientRect() ?? {
2319
+ left: 0,
2320
+ top: 0,
2321
+ right: window.innerWidth,
2322
+ bottom: window.innerHeight
2323
+ };
2324
+ const next = getBoundedPosition({
2325
+ left,
2326
+ top,
2327
+ offsetLeft,
2328
+ offsetTop,
2329
+ placement,
2330
+ box: {
2331
+ width: own.width,
2332
+ height: own.height
2333
+ },
2334
+ wrapper: {
2335
+ left: 0,
2336
+ top: 0,
2337
+ right: wrapperRect.width,
2338
+ bottom: wrapperRect.height
2339
+ },
2340
+ bounds: {
2341
+ left: clipRect.left - wrapperRect.left,
2342
+ top: clipRect.top - wrapperRect.top,
2343
+ right: clipRect.right - wrapperRect.left,
2344
+ bottom: clipRect.bottom - wrapperRect.top
2345
+ }
2346
+ });
2347
+ setPosition((current) => current && current.x === next.x && current.y === next.y ? current : next);
2348
+ }, [
2349
+ left,
2350
+ top,
2351
+ offsetLeft,
2352
+ offsetTop,
2353
+ children,
2354
+ placement
2355
+ ]);
2356
+ const x = position?.x ?? left + offsetLeft;
2357
+ const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2358
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_tooltip.Tooltip, {
2359
+ ref: nodeRef,
2360
+ style: {
2361
+ position: "absolute",
2362
+ left: 0,
2363
+ top: 0,
2364
+ transform: `translate(${x}px, ${y}px)`,
2365
+ ...!unstyled && style
2366
+ },
2367
+ ...rest,
2368
+ children: [placement === "below-axis" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2369
+ "aria-hidden": "true",
2370
+ style: {
2371
+ position: "absolute",
2372
+ left: left - x - POINTER_HEIGHT,
2373
+ top: -6,
2374
+ width: 12,
2375
+ height: POINTER_HEIGHT,
2376
+ background: "inherit",
2377
+ clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
2378
+ pointerEvents: "none"
2379
+ }
2380
+ }), children]
2381
+ });
2382
+ };
2383
+ //#endregion
2384
+ //#region src/components/tooltip/xy-chart-tooltip.tsx
2385
+ const CROSSHAIR_STROKE_WIDTH = 1.5;
2386
+ const CROSSHAIR_PAINT_PROPERTIES = /* @__PURE__ */ new Set([
2387
+ "stroke",
2388
+ "strokeWidth",
2389
+ "strokeOpacity",
2390
+ "strokeDasharray",
2391
+ "strokeLinecap",
2392
+ "opacity"
2393
+ ]);
2394
+ const crosshairPaintProps = (props = {}) => {
2395
+ const pickPaint = (value) => Object.fromEntries(Object.entries(value).filter(([key]) => CROSSHAIR_PAINT_PROPERTIES.has(key)));
2396
+ return {
2397
+ ...pickPaint(props),
2398
+ ...props.className === void 0 ? {} : { className: props.className },
2399
+ ...props.style === void 0 ? {} : { style: pickPaint(props.style) }
2400
+ };
2401
+ };
2402
+ const DEFAULT_GLYPH_RADIUS = 4;
2403
+ const FALLBACK_COLOR = "#222";
2404
+ const DEFAULT_TOOLTIP_Z_INDEX = 3;
2405
+ const PORTAL_OPTIONS = /* @__PURE__ */ new Set([
2406
+ "scroll",
2407
+ "debounce",
2408
+ "resizeObserverPolyfill"
2409
+ ]);
2410
+ const isValidNumber = (value) => typeof value === "number" && Number.isFinite(value);
2411
+ const scaleBandwidth = (scale) => {
2412
+ const bandwidth = scale?.bandwidth;
2413
+ return typeof bandwidth === "function" ? Number(bandwidth()) : 0;
2414
+ };
2415
+ const DefaultGlyph$1 = ({ x, y, size, color, glyphStyle, seriesKey }) => {
2416
+ const { theme } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2417
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2418
+ cx: x,
2419
+ cy: y,
2420
+ r: size,
2421
+ fill: color,
2422
+ stroke: theme?.backgroundColor,
2423
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2424
+ paintOrder: "fill",
2425
+ ...glyphStyle
2426
+ });
2427
+ };
2428
+ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultGlyph$1, {
2429
+ seriesKey: key,
2430
+ ...props
2431
+ }, key);
2432
+ 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, tooltipPlacement = "auto", zIndex = DEFAULT_TOOLTIP_Z_INDEX, style, ...rest }) => {
2433
+ const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2434
+ const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2435
+ const tooltipContent = renderTooltip ? renderTooltip({
2436
+ ...tooltipContext,
2437
+ colorScale
2438
+ }) : null;
2439
+ if (tooltipContent == null) return null;
2440
+ const scaleX = xScale;
2441
+ const scaleY = yScale;
2442
+ const getDatumLeftTop = (key, datum) => {
2443
+ const entry = dataRegistry?.get(key);
2444
+ return {
2445
+ left: scaleX && entry?.xAccessor ? Number(scaleX(entry.xAccessor(datum))) + scaleBandwidth(xScale) / 2 : void 0,
2446
+ top: scaleY && entry?.yAccessor ? Number(scaleY(entry.yAccessor(datum))) + scaleBandwidth(yScale) / 2 : void 0
2447
+ };
2448
+ };
2449
+ const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
2450
+ let { tooltipLeft, tooltipTop } = tooltipContext;
2451
+ if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY || tooltipPlacement === "below-axis")) {
2452
+ const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
2453
+ if ((snapTooltipToDatumX || tooltipPlacement === "below-axis") && isValidNumber(left)) tooltipLeft = left;
2454
+ if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
2455
+ }
2456
+ const glyphs = [];
2457
+ if (showDatumGlyph || showSeriesGlyphs) {
2458
+ const size = Number(glyphStyle?.radius ?? DEFAULT_GLYPH_RADIUS);
2459
+ const labelColor = theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2460
+ const fallbackColor = showSeriesGlyphs ? labelColor : theme?.gridStyles?.stroke ?? labelColor;
2461
+ let entries = [];
2462
+ if (showSeriesGlyphs) entries = Object.values(tooltipContext.tooltipData?.datumByKey ?? {});
2463
+ else if (nearestDatum) entries = [nearestDatum];
2464
+ for (const { key, datum, index } of entries) {
2465
+ const { left, top } = getDatumLeftTop(key, datum);
2466
+ if (!isValidNumber(left) || !isValidNumber(top)) continue;
2467
+ glyphs.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("g", {
2468
+ className: "visx-tooltip-glyph",
2469
+ transform: `translate(${left}, ${top})`,
2470
+ children: renderGlyph({
2471
+ key,
2472
+ color: colorScale?.(key) ?? fallbackColor,
2473
+ datum,
2474
+ index,
2475
+ size,
2476
+ x: 0,
2477
+ y: 0,
2478
+ glyphStyle,
2479
+ isNearestDatum: nearestDatum?.key === key
2480
+ })
2481
+ }, key));
2482
+ }
2483
+ }
2484
+ const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2485
+ const marginTop = margin?.top ?? 0;
2486
+ const marginLeft = margin?.left ?? 0;
2487
+ const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : _visx_tooltip.Tooltip;
2488
+ const boxStyle = {
2489
+ ..._visx_tooltip.defaultStyles,
2490
+ zIndex,
2491
+ backgroundColor: theme?.backgroundColor ?? "white",
2492
+ boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2493
+ ...theme?.htmlLabel,
2494
+ ...style
2495
+ };
2496
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2497
+ className: "visx-tooltip-overlay",
2498
+ pointerEvents: "none",
2499
+ children: [
2500
+ showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2501
+ className: "visx-crosshair visx-crosshair-vertical",
2502
+ stroke: crosshairStroke,
2503
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2504
+ ...crosshairPaintProps(verticalCrosshairStyle),
2505
+ x1: tooltipLeft,
2506
+ x2: tooltipLeft,
2507
+ y1: marginTop,
2508
+ y2: marginTop + innerHeight
2509
+ }),
2510
+ showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2511
+ className: "visx-crosshair visx-crosshair-horizontal",
2512
+ stroke: crosshairStroke,
2513
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2514
+ ...crosshairPaintProps(horizontalCrosshairStyle),
2515
+ x1: marginLeft,
2516
+ x2: marginLeft + innerWidth,
2517
+ y1: tooltipTop,
2518
+ y2: tooltipTop
2519
+ }),
2520
+ glyphs
2521
+ ]
2522
+ }), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2523
+ left: tooltipLeft,
2524
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
2525
+ style: boxStyle,
2526
+ applyPositionStyle: true,
2527
+ ...tooltipProps,
2528
+ ...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
2529
+ children: tooltipContent
2530
+ }), container)] });
2531
+ };
2532
+ /**
2533
+ * In-tree replacement for `@visx/xychart`'s `Tooltip`.
2534
+ *
2535
+ * visx renders its tooltip box, glyphs and crosshairs through portals appended
2536
+ * to `document.body`. That puts them in the page's root stacking context, where
2537
+ * they paint above every sticky or fixed element that lives inside a nested
2538
+ * stacking context, and no z-index on that element can change the order. This
2539
+ * component draws the glyphs and crosshairs straight into the chart SVG and
2540
+ * renders the tooltip box into the SVG's parent element, so all of them stack
2541
+ * as ordinary descendants of the chart.
2542
+ *
2543
+ * Render it as a child of `XYChart`. The element wrapping that `XYChart` must
2544
+ * be `position: relative` with the SVG at its origin, and `isolation: isolate`:
2545
+ * the box is placed with the SVG-local coordinates visx reports, and the
2546
+ * isolation keeps the box's `zIndex` from competing with page chrome. The box
2547
+ * flips and clamps to stay inside the nearest ancestor that clips its overflow
2548
+ * (or the viewport), so it may extend past the chart wrapper but is never cut
2549
+ * off unless that ancestor is smaller than the box itself. Below-axis placement
2550
+ * pins it beneath the x-axis and applies horizontal bounds only.
2551
+ *
2552
+ * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2553
+ * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
2554
+ */
2555
+ const XyChartTooltip = (props) => {
2556
+ const tooltipContext = (0, react.useContext)(_visx_xychart.TooltipContext);
2557
+ const [container, setContainer] = (0, react.useState)(null);
2558
+ const anchorRef = (0, react.useCallback)((node) => {
2559
+ setContainer(node?.ownerSVGElement?.parentElement ?? null);
2560
+ }, []);
2561
+ 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, {
2562
+ ...props,
2563
+ tooltipContext,
2564
+ container
2565
+ })] });
2566
+ };
2567
+ //#endregion
2131
2568
  //#region src/components/tooltip/accessible-tooltip.tsx
2132
2569
  const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardFocusedClassName, series = [], mode = "group", verticalCrosshairStyle, horizontalCrosshairStyle, ...props }) => {
2133
2570
  const tooltipContext = (0, react.useContext)(_visx_xychart.TooltipContext);
2134
2571
  const scopeElement = useChartScopeElement();
2135
- const gridStroke = useGlobalChartsTheme().gridStyles?.stroke;
2136
2572
  const crosshairStroke = (0, react.useMemo)(() => {
2137
- const stroke = gridStroke ? resolveCssVariable(gridStroke, scopeElement) : null;
2573
+ const stroke = resolveCssVariable(CATALOG_POINTERS.grid, scopeElement);
2138
2574
  return stroke ? { stroke } : void 0;
2139
- }, [gridStroke, scopeElement]);
2575
+ }, [scopeElement]);
2576
+ const standaloneScopeClass = useStandaloneScopeClass();
2140
2577
  const tooltipData = (0, react.useMemo)(() => {
2141
2578
  if (mode !== "individual") return [];
2142
2579
  if (series.length === 0) return [];
@@ -2197,11 +2634,11 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2197
2634
  tabIndex: -1,
2198
2635
  role: "tooltip",
2199
2636
  "aria-atomic": "true",
2200
- className: (0, clsx.default)(CHART_SCOPE_CLASS, keyboardFocusedClassName),
2637
+ className: (0, clsx.default)(standaloneScopeClass, keyboardFocusedClassName),
2201
2638
  children: tooltipContent
2202
2639
  }, `chart-tooltip-${selectedIndex}`);
2203
2640
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2204
- className: CHART_SCOPE_CLASS,
2641
+ className: standaloneScopeClass,
2205
2642
  role: "tooltip",
2206
2643
  "aria-live": "polite",
2207
2644
  children: tooltipContent
@@ -2211,9 +2648,10 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2211
2648
  renderTooltip,
2212
2649
  selectedIndex,
2213
2650
  tooltipRef,
2214
- keyboardFocusedClassName
2651
+ keyboardFocusedClassName,
2652
+ standaloneScopeClass
2215
2653
  ]);
2216
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_xychart.Tooltip, {
2654
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XyChartTooltip, {
2217
2655
  ...props,
2218
2656
  verticalCrosshairStyle: {
2219
2657
  ...crosshairStroke,
@@ -2226,11 +2664,14 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2226
2664
  renderTooltip: focusableRenderTooltip
2227
2665
  });
2228
2666
  };
2229
- const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate }) => {
2667
+ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
2230
2668
  return {
2231
2669
  tooltipRef: (0, react.useCallback)((element) => {
2232
- if (element && selectedIndex !== void 0) element.focus();
2233
- }, [selectedIndex]),
2670
+ if (element && selectedIndex !== void 0) {
2671
+ if (preventTooltipScroll) element.focus({ preventScroll: true });
2672
+ else element.focus();
2673
+ }
2674
+ }, [preventTooltipScroll, selectedIndex]),
2234
2675
  onChartFocus: (0, react.useCallback)(() => {
2235
2676
  if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
2236
2677
  }, [
@@ -2561,72 +3002,90 @@ const getCurveType = (type, smoothing) => {
2561
3002
  default: return _visx_curve.curveLinear;
2562
3003
  }
2563
3004
  };
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({
3005
+ const YEAR_TICK = { year: "numeric" };
3006
+ const DATE_TICK = {
2574
3007
  month: "short",
2575
3008
  day: "numeric"
2576
- });
2577
- const formatHourTick = dateTimeFormat({
2578
- hour: "numeric",
2579
- hour12: true
2580
- });
2581
- const formatMonthTick = dateTimeFormat({ month: "short" });
3009
+ };
3010
+ const HOUR_TICK = { hour: "numeric" };
3011
+ const MONTH_TICK = { month: "short" };
2582
3012
  const boundaryFormat = (isAnchor, atBoundary, between) => {
2583
3013
  const format = (timestamp) => isAnchor(new Date(timestamp)) ? atBoundary(timestamp) : between(timestamp);
2584
3014
  format.isAnchor = isAnchor;
2585
3015
  return format;
2586
3016
  };
2587
- const formatDateOrHourTick = boundaryFormat((date) => date.getHours() === 0 && date.getMinutes() === 0, formatDateTick, formatHourTick);
2588
- const formatMonthOrYearTick = boundaryFormat((date) => date.getMonth() === 0, formatYearTick, formatMonthTick);
3017
+ const tickFormats = (formatting) => {
3018
+ const clock = createZonedClock(formatting.timeZone);
3019
+ const year = createDateFormatter(YEAR_TICK, formatting);
3020
+ const date = createDateFormatter(DATE_TICK, formatting);
3021
+ const hour = createDateFormatter(HOUR_TICK, formatting);
3022
+ const month = createDateFormatter(MONTH_TICK, formatting);
3023
+ const datedTick = (timestamp) => date(timestamp);
3024
+ datedTick.anchorsAtLabelChange = true;
3025
+ return {
3026
+ year,
3027
+ date: datedTick,
3028
+ dateOrHour: boundaryFormat((value) => clock(value).hour === 0, date, hour),
3029
+ hour,
3030
+ monthOrYear: boundaryFormat((value) => clock(value).month === 1, year, month)
3031
+ };
3032
+ };
2589
3033
  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);
3034
+ 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
3035
  if (!bounds.length) return null;
2592
3036
  return {
2593
3037
  minX: Math.min(...bounds.map(([first]) => Number(first))),
2594
3038
  maxX: Math.max(...bounds.map(([, last]) => Number(last)))
2595
3039
  };
2596
3040
  };
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) => {
3041
+ /**
3042
+ * The most informative tick format for a bucket resolution and the span on screen.
3043
+ *
3044
+ * @param sortedData - Series as returned by `useChartDataTransform`.
3045
+ * @param tickResolution - Caller-declared bucket resolution, when known.
3046
+ * @param formatting - Host locale and time zone.
3047
+ * @param domain - The effective scale domain, or undefined for the data's own extent.
3048
+ * @return The formatter the axis labels its ticks with.
3049
+ */
3050
+ const getFormatter = (sortedData, tickResolution, formatting = {}, domain) => {
3051
+ const format = tickFormats(formatting);
2614
3052
  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;
3053
+ if (resolution === "year") return format.year;
3054
+ if (resolution === "month") return format.monthOrYear;
3055
+ const span = domain ? {
3056
+ minX: domain[0].getTime(),
3057
+ maxX: domain[1].getTime()
3058
+ } : getSpan(sortedData);
3059
+ if (!span) return format.date;
2619
3060
  const diffInHours = Math.abs((0, date_fns.differenceInHours)(span.maxX, span.minX));
2620
3061
  if (resolution === "hour") {
2621
- if (diffInHours <= 24) return formatHourTick;
2622
- if (diffInHours <= 168) return formatDateOrHourTick;
3062
+ if (diffInHours < 24) return format.hour;
3063
+ if (diffInHours <= 168) return format.dateOrHour;
2623
3064
  }
2624
- return Math.abs((0, date_fns.differenceInYears)(span.maxX, span.minX)) <= 1 ? formatDateTick : formatYearTick;
3065
+ return Math.abs((0, date_fns.differenceInYears)(span.maxX, span.minX)) <= 1 ? format.date : format.year;
3066
+ };
3067
+ /**
3068
+ * The instants a set of series spans, as a scale domain.
3069
+ *
3070
+ * @param sortedData - Series as returned by `useChartDataTransform`.
3071
+ * @return Earliest and latest dated point, or undefined when nothing is dated.
3072
+ */
3073
+ const getSeriesExtent = (sortedData) => {
3074
+ const span = getSpan(sortedData);
3075
+ return span ? [new Date(span.minX), new Date(span.maxX)] : void 0;
3076
+ };
3077
+ const getAnchorIndices = (domain, tickFormatter, labels) => {
3078
+ if (tickFormatter.anchorsAtLabelChange) return labels.reduce((indices, label, index) => {
3079
+ if (index === 0 || label !== labels[index - 1]) indices.push(index);
3080
+ return indices;
3081
+ }, []);
3082
+ const { isAnchor } = tickFormatter;
3083
+ if (!isAnchor) return [];
3084
+ return domain.reduce((indices, date, index) => {
3085
+ if (isAnchor(date)) indices.push(index);
3086
+ return indices;
3087
+ }, []);
2625
3088
  };
2626
- const getAnchorIndices = (domain, isAnchor) => domain.reduce((indices, date, index) => {
2627
- if (isAnchor(date)) indices.push(index);
2628
- return indices;
2629
- }, []);
2630
3089
  const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
2631
3090
  const steps = /* @__PURE__ */ new Set();
2632
3091
  for (let count = maxTicks; count > 1; count--) {
@@ -2641,26 +3100,25 @@ const getCandidateSteps = (length, maxTicks, anchorSpacing) => {
2641
3100
  return steps;
2642
3101
  };
2643
3102
  /**
2644
- * Tick values for a band time axis.
3103
+ * Tick values sampled by index, for the bar chart's band axis and for evenly
3104
+ * spaced points on a continuous one.
2645
3105
  *
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.
3106
+ * visx samples by index from offset zero, blind to which labels carry a boundary
3107
+ * and without collapsing repeats — so the tick that prints the year or the date
3108
+ * often isn't sampled at all, and a long series can show the same label twice.
3109
+ * Choose the values instead: sweep the evenly spaced candidates, including those
3110
+ * that step from anchor to anchor, and keep the one that reaches the most anchors
3111
+ * without thinning the axis or putting two identical labels side by side.
2653
3112
  *
2654
- * @param domain - Band domain, in axis order.
3113
+ * @param domain - Buckets in axis order.
2655
3114
  * @param tickFormatter - Formatter the axis will render these values with.
2656
3115
  * @param maxTicks - Most ticks the axis should carry.
2657
3116
  * @return Values to hand the axis as `tickValues`.
2658
3117
  */
2659
3118
  const getBandTickValues = (domain, tickFormatter, maxTicks) => {
2660
3119
  if (!domain.length) return [];
2661
- const { isAnchor } = tickFormatter;
2662
- const anchorIndices = isAnchor ? getAnchorIndices(domain, isAnchor) : [];
2663
3120
  const domainLabels = domain.map((date) => tickFormatter(date.getTime()));
3121
+ const anchorIndices = getAnchorIndices(domain, tickFormatter, domainLabels);
2664
3122
  const candidates = [];
2665
3123
  const consider = (indices) => {
2666
3124
  if (!indices.length || indices.length > maxTicks) return;
@@ -2672,15 +3130,108 @@ const getBandTickValues = (domain, tickFormatter, maxTicks) => {
2672
3130
  for (let index = offset; index < domain.length; index += step) indices.push(index);
2673
3131
  consider(indices);
2674
3132
  }
2675
- for (let stride = 1; stride <= anchorIndices.length; stride++) consider(anchorIndices.filter((_, position) => position % stride === 0));
3133
+ const minStride = Math.max(1, Math.ceil(anchorIndices.length / maxTicks));
3134
+ for (let stride = minStride; stride <= anchorIndices.length; stride++) {
3135
+ const stepped = [];
3136
+ for (let position = 0; position < anchorIndices.length; position += stride) stepped.push(anchorIndices[position]);
3137
+ consider(stepped);
3138
+ }
2676
3139
  if (!candidates.length) return [domain[0]];
2677
3140
  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;
3141
+ const anchorIndexSet = new Set(anchorIndices);
3142
+ const anchorsIn = (indices) => indices.filter((index) => anchorIndexSet.has(index)).length;
2679
3143
  return candidates.filter((indices) => indices.length >= densest - 1).reduce((chosen, indices) => {
2680
3144
  const gain = anchorsIn(indices) - anchorsIn(chosen);
2681
3145
  return gain > 0 || gain === 0 && indices.length > chosen.length ? indices : chosen;
2682
3146
  }).map((index) => domain[index]);
2683
3147
  };
3148
+ /**
3149
+ * The most x-axis ticks a chart of this width has room for.
3150
+ *
3151
+ * @param chartWidth - Chart width in pixels.
3152
+ * @return A tick budget of at least one.
3153
+ */
3154
+ const getMaxTicksForWidth = (chartWidth) => Math.max(1, Math.floor(chartWidth / X_TICK_WIDTH));
3155
+ const MIN_GAP_IN_LABELS = .9;
3156
+ const nearestValue = (sorted, target) => {
3157
+ let low = 0;
3158
+ let high = sorted.length - 1;
3159
+ while (low < high) {
3160
+ const middle = Math.floor((low + high) / 2);
3161
+ if (sorted[middle] < target) low = middle + 1;
3162
+ else high = middle;
3163
+ }
3164
+ const above = sorted[low];
3165
+ const below = low > 0 ? sorted[low - 1] : above;
3166
+ return Math.abs(above - target) <= Math.abs(below - target) ? above : below;
3167
+ };
3168
+ const getPositionTickValues = (points, tickFormatter, maxTicks, axis) => {
3169
+ const times = points.map((point) => point.getTime());
3170
+ const { first, last } = axis;
3171
+ if (maxTicks <= 1 || times.length === 1 || first === last) return [points[0]];
3172
+ const step = (last - first) / (maxTicks - 1);
3173
+ const labels = points.map((point) => tickFormatter(point.getTime()));
3174
+ const minGap = (last - first) / maxTicks * MIN_GAP_IN_LABELS;
3175
+ const anchors = getAnchorIndices(points, tickFormatter, labels).map((index) => times[index]);
3176
+ const chosen = [];
3177
+ let lastLabel = null;
3178
+ for (let index = 0; index < maxTicks; index++) {
3179
+ const target = first + index * step;
3180
+ const anchor = anchors.length ? nearestValue(anchors, target) : null;
3181
+ let tick = anchor !== null && Math.abs(anchor - target) <= step / 2 ? anchor : null;
3182
+ if (tick === null) {
3183
+ const point = nearestValue(times, target);
3184
+ tick = Math.abs(point - target) <= step / 2 ? point : target;
3185
+ }
3186
+ const previous = chosen[chosen.length - 1];
3187
+ if (previous !== void 0 && (tick <= previous || tick - previous < minGap)) continue;
3188
+ const label = tickFormatter(tick);
3189
+ if (label === lastLabel) continue;
3190
+ chosen.push(tick);
3191
+ lastLabel = label;
3192
+ }
3193
+ return chosen.map((timestamp) => new Date(timestamp));
3194
+ };
3195
+ const isIndexSamplingUnusable = (ticks, points, maxTicks, axis) => {
3196
+ if (ticks.length < 2 || points.length < 2) return false;
3197
+ const span = axis.last - axis.first;
3198
+ if (!span) return false;
3199
+ 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;
3200
+ };
3201
+ /**
3202
+ * Tick values for a continuous time axis, chosen from the points it draws.
3203
+ *
3204
+ * Sampled by index while the points are evenly spaced, which keeps the axis on
3205
+ * the calendar boundaries `getBandTickValues` steers towards, and by position
3206
+ * once they are not, where an index stride crowds one end of the scale.
3207
+ *
3208
+ * Values are taken from the data wherever there is one to take, so a local time
3209
+ * that does not exist cannot become a tick and DST needs no special case. Only a
3210
+ * target stranded in a gap is constructed, as a plain instant.
3211
+ *
3212
+ * @param sortedData - Series as returned by `useChartDataTransform`.
3213
+ * @param domain - The effective scale domain, or undefined for all data.
3214
+ * @param tickFormatter - The formatter the ticks will be labeled with.
3215
+ * @param maxTicks - The most ticks the axis has room for.
3216
+ * @return Tick dates, or null when no series carries a usable date.
3217
+ */
3218
+ const getTimeAxisTickValues = (sortedData, domain, tickFormatter, maxTicks) => {
3219
+ const byTimestamp = /* @__PURE__ */ new Map();
3220
+ for (const series of sortedData) for (const point of series.data) {
3221
+ const date = point.date;
3222
+ if (date instanceof Date && Number.isFinite(date.getTime())) byTimestamp.set(date.getTime(), date);
3223
+ }
3224
+ if (!byTimestamp.size) return null;
3225
+ const min = domain?.[0]?.getTime();
3226
+ const max = domain?.[1]?.getTime();
3227
+ 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));
3228
+ const axis = {
3229
+ first: min ?? visible[0].getTime(),
3230
+ last: max ?? visible[visible.length - 1].getTime()
3231
+ };
3232
+ const ticks = getBandTickValues(visible, tickFormatter, maxTicks);
3233
+ return isIndexSamplingUnusable(ticks, visible, maxTicks, axis) ? getPositionTickValues(visible, tickFormatter, maxTicks, axis) : ticks;
3234
+ };
2684
3235
  const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
2685
3236
  const span = getSpan(data);
2686
3237
  if (!span) return 1;
@@ -2695,7 +3246,43 @@ const guessOptimalNumTicks = (data, chartWidth, tickFormatter) => {
2695
3246
  if (ticks.some((tick, idx) => idx > 0 && tick === ticks[idx - 1])) continue;
2696
3247
  return ticks.length;
2697
3248
  }
2698
- return secondBestGuess;
3249
+ return Math.max(1, secondBestGuess);
3250
+ };
3251
+ //#endregion
3252
+ //#region src/charts/private/time-axis-options.ts
3253
+ const toDateDomain = (domain) => domain ? [new Date(domain[0]), new Date(domain[1])] : void 0;
3254
+ /**
3255
+ * The x-axis options for a continuous time chart, shared by LineChart and AreaChart.
3256
+ *
3257
+ * Kept in one place so the tick-selection logic isn't duplicated between them.
3258
+ *
3259
+ * @param args - Named arguments.
3260
+ * @param args.dataSorted - Series as returned by `useChartDataTransform`.
3261
+ * @param args.width - Chart width in pixels, which bounds how many ticks fit.
3262
+ * @param args.axisOptions - The caller's `options.axis.x`.
3263
+ * @param args.scaleDomain - The caller's `options.xScale.domain`.
3264
+ * @param args.zoomDomain - The chart's current zoom window, when zoomed.
3265
+ * @param args.formatting - Host locale and time zone.
3266
+ * @param args.isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it. Defaults to all of them.
3267
+ * @return Options ready to spread into the chart's `axis.x`; `orientation` is always set.
3268
+ */
3269
+ const buildTimeAxisOptions = ({ dataSorted, width, axisOptions, scaleDomain, zoomDomain, formatting, isSeriesRendered }) => {
3270
+ const { tickResolution, tickFormat, tickValues: callerTickValues, numTicks: callerNumTicks, orientation: callerOrientation, display: callerDisplay, ...rest } = axisOptions ?? {};
3271
+ const rendered = isSeriesRendered ? dataSorted.filter(isSeriesRendered) : dataSorted;
3272
+ const effectiveDomain = zoomDomain ?? toDateDomain(scaleDomain) ?? getSeriesExtent(rendered);
3273
+ const ownFormatter = getFormatter(rendered, tickResolution, formatting, effectiveDomain);
3274
+ const formatter = tickFormat || ownFormatter;
3275
+ const ownTickValues = tickFormat || callerTickValues ? null : getTimeAxisTickValues(rendered, effectiveDomain, ownFormatter, callerNumTicks ?? getMaxTicksForWidth(width));
3276
+ const tickValues = callerTickValues ?? (ownTickValues?.length ? ownTickValues : void 0);
3277
+ const numTicks = callerNumTicks ?? (tickValues ? void 0 : guessOptimalNumTicks(rendered, width, formatter));
3278
+ return {
3279
+ orientation: callerOrientation ?? "bottom",
3280
+ tickFormat: formatter,
3281
+ display: callerDisplay ?? true,
3282
+ ...rest,
3283
+ ...tickValues ? { tickValues } : {},
3284
+ ...numTicks === void 0 ? {} : { numTicks }
3285
+ };
2699
3286
  };
2700
3287
  //#endregion
2701
3288
  //#region src/charts/private/with-responsive/with-responsive.module.scss
@@ -2976,6 +3563,9 @@ function ZoomResetButton({ onClick }) {
2976
3563
  });
2977
3564
  }
2978
3565
  //#endregion
3566
+ //#region src/charts/private/xy-plot/xy-plot.module.scss
3567
+ var xy_plot_module_default = { "xy-plot": "a8ccharts-bIyhgG-xy-plot" };
3568
+ //#endregion
2979
3569
  //#region src/charts/line-chart/line-chart.module.scss
2980
3570
  var line_chart_module_default = {
2981
3571
  "line-chart": "a8ccharts-inuQka-line-chart",
@@ -3216,12 +3806,14 @@ const getVerticalAnchor = (subjectType, isFlippedVertically, y, yMax, height) =>
3216
3806
  };
3217
3807
  const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", styles: datumStyles, testId, renderLabel, renderLabelPopover }) => {
3218
3808
  const providerTheme = useGlobalChartsTheme();
3219
- const scopeElement = useChartScopeElement();
3220
3809
  const { xScale, yScale } = (0, react.useContext)(_visx_xychart.DataContext) || {};
3221
3810
  const labelRef = (0, react.useRef)(null);
3222
3811
  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;
3812
+ const styles = (0, react.useMemo)(() => deepmerge.default.all([
3813
+ ANNOTATION_POINTERS,
3814
+ providerTheme.annotationStyles ?? {},
3815
+ datumStyles ?? {}
3816
+ ]), [providerTheme.annotationStyles, datumStyles]);
3225
3817
  (0, react.useEffect)(() => {
3226
3818
  if (labelRef.current?.getBBox) {
3227
3819
  const bbox = labelRef.current.getBBox();
@@ -3307,15 +3899,8 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
3307
3899
  dx,
3308
3900
  dy,
3309
3901
  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
- }),
3902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.Connector, { ...styles?.connector }),
3903
+ subjectType === "circle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.CircleSubject, { ...styles?.circleSubject }),
3319
3904
  subjectType === "line-vertical" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_annotation.LineSubject, {
3320
3905
  min: yMax,
3321
3906
  max: yMin,
@@ -3349,8 +3934,6 @@ const LineChartAnnotation = ({ datum, title, subtitle, subjectType = "circle", s
3349
3934
  title,
3350
3935
  subtitle,
3351
3936
  ...styles?.label,
3352
- anchorLineStroke: resolveColor(styles?.label?.anchorLineStroke),
3353
- backgroundFill: resolveColor(styles?.label?.backgroundFill),
3354
3937
  ...labelPosition,
3355
3938
  horizontalAnchor: getHorizontalAnchor(subjectType, isFlippedHorizontally),
3356
3939
  verticalAnchor: getVerticalAnchor(subjectType, isFlippedVertically, y, yMax, height ?? ANNOTATION_INIT_HEIGHT)
@@ -3398,16 +3981,34 @@ const toNumber = (val) => {
3398
3981
  const num = typeof val === "number" ? val : parseFloat(val);
3399
3982
  return isNaN(num) ? void 0 : num;
3400
3983
  };
3984
+ const TOOLTIP_DATE = {};
3985
+ const TOOLTIP_FORMAT_BY_RESOLUTION$1 = {
3986
+ hour: {
3987
+ year: "numeric",
3988
+ month: "numeric",
3989
+ day: "numeric",
3990
+ hour: "numeric"
3991
+ },
3992
+ day: TOOLTIP_DATE,
3993
+ month: TOOLTIP_DATE,
3994
+ year: TOOLTIP_DATE
3995
+ };
3996
+ const TooltipDate = ({ date, displayResolution }) => {
3997
+ const formatting = useChartFormatting();
3998
+ const format = (0, react.useMemo)(() => createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION$1[displayResolution], formatting), [displayResolution, formatting]);
3999
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: date ? format(date) : null });
4000
+ };
3401
4001
  /**
3402
4002
  * Default visx-tooltip render that prints the hovered date as a heading and
3403
4003
  * one row per visible series (label + formatted value), sorted descending by
3404
4004
  * value. Reused by AreaChart, which has the same multi-series shape.
3405
4005
  *
3406
- * @param params - visx `RenderTooltipParams< DataPointDate >`.
4006
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4007
+ * @param contentStyle - Explicit tooltip content color overrides.
3407
4008
  * @return Tooltip JSX, or `null` when no datum is hovered.
3408
4009
  */
3409
- const renderDefaultTooltip = (params) => {
3410
- const { tooltipData } = params;
4010
+ const renderDefaultTooltip = (params, contentStyle) => {
4011
+ const { tooltipData, bucketInfo } = params;
3411
4012
  const nearestDatum = tooltipData?.nearestDatum?.datum;
3412
4013
  if (!nearestDatum) return null;
3413
4014
  const tooltipPoints = Object.entries(tooltipData?.datumByKey || {}).map(([key, { datum }]) => ({
@@ -3416,9 +4017,13 @@ const renderDefaultTooltip = (params) => {
3416
4017
  })).sort((a, b) => b.value - a.value);
3417
4018
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3418
4019
  className: line_chart_module_default["line-chart__tooltip"],
4020
+ style: contentStyle,
3419
4021
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3420
4022
  className: line_chart_module_default["line-chart__tooltip-date"],
3421
- children: nearestDatum.date?.toLocaleDateString()
4023
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
4024
+ date: nearestDatum.date,
4025
+ displayResolution: bucketInfo?.displayResolution ?? "day"
4026
+ })
3422
4027
  }), tooltipPoints.map((point) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
3423
4028
  direction: "row",
3424
4029
  align: "center",
@@ -3462,14 +4067,14 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
3462
4067
  ]);
3463
4068
  return null;
3464
4069
  };
3465
- const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
4070
+ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartId, width, height, className, margin, withTooltips = true, withTooltipCrosshairs, showLegend = false, legend = {}, renderGlyph = defaultRenderGlyph, glyphStyle = {}, withLegendGlyph = false, withGradientFill = false, smoothing = true, curveType, renderTooltip = renderDefaultTooltip, tooltipPlacement, tooltipStyle, withStartGlyphs = false, withEndGlyphs = false, animation, options = {}, onPointerDown = void 0, onPointerUp = void 0, onPointerMove = void 0, onPointerOut = void 0, onDatumActivate = void 0, zoomable = false, rescaleYOnVisibilityChange = true, defaultHiddenSeries, children, gridVisibility, gap = "md" }, ref) => {
3466
4071
  const legendInteractive = legend.interactive ?? false;
3467
4072
  const legendCollapseGroups = legend.collapseGroups ?? false;
3468
4073
  const legendShape = legend.shape ?? "line";
3469
4074
  const legendPosition = legend.position ?? "bottom";
3470
- const providerTheme = useGlobalChartsTheme();
4075
+ const formatting = useChartFormatting();
3471
4076
  const theme = useXYChartTheme(data);
3472
- const resolvedBackgroundColor = theme.backgroundColor ?? providerTheme.backgroundColor;
4077
+ const resolvedBackgroundColor = theme.backgroundColor;
3473
4078
  const chartId = useChartId(providedChartId);
3474
4079
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
3475
4080
  const isSeriesVisible = (0, react.useCallback)((seriesLabel) => !hiddenSeries.has(seriesLabel), [hiddenSeries]);
@@ -3540,20 +4145,21 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3540
4145
  setIsNavigating,
3541
4146
  chartRef,
3542
4147
  totalPoints: dataSorted[0]?.data.length || 0,
3543
- onActivate: activateSelectedPoint
4148
+ onActivate: activateSelectedPoint,
4149
+ preventTooltipScroll: tooltipPlacement === "below-axis"
3544
4150
  });
3545
4151
  const chartOptions = (0, react.useMemo)(() => {
3546
- const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
3547
- const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
3548
4152
  return {
3549
4153
  axis: {
3550
- x: {
3551
- orientation: "bottom",
3552
- numTicks: guessOptimalNumTicks(dataSorted, width, formatter),
3553
- tickFormat: formatter,
3554
- display: true,
3555
- ...xAxisOptions
3556
- },
4154
+ x: buildTimeAxisOptions({
4155
+ dataSorted,
4156
+ width,
4157
+ axisOptions: options?.axis?.x,
4158
+ scaleDomain: options?.xScale?.domain,
4159
+ zoomDomain: zoom.domain,
4160
+ formatting,
4161
+ isSeriesRendered: (series) => isSeriesVisible(series.label)
4162
+ }),
3557
4163
  y: {
3558
4164
  orientation: "left",
3559
4165
  numTicks: 4,
@@ -3580,7 +4186,14 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3580
4186
  dataSorted,
3581
4187
  width,
3582
4188
  zoom.domain,
3583
- stableYDomain
4189
+ stableYDomain,
4190
+ formatting,
4191
+ isSeriesVisible
4192
+ ]);
4193
+ const bucketInfo = (0, react.useMemo)(() => getBucketInfo(dataSorted.filter((series) => isSeriesVisible(series.label)), options?.axis?.x?.tickResolution), [
4194
+ dataSorted,
4195
+ isSeriesVisible,
4196
+ options?.axis?.x?.tickResolution
3584
4197
  ]);
3585
4198
  const tooltipRenderGlyph = (0, react.useMemo)(() => {
3586
4199
  return (props) => {
@@ -3642,6 +4255,29 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3642
4255
  xAccessor: (d) => d?.date,
3643
4256
  yAccessor: (d) => d?.value
3644
4257
  };
4258
+ const resolvedTooltipStyle = (0, react.useMemo)(() => {
4259
+ if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
4260
+ if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
4261
+ return {
4262
+ backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
4263
+ ...tooltipStyle
4264
+ };
4265
+ }, [renderTooltip, tooltipStyle]);
4266
+ const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
4267
+ ...params,
4268
+ bucketInfo
4269
+ }, {
4270
+ color: tooltipStyle?.color,
4271
+ background: tooltipStyle?.background,
4272
+ backgroundColor: tooltipStyle?.backgroundColor
4273
+ }) : renderTooltip({
4274
+ ...params,
4275
+ bucketInfo
4276
+ }), [
4277
+ renderTooltip,
4278
+ bucketInfo,
4279
+ tooltipStyle
4280
+ ]);
3645
4281
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3646
4282
  className: (0, clsx.default)("line-chart", line_chart_module_default["line-chart"]),
3647
4283
  children: error
@@ -3690,7 +4326,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3690
4326
  onBlur: onChartBlur,
3691
4327
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3692
4328
  ref: chartRef,
3693
- style: { position: "relative" },
4329
+ className: xy_plot_module_default["xy-plot"],
3694
4330
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
3695
4331
  theme,
3696
4332
  width,
@@ -3780,13 +4416,17 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3780
4416
  withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
3781
4417
  detectBounds: true,
3782
4418
  snapTooltipToDatumX: true,
4419
+ tooltipPlacement,
4420
+ style: resolvedTooltipStyle,
3783
4421
  snapTooltipToDatumY: true,
3784
4422
  showSeriesGlyphs: true,
3785
- renderTooltip,
4423
+ renderTooltip: tooltipRenderer,
3786
4424
  renderGlyph: tooltipRenderGlyph,
3787
4425
  glyphStyle,
3788
4426
  showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
3789
4427
  showHorizontalCrosshair: withTooltipCrosshairs?.showHorizontal,
4428
+ verticalCrosshairStyle: withTooltipCrosshairs?.verticalStyle,
4429
+ horizontalCrosshairStyle: withTooltipCrosshairs?.horizontalStyle,
3790
4430
  selectedIndex,
3791
4431
  tooltipRef,
3792
4432
  keyboardFocusedClassName: line_chart_module_default["line-chart__tooltip--keyboard-focused"],
@@ -3920,12 +4560,11 @@ const HoverGlyphs = ({ visibleSeries, stacked, stackOffset, getElementStyles, st
3920
4560
  };
3921
4561
  //#endregion
3922
4562
  //#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) => {
4563
+ 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
4564
  const legendInteractive = legend.interactive ?? false;
3925
4565
  const legendShape = legend.shape ?? "rect";
3926
4566
  const legendPosition = legend.position ?? "bottom";
3927
- const rescaleYOnVisibility = rescaleYOnVisibilityChange ?? rescaleYOnLegendToggle ?? true;
3928
- const providerTheme = useGlobalChartsTheme();
4567
+ const formatting = useChartFormatting();
3929
4568
  const theme = useXYChartTheme(data);
3930
4569
  const chartId = useChartId(providedChartId);
3931
4570
  const hiddenSeries = useDefaultHiddenSeries(chartId, defaultHiddenSeries);
@@ -3975,7 +4614,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
3975
4614
  totalPoints: dataSorted[0]?.data.length || 0
3976
4615
  });
3977
4616
  const fixedYDomain = (0, react.useMemo)(() => {
3978
- if (rescaleYOnVisibility || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
4617
+ if (rescaleYOnVisibilityChange || !dataSorted.length || !dataSorted[0].data.length || stacked && stackOffset !== "none") return;
3979
4618
  if (stacked) {
3980
4619
  const numPoints = Math.max(...dataSorted.map((s) => s.data.length));
3981
4620
  let posMax = 0;
@@ -4009,20 +4648,19 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4009
4648
  dataSorted,
4010
4649
  stacked,
4011
4650
  stackOffset,
4012
- rescaleYOnVisibility
4651
+ rescaleYOnVisibilityChange
4013
4652
  ]);
4014
4653
  const chartOptions = (0, react.useMemo)(() => {
4015
- const { tickResolution, tickFormat, ...xAxisOptions } = options?.axis?.x ?? {};
4016
- const formatter = tickFormat || getFormatter(dataSorted, tickResolution);
4017
4654
  return {
4018
4655
  axis: {
4019
- x: {
4020
- orientation: "bottom",
4021
- numTicks: guessOptimalNumTicks(dataSorted, width, formatter),
4022
- tickFormat: formatter,
4023
- display: true,
4024
- ...xAxisOptions
4025
- },
4656
+ x: buildTimeAxisOptions({
4657
+ dataSorted,
4658
+ width,
4659
+ axisOptions: options?.axis?.x,
4660
+ scaleDomain: options?.xScale?.domain,
4661
+ zoomDomain: zoom.domain,
4662
+ formatting
4663
+ }),
4026
4664
  y: {
4027
4665
  orientation: "left",
4028
4666
  numTicks: 4,
@@ -4050,7 +4688,8 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4050
4688
  width,
4051
4689
  stacked,
4052
4690
  fixedYDomain,
4053
- zoom.domain
4691
+ zoom.domain,
4692
+ formatting
4054
4693
  ]);
4055
4694
  const defaultMargin = useChartMargin(height, chartOptions, dataSorted, theme);
4056
4695
  const error = validateData$3(dataSorted);
@@ -4086,9 +4725,17 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4086
4725
  };
4087
4726
  const zeroYAccessor = (0, react.useCallback)(() => 0, []);
4088
4727
  const visibleLabels = (0, react.useMemo)(() => new Set(seriesWithVisibility.filter((s) => s.isVisible).map((s) => s.series.label)), [seriesWithVisibility]);
4728
+ const bucketInfo = (0, react.useMemo)(() => getBucketInfo(dataSorted.filter((series) => visibleLabels.has(series.label)), options?.axis?.x?.tickResolution), [
4729
+ dataSorted,
4730
+ visibleLabels,
4731
+ options?.axis?.x?.tickResolution
4732
+ ]);
4089
4733
  const filteredRenderTooltip = (0, react.useCallback)((params) => {
4090
4734
  const datumByKey = params?.tooltipData?.datumByKey;
4091
- if (!datumByKey) return renderTooltip(params);
4735
+ if (!datumByKey) return renderTooltip({
4736
+ ...params,
4737
+ bucketInfo
4738
+ });
4092
4739
  const filtered = Object.fromEntries(Object.entries(datumByKey).filter(([key]) => visibleLabels.has(key)));
4093
4740
  if (Object.keys(filtered).length === 0) return null;
4094
4741
  const nearestDatum = params?.tooltipData?.nearestDatum;
@@ -4098,13 +4745,18 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4098
4745
  };
4099
4746
  return renderTooltip({
4100
4747
  ...params,
4748
+ bucketInfo,
4101
4749
  tooltipData: {
4102
4750
  ...params.tooltipData,
4103
4751
  datumByKey: filtered,
4104
4752
  nearestDatum: nextNearest
4105
4753
  }
4106
4754
  });
4107
- }, [renderTooltip, visibleLabels]);
4755
+ }, [
4756
+ renderTooltip,
4757
+ visibleLabels,
4758
+ bucketInfo
4759
+ ]);
4108
4760
  const resolvedFillOpacity = fillOpacity ?? (stacked ? .85 : .4);
4109
4761
  const resolvedWithStroke = withStroke ?? !stacked;
4110
4762
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4179,7 +4831,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4179
4831
  onBlur: onChartBlur,
4180
4832
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4181
4833
  ref: chartRef,
4182
- style: { position: "relative" },
4834
+ className: xy_plot_module_default["xy-plot"],
4183
4835
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
4184
4836
  theme,
4185
4837
  width,
@@ -4235,7 +4887,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4235
4887
  stacked,
4236
4888
  stackOffset,
4237
4889
  getElementStyles,
4238
- strokeColor: theme.backgroundColor ?? providerTheme.backgroundColor
4890
+ strokeColor: theme.backgroundColor
4239
4891
  })] }),
4240
4892
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AreaChartScalesRef, {
4241
4893
  chartRef: internalChartRef,
@@ -4403,8 +5055,7 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
4403
5055
  year: "numeric",
4404
5056
  month: "long",
4405
5057
  day: "numeric",
4406
- hour: "numeric",
4407
- hour12: true
5058
+ hour: "numeric"
4408
5059
  },
4409
5060
  day: {
4410
5061
  year: "numeric",
@@ -4423,16 +5074,16 @@ const TOOLTIP_FORMAT_BY_RESOLUTION = {
4423
5074
  *
4424
5075
  * @param data - Date-based series, already parsed and sorted by `useChartDataTransform`.
4425
5076
  * @param tickResolution - Caller-declared bucket resolution, when known.
5077
+ * @param formatting - Host locale and time zone.
4426
5078
  * @return Tooltip label formatter.
4427
5079
  */
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
- }));
5080
+ const getTooltipFormatter = (data, tickResolution, formatting) => {
5081
+ if (tickResolution === "week") {
5082
+ const formatDay = createDateFormatter(TOOLTIP_FORMAT_BY_RESOLUTION.day, formatting);
5083
+ return (timestamp) => (0, _wordpress_i18n.sprintf)((0, _wordpress_i18n.__)("Week of %s", "jetpack-charts"), formatDay(timestamp));
5084
+ }
4434
5085
  const format = TOOLTIP_FORMAT_BY_RESOLUTION[getBucketResolution(data, tickResolution)] ?? TOOLTIP_FORMAT_BY_RESOLUTION.day;
4435
- return (timestamp) => new Date(timestamp).toLocaleString(void 0, format);
5086
+ return createDateFormatter(format, formatting);
4436
5087
  };
4437
5088
  const identity = (label) => label;
4438
5089
  const byBucket = (format) => (bucket) => typeof bucket === "string" ? bucket : format(Number(bucket));
@@ -4483,6 +5134,7 @@ const getGroupPadding = (scale) => {
4483
5134
  */
4484
5135
  function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = ALL_RENDERED) {
4485
5136
  const stableOptions = useDeepMemo(options);
5137
+ const formatting = useChartFormatting();
4486
5138
  const axisConfig = (0, react.useMemo)(() => {
4487
5139
  const { labelOverflow: xLabelOverflow, tickResolution: xTickResolution, tickFormat: xTickFormat, ...xAxisOptions } = stableOptions.axis?.x || {};
4488
5140
  const { labelOverflow: yLabelOverflow, tickResolution: yTickResolution, tickFormat: yTickFormat, ...yAxisOptions } = stableOptions.axis?.y || {};
@@ -4509,9 +5161,9 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
4509
5161
  zero: false
4510
5162
  };
4511
5163
  const hasLabels = Boolean(data?.[0]?.data?.[0]?.label);
4512
- const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution);
5164
+ const timeTickFormatter = hasLabels ? null : getFormatter(data, tickResolution, formatting);
4513
5165
  const labelFormatter = timeTickFormatter ? byBucket(timeTickFormatter) : identity;
4514
- const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution));
5166
+ const tooltipDatumFormatter = hasLabels ? labelFormatter : byBucket(getTooltipFormatter(data, tickResolution, formatting));
4515
5167
  const valueFormatter = _automattic_number_formatters.formatNumberCompact;
4516
5168
  const bandDomain = timeTickFormatter ? getBandDomain(data, isSeriesRendered) : null;
4517
5169
  const valueAccessor = (d) => {
@@ -4547,7 +5199,8 @@ function useBarChartOptions(data, horizontal, options = {}, isSeriesRendered = A
4547
5199
  }, [
4548
5200
  data,
4549
5201
  tickResolution,
4550
- isSeriesRendered
5202
+ isSeriesRendered,
5203
+ formatting
4551
5204
  ]);
4552
5205
  return (0, react.useMemo)(() => {
4553
5206
  const { xTickFormat: defaultXTickFormat, yTickFormat: defaultYTickFormat, tooltipLabelFormatter: defaultTooltipLabelFormatter, xAccessor, yAccessor, gridVisibility, xScale: baseXScale, yScale: baseYScale } = defaultOptions[horizontal ? "horizontal" : "vertical"];
@@ -5056,6 +5709,7 @@ const BarChartInternal = ({ data, chartId: providedChartId, width, height, class
5056
5709
  onBlur: onChartBlur,
5057
5710
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5058
5711
  ref: chartRef,
5712
+ className: xy_plot_module_default["xy-plot"],
5059
5713
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
5060
5714
  theme,
5061
5715
  width,
@@ -5223,7 +5877,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
5223
5877
  right: 20,
5224
5878
  bottom: 0,
5225
5879
  top: 0
5226
- }, ...rest }) => {
5880
+ }, className, ...rest }) => {
5227
5881
  const { getElementStyles } = useGlobalChartsContext();
5228
5882
  const tintedData = (0, react.useMemo)(() => {
5229
5883
  if (data.length > 1) return data;
@@ -5275,6 +5929,7 @@ const BarListChartInternal = ({ data, width, height, options = {}, margin = {
5275
5929
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarChart, {
5276
5930
  orientation: "horizontal",
5277
5931
  gridVisibility: "none",
5932
+ className: (0, clsx.default)("bar-list-chart", className),
5278
5933
  data: tintedData,
5279
5934
  width,
5280
5935
  height,
@@ -5311,6 +5966,9 @@ const BarListChartResponsive = withResponsive(BarListChart);
5311
5966
  var conversion_funnel_chart_module_default = {
5312
5967
  "bar-container": "a8ccharts-mGEVca-bar-container",
5313
5968
  "change-indicator": "a8ccharts-mGEVca-change-indicator",
5969
+ "change-indicator--negative": "a8ccharts-mGEVca-change-indicator--negative",
5970
+ "change-indicator--positive": "a8ccharts-mGEVca-change-indicator--positive",
5971
+ "conversion-funnel-chart": "a8ccharts-mGEVca-conversion-funnel-chart",
5314
5972
  "conversion-funnel-chart--loading": "a8ccharts-mGEVca-conversion-funnel-chart--loading",
5315
5973
  "empty-state": "a8ccharts-mGEVca-empty-state",
5316
5974
  "funnel-bar": "a8ccharts-mGEVca-funnel-bar",
@@ -5391,23 +6049,14 @@ const useFunnelSelection = (hideTooltip) => {
5391
6049
  */
5392
6050
  const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loading = false, animation, className, chartId: providedChartId, height, style, renderStepLabel, renderStepRate, renderMainMetric, renderTooltip }) => {
5393
6051
  const chartId = useChartId(providedChartId);
5394
- const { conversionFunnelChart: conversionFunnelChartSettings } = useGlobalChartsTheme();
5395
6052
  const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
5396
6053
  const chartRef = (0, react.useRef)(null);
5397
6054
  const selectedBarRef = (0, react.useRef)(null);
5398
6055
  const [scopeNode, setScopeNode] = (0, react.useState)(null);
5399
6056
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip } = (0, _visx_tooltip.useTooltip)();
5400
6057
  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]);
6058
+ const standaloneScopeClass = useStandaloneScopeClass();
5409
6059
  const setChartRef = (0, react.useCallback)((node) => {
5410
- portalContainerRefRef.current(node);
5411
6060
  chartRef.current = node;
5412
6061
  setScopeNode(node);
5413
6062
  }, []);
@@ -5424,30 +6073,22 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5424
6073
  });
5425
6074
  }, [showTooltip]);
5426
6075
  const getMouseTooltipCoords = (0, react.useCallback)((event) => {
5427
- if (containerBounds.width === 0 || containerBounds.height === 0) return null;
6076
+ const bounds = chartRef.current?.getBoundingClientRect();
6077
+ if (!bounds) return null;
5428
6078
  return {
5429
- x: event.clientX - containerBounds.left,
5430
- y: event.clientY - containerBounds.top
6079
+ x: event.clientX - bounds.left,
6080
+ y: event.clientY - bounds.top
5431
6081
  };
5432
- }, [
5433
- containerBounds.width,
5434
- containerBounds.height,
5435
- containerBounds.left,
5436
- containerBounds.top
5437
- ]);
6082
+ }, []);
5438
6083
  const getKeyboardTooltipCoords = (0, react.useCallback)((event) => {
5439
- if (containerBounds.width === 0 || containerBounds.height === 0) return null;
6084
+ const bounds = chartRef.current?.getBoundingClientRect();
6085
+ if (!bounds) return null;
5440
6086
  const rect = event.currentTarget.getBoundingClientRect();
5441
6087
  return {
5442
- x: rect.left + rect.width / 2 - containerBounds.left,
5443
- y: rect.top - containerBounds.top
6088
+ x: rect.left + rect.width / 2 - bounds.left,
6089
+ y: rect.top - bounds.top
5444
6090
  };
5445
- }, [
5446
- containerBounds.width,
5447
- containerBounds.height,
5448
- containerBounds.left,
5449
- containerBounds.top
5450
- ]);
6091
+ }, []);
5451
6092
  const handleStepInteraction = (0, react.useCallback)((step, event, interactionType) => {
5452
6093
  selectedBarRef.current = event.currentTarget;
5453
6094
  const { isClicked } = getStepState(step.id);
@@ -5508,19 +6149,14 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5508
6149
  };
5509
6150
  }, [clearSelectionAndRef]);
5510
6151
  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)";
6152
+ const { color: barColor } = getElementStyles ? getElementStyles({ index: 0 }) : { color: "#000000" };
6153
+ const isPositiveChange = changeIndicator?.startsWith("+");
6154
+ const changeColor = isPositiveChange ? CATALOG_POINTERS.trendUp : CATALOG_POINTERS.trendDown;
5518
6155
  const renderDefaultMainMetric = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5519
6156
  className: conversion_funnel_chart_module_default["main-rate"],
5520
6157
  children: formatPercentage(mainRate)
5521
6158
  }), changeIndicator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5522
- className: conversion_funnel_chart_module_default["change-indicator"],
5523
- style: { color: changeColor },
6159
+ className: (0, clsx.default)(conversion_funnel_chart_module_default["change-indicator"], conversion_funnel_chart_module_default[isPositiveChange ? "change-indicator--positive" : "change-indicator--negative"]),
5524
6160
  children: changeIndicator
5525
6161
  })] });
5526
6162
  const renderDefaultTooltip = (step) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
@@ -5558,7 +6194,7 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5558
6194
  align: "center",
5559
6195
  justify: "center",
5560
6196
  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),
6197
+ 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
6198
  style: {
5563
6199
  ...style,
5564
6200
  height: resolvedHeight
@@ -5569,16 +6205,16 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5569
6205
  })
5570
6206
  });
5571
6207
  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, {
6208
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_wordpress_ui.Stack, {
5573
6209
  direction: "column",
5574
6210
  gap: "xl",
5575
6211
  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),
6212
+ 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
6213
  style: {
5578
6214
  ...style,
5579
6215
  height: resolvedHeight
5580
6216
  },
5581
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartScopeContext.Provider, {
6217
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ChartScopeContext.Provider, {
5582
6218
  value: scopeNode,
5583
6219
  children: [renderMainMetric ? renderMainMetric({
5584
6220
  mainRate,
@@ -5630,7 +6266,6 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5630
6266
  role: "button",
5631
6267
  tabIndex: isBlurred ? -1 : 0,
5632
6268
  "aria-label": step.label,
5633
- style: { backgroundColor: barBackgroundColor },
5634
6269
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5635
6270
  className: (0, clsx.default)(conversion_funnel_chart_module_default["funnel-bar"], { [conversion_funnel_chart_module_default["funnel-bar--animated"]]: animation && !loading && !prefersReducedMotion }),
5636
6271
  style: {
@@ -5642,23 +6277,23 @@ const ConversionFunnelChartInternal = ({ mainRate, changeIndicator, steps, loadi
5642
6277
  }, step.id);
5643
6278
  })
5644
6279
  })]
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
- })()] });
6280
+ }), tooltipOpen && tooltipData && (() => {
6281
+ const tooltipContent = renderTooltip ? renderTooltip({
6282
+ step: tooltipData,
6283
+ index: steps.findIndex((s) => s.id === tooltipData.id),
6284
+ top: tooltipTop,
6285
+ left: tooltipLeft,
6286
+ className: conversion_funnel_chart_module_default["tooltip-wrapper"]
6287
+ }) : renderDefaultTooltip(tooltipData);
6288
+ if (!tooltipContent) return null;
6289
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
6290
+ top: tooltipTop,
6291
+ left: tooltipLeft,
6292
+ className: (0, clsx.default)(standaloneScopeClass, conversion_funnel_chart_module_default["tooltip-wrapper"]),
6293
+ children: tooltipContent
6294
+ });
6295
+ })()]
6296
+ });
5662
6297
  };
5663
6298
  /**
5664
6299
  * ConversionFunnelChart component with provider wrapper
@@ -5804,7 +6439,7 @@ function normalizeGeoChartError(eventArgs) {
5804
6439
  * @return A React component displaying an interactive map with data visualization
5805
6440
  */
5806
6441
  const GeoChartInternal = ({ className, data, width, height, region = "world", resolution = "countries", onError, renderPlaceholder }) => {
5807
- const { getElementStyles, theme: { geoChart: { featureFillColor }, backgroundColor } } = useGlobalChartsContext();
6442
+ const { getElementStyles } = useGlobalChartsContext();
5808
6443
  const scopeElement = useChartScopeElement();
5809
6444
  const containerRef = (0, react.useRef)(null);
5810
6445
  const reportedErrorIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
@@ -5838,8 +6473,8 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
5838
6473
  });
5839
6474
  const fullColorHex = getElementStyles({ index: 0 }).color;
5840
6475
  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;
6476
+ const backgroundColorHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable) || DEFAULT_BACKGROUND_COLOR;
6477
+ const defaultFillColorHex = normalizeColorToHex(CATALOG_POINTERS.surfaceSecondary, scopeElement, resolveCssVariable) || DEFAULT_FEATURE_FILL_COLOR;
5843
6478
  const sanitizedData = (0, react.useMemo)(() => {
5844
6479
  if (data.length === 0) return {
5845
6480
  data,
@@ -5900,8 +6535,7 @@ const GeoChartInternal = ({ className, data, width, height, region = "world", re
5900
6535
  className: (0, clsx.default)("geo-chart", geo_chart_module_default.container, className),
5901
6536
  style: {
5902
6537
  width,
5903
- height,
5904
- backgroundColor
6538
+ height
5905
6539
  },
5906
6540
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_google_charts.Chart, {
5907
6541
  chartType: "GeoChart",
@@ -5945,12 +6579,17 @@ var heatmap_chart_module_default = {
5945
6579
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
5946
6580
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
5947
6581
  "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
6582
+ "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
5948
6583
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
5949
6584
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
6585
+ "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
5950
6586
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
6587
+ "heatmap-chart__gap-end": "a8ccharts-O3YMOW-heatmap-chart__gap-end",
6588
+ "heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
5951
6589
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
5952
6590
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
5953
6591
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
6592
+ "heatmap-chart__legend-label": "a8ccharts-O3YMOW-heatmap-chart__legend-label",
5954
6593
  "heatmap-chart__legend-swatch": "a8ccharts-O3YMOW-heatmap-chart__legend-swatch",
5955
6594
  "heatmap-chart__row": "a8ccharts-O3YMOW-heatmap-chart__row",
5956
6595
  "heatmap-chart__row-label": "a8ccharts-O3YMOW-heatmap-chart__row-label",
@@ -5960,17 +6599,21 @@ var heatmap_chart_module_default = {
5960
6599
  //#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
5961
6600
  const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
5962
6601
  /**
5963
- * Get the min and max values from heatmap data, ignoring null/NaN.
6602
+ * Get the min and max values from heatmap data, ignoring null/NaN. Summary
6603
+ * columns stay out: a roll-up on the scale would flatten every real cell.
5964
6604
  * @param data - The heatmap columns
5965
6605
  * @return Tuple of [min, max] values
5966
6606
  */
5967
6607
  const getValueExtent = (data) => {
5968
6608
  let min = Infinity;
5969
6609
  let max = -Infinity;
5970
- for (const column of data) for (const cell of column.data) {
5971
- if (!isPresent(cell.value)) continue;
5972
- if (cell.value < min) min = cell.value;
5973
- if (cell.value > max) max = cell.value;
6610
+ for (const column of data) {
6611
+ if (column.summary) continue;
6612
+ for (const cell of column.data) {
6613
+ if (!isPresent(cell.value)) continue;
6614
+ if (cell.value < min) min = cell.value;
6615
+ if (cell.value > max) max = cell.value;
6616
+ }
5974
6617
  }
5975
6618
  if (min === Infinity) return [0, 0];
5976
6619
  return [min, max];
@@ -5991,6 +6634,119 @@ const getNormalizedValue = (value, extent) => {
5991
6634
  return Math.min(1, Math.max(0, (value - min) / (max - min)));
5992
6635
  };
5993
6636
  //#endregion
6637
+ //#region src/charts/heatmap-chart/private/civil-day.ts
6638
+ const DAY_MS = 864e5;
6639
+ const KEY_OPTIONS = {
6640
+ calendar: "gregory",
6641
+ numberingSystem: "latn",
6642
+ year: "numeric",
6643
+ month: "2-digit",
6644
+ day: "2-digit"
6645
+ };
6646
+ const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
6647
+ const pad = (value, length) => String(value).padStart(length, "0");
6648
+ /**
6649
+ * The day a UTC proxy stands for.
6650
+ *
6651
+ * @param date - UTC proxy.
6652
+ * @return Its day key.
6653
+ */
6654
+ const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
6655
+ /**
6656
+ * A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
6657
+ * `DAY_MS`.
6658
+ *
6659
+ * @param key - Day key.
6660
+ * @return The proxy, or null when the key names no real day.
6661
+ */
6662
+ const civilDate = (key) => {
6663
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
6664
+ if (!match) return null;
6665
+ const date = /* @__PURE__ */ new Date(0);
6666
+ date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
6667
+ date.setUTCHours(0, 0, 0, 0);
6668
+ return civilKey(date) === key ? date : null;
6669
+ };
6670
+ /**
6671
+ * The day a written date names, ignoring any time and zone it carries.
6672
+ *
6673
+ * @param text - `yyyy-MM-dd`, optionally followed by a time.
6674
+ * @return Its day key, or null.
6675
+ */
6676
+ const writtenDayKey = (text) => {
6677
+ const match = DATE_PREFIX.exec(text);
6678
+ if (!match) return null;
6679
+ const key = `${match[1]}-${match[2]}-${match[3]}`;
6680
+ return civilDate(key) ? key : null;
6681
+ };
6682
+ /**
6683
+ * Reads the day an instant falls on in a zone.
6684
+ *
6685
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
6686
+ * @return Reader taking a `Date`, null for a day no key can hold.
6687
+ */
6688
+ const instantDayReader = (timeZone) => {
6689
+ const formatter = new Intl.DateTimeFormat("en-US", {
6690
+ ...KEY_OPTIONS,
6691
+ timeZone
6692
+ });
6693
+ return (date) => {
6694
+ const parts = formatter.formatToParts(date);
6695
+ const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
6696
+ const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
6697
+ return civilDate(key) ? key : null;
6698
+ };
6699
+ };
6700
+ /**
6701
+ * The day a series point belongs to.
6702
+ *
6703
+ * A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
6704
+ * zone. Anything else is a wall clock the host wrote, so its date part is the day.
6705
+ *
6706
+ * @param point - Series point.
6707
+ * @param readInstant - Reader from `instantDayReader`.
6708
+ * @return Its day key, or null when the point carries no usable date.
6709
+ */
6710
+ const pointDayKey = (point, readInstant) => {
6711
+ if (point.date instanceof Date && !isNaN(point.date.getTime())) {
6712
+ const key = readInstant(point.date);
6713
+ if (key) return key;
6714
+ }
6715
+ const text = point.dateString;
6716
+ if (!text) return null;
6717
+ if (!hasTimezone(text)) return writtenDayKey(text);
6718
+ const parsed = (0, date_fns.parseISO)(text);
6719
+ return isNaN(parsed.getTime()) ? null : readInstant(parsed);
6720
+ };
6721
+ /**
6722
+ * Steps a proxy by whole days.
6723
+ *
6724
+ * @param date - UTC proxy.
6725
+ * @param days - Days to add; may be negative.
6726
+ * @return The shifted proxy.
6727
+ */
6728
+ const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
6729
+ /**
6730
+ * Rounds a proxy down to its week.
6731
+ *
6732
+ * @param date - UTC proxy.
6733
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
6734
+ * @return The proxy for that week's first day.
6735
+ */
6736
+ const startOfCivilWeek = (date, weekStartsOn) => {
6737
+ const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
6738
+ return addCivilDays(date, -daysSinceWeekStart);
6739
+ };
6740
+ /**
6741
+ * Counts the columns a span needs.
6742
+ *
6743
+ * @param from - Earlier UTC proxy.
6744
+ * @param to - Later UTC proxy.
6745
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
6746
+ * @return Columns needed to cover the span, inclusive.
6747
+ */
6748
+ const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
6749
+ //#endregion
5994
6750
  //#region src/charts/heatmap-chart/private/build-calendar-data.ts
5995
6751
  /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
5996
6752
  const LABELLED_ROWS = [
@@ -6005,69 +6761,88 @@ const LABELLED_ROWS = [
6005
6761
  * @param bound - Requested bound as `yyyy-MM-dd`, if any.
6006
6762
  * @param fallback - The series' own bound on this side.
6007
6763
  * @param direction - Which way the bound is allowed to move.
6008
- * @return The bound to draw to.
6764
+ * @return The day key to draw to.
6009
6765
  */
6010
6766
  const widenTo = (bound, fallback, direction) => {
6011
- if (!bound) return fallback;
6012
- const parsed = (0, date_fns.parseISO)(bound);
6013
- if (isNaN(parsed.getTime())) return fallback;
6014
- if (direction === "earlier") return parsed < fallback ? parsed : fallback;
6015
- return parsed > fallback ? parsed : fallback;
6016
- };
6017
- const toDate = (point) => {
6018
- if (point.date instanceof Date && !isNaN(point.date.getTime())) return point.date;
6019
- if (point.dateString) {
6020
- const parsed = (0, date_fns.parseISO)(point.dateString);
6021
- if (!isNaN(parsed.getTime())) return parsed;
6767
+ const key = bound ? writtenDayKey(bound) : null;
6768
+ if (!key) {
6769
+ if (bound) warnOnce(`heatmap:gridSpan:${bound}`, `gridSpan.${direction === "earlier" ? "start" : "end"} ${JSON.stringify(bound)} is not a \`yyyy-MM-dd\` day, so the grid is drawn over the series' own span.`);
6770
+ return fallback;
6022
6771
  }
6023
- return null;
6772
+ if (direction === "earlier") return key < fallback ? key : fallback;
6773
+ return key > fallback ? key : fallback;
6024
6774
  };
6775
+ /**
6776
+ * Lay a day-bucketed series out as calendar columns.
6777
+ *
6778
+ * @param series - Points to bucket.
6779
+ * @param options - Grid shape, plus the locale and zone to read days in.
6780
+ * @return Columns and row labels for `HeatmapChart`.
6781
+ */
6025
6782
  const buildCalendarHeatmapData = (series, options = {}) => {
6026
6783
  const weekStartsOn = options.weekStartsOn ?? 1;
6027
6784
  const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
6028
- const entries = series.map((point) => ({
6029
- date: toDate(point),
6030
- value: point.value
6031
- })).filter((entry) => entry.date !== null);
6032
- if (!entries.length) return {
6785
+ const { locale, timeZone } = sanitizeFormatting({
6786
+ locale: options.locale,
6787
+ timeZone: options.timeZone
6788
+ });
6789
+ const readInstant = instantDayReader(timeZone);
6790
+ const valueByDay = /* @__PURE__ */ new Map();
6791
+ let minDayKey;
6792
+ let maxDayKey;
6793
+ for (const point of series) {
6794
+ const key = pointDayKey(point, readInstant);
6795
+ if (!key) {
6796
+ const offending = point.dateString ?? (point.date && String(point.date));
6797
+ warnOnce(`heatmap:unreadableDate:${offending}`, offending ? `${JSON.stringify(offending)} is not a day this can read, so its point is left out of the calendar. A \`dateString\` must start \`yyyy-MM-dd\`.` : "A point carries neither `date` nor `dateString`, so it is left out of the calendar.");
6798
+ continue;
6799
+ }
6800
+ valueByDay.set(key, point.value);
6801
+ if (!minDayKey || key < minDayKey) minDayKey = key;
6802
+ if (!maxDayKey || key > maxDayKey) maxDayKey = key;
6803
+ }
6804
+ if (!minDayKey || !maxDayKey) return {
6033
6805
  data: [],
6034
6806
  rowLabels: []
6035
6807
  };
6036
- const valueByDay = /* @__PURE__ */ new Map();
6037
- let minDate = entries[0].date;
6038
- let maxDate = entries[0].date;
6039
- for (const { date, value } of entries) {
6040
- valueByDay.set((0, date_fns.format)(date, "yyyy-MM-dd"), value);
6041
- if (date < minDate) minDate = date;
6042
- if (date > maxDate) maxDate = date;
6043
- }
6044
- const minDayKey = (0, date_fns.format)(minDate, "yyyy-MM-dd");
6045
- const maxDayKey = (0, date_fns.format)(maxDate, "yyyy-MM-dd");
6046
- const gridMinDate = widenTo(options.gridSpan?.start, minDate, "earlier");
6047
- const gridMaxDate = widenTo(options.gridSpan?.end, maxDate, "later");
6048
- const gridMaxDayKey = (0, date_fns.format)(gridMaxDate, "yyyy-MM-dd");
6049
- const gridStart = (0, date_fns.startOfWeek)(gridMinDate, { weekStartsOn });
6050
- const gridMinDayKey = (0, date_fns.format)(gridMinDate < minDate ? gridStart : gridMinDate, "yyyy-MM-dd");
6051
- const weekCount = (0, date_fns.differenceInCalendarWeeks)(gridMaxDate, gridStart, { weekStartsOn }) + 1;
6052
- const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? (0, date_fns.format)((0, date_fns.addDays)(gridStart, row), "EEE") : "");
6808
+ const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
6809
+ const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
6810
+ const requestedMinDate = civilDate(requestedMinDayKey);
6811
+ const gridMaxDate = civilDate(gridMaxDayKey);
6812
+ const labelFormatting = {
6813
+ locale,
6814
+ timeZone: "UTC"
6815
+ };
6816
+ const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
6817
+ const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
6818
+ const formatDay = createDateFormatter({
6819
+ weekday: "short",
6820
+ month: "short",
6821
+ day: "numeric",
6822
+ year: "numeric"
6823
+ }, labelFormatting);
6824
+ const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
6825
+ const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
6826
+ const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
6827
+ const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
6053
6828
  const MIN_FIRST_MONTH_WEEKS = 2;
6054
- const firstMonth = gridStart.getMonth();
6829
+ const firstMonth = gridStart.getUTCMonth();
6055
6830
  let firstMonthWeeks = 0;
6056
- while (firstMonthWeeks < weekCount && (0, date_fns.addDays)(gridStart, firstMonthWeeks * 7).getMonth() === firstMonth) firstMonthWeeks++;
6831
+ while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
6057
6832
  const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
6058
6833
  const data = [];
6059
6834
  let previousMonth = -1;
6060
6835
  for (let week = 0; week < weekCount; week++) {
6061
- const columnStart = (0, date_fns.addDays)(gridStart, week * 7);
6062
- const month = columnStart.getMonth();
6063
- const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? (0, date_fns.format)(columnStart, "MMM") : "";
6836
+ const columnStart = addCivilDays(gridStart, week * 7);
6837
+ const month = columnStart.getUTCMonth();
6838
+ const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
6064
6839
  previousMonth = month;
6065
6840
  const cells = [];
6066
6841
  for (let row = 0; row < 7; row++) {
6067
- const day = (0, date_fns.addDays)(gridStart, week * 7 + row);
6068
- const key = (0, date_fns.format)(day, "yyyy-MM-dd");
6842
+ const day = addCivilDays(gridStart, week * 7 + row);
6843
+ const key = civilKey(day);
6069
6844
  const cell = {
6070
- label: (0, date_fns.format)(day, "EEE, MMM d, yyyy"),
6845
+ label: formatDay(day),
6071
6846
  value: valueByDay.has(key) ? valueByDay.get(key) : null
6072
6847
  };
6073
6848
  if (key < gridMinDayKey || key > gridMaxDayKey) {
@@ -6093,7 +6868,7 @@ const HeatmapContext = (0, react.createContext)(null);
6093
6868
  //#region src/charts/heatmap-chart/private/heatmap-legend.tsx
6094
6869
  const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6095
6870
  const context = (0, react.useContext)(HeatmapContext);
6096
- const { legend, backgroundColor } = useGlobalChartsTheme();
6871
+ const { legend } = useGlobalChartsTheme();
6097
6872
  if (!context) return null;
6098
6873
  const { primaryColorHex } = context;
6099
6874
  const labelStyle = legend.labelStyles;
@@ -6104,6 +6879,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6104
6879
  children: [
6105
6880
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6106
6881
  variant: "body-sm",
6882
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6107
6883
  style: labelStyle,
6108
6884
  children: lessLabel ?? (0, _wordpress_i18n.__)("Less", "jetpack-charts")
6109
6885
  }),
@@ -6117,7 +6893,6 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6117
6893
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6118
6894
  style: {
6119
6895
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6120
- "--a8c-charts-color-heatmap-background": backgroundColor,
6121
6896
  "--a8c-charts-heatmap-cell-intensity": intensity
6122
6897
  }
6123
6898
  }, index);
@@ -6125,6 +6900,7 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6125
6900
  }),
6126
6901
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
6127
6902
  variant: "body-sm",
6903
+ className: heatmap_chart_module_default["heatmap-chart__legend-label"],
6128
6904
  style: labelStyle,
6129
6905
  children: moreLabel ?? (0, _wordpress_i18n.__)("More", "jetpack-charts")
6130
6906
  })
@@ -6134,7 +6910,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6134
6910
  //#endregion
6135
6911
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
6136
6912
  const CELL_MIX_FLOOR = .15;
6137
- const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = [], primaryColor, gap = "md", withTooltips = false, renderTooltip, children }) => {
6913
+ const NO_ROW_LABELS = [];
6914
+ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, height = 0, className, compact = false, showValues, maxCellWidth, maxCellHeight, minCellWidth, minCellHeight, rowLabels = NO_ROW_LABELS, primaryColor, gap = "md", withTooltips = false, renderTooltip, children }) => {
6138
6915
  const chartId = useChartId(providedChartId);
6139
6916
  const { getElementStyles, theme } = useGlobalChartsContext();
6140
6917
  const scopeElement = useChartScopeElement();
@@ -6142,17 +6919,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6142
6919
  const { nonLegendChildren } = useChartChildren(children, "HeatmapChart");
6143
6920
  const [selectedIndex, setSelectedIndex] = (0, react.useState)();
6144
6921
  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;
6922
+ const standaloneScopeClass = useStandaloneScopeClass();
6923
+ const containerRef = (0, react.useRef)(null);
6924
+ const getTooltipOrigin = (0, react.useCallback)(() => containerRef.current?.closest(`[data-chart-id="heatmap-chart-${chartId}"]`)?.getBoundingClientRect() ?? null, [chartId]);
6151
6925
  const { color: primaryColorHex } = getElementStyles({
6152
6926
  index: 0,
6153
- overrideColor: primaryColor || heatmapChartSettings.primaryColor
6927
+ overrideColor: primaryColor
6154
6928
  });
6155
- const chartBackgroundHex = normalizeColorToHex(theme.backgroundColor, scopeElement, resolveCssVariable);
6929
+ const chartBackgroundHex = normalizeColorToHex(CATALOG_POINTERS.background, scopeElement, resolveCssVariable);
6156
6930
  const primaryHex = normalizeColorToHex(primaryColorHex);
6157
6931
  const cellHasLightText = (intensity) => isValidHexColor(primaryHex) && isValidHexColor(chartBackgroundHex) && prefersLightText(mixHexColors(primaryHex, chartBackgroundHex, 1 - (CELL_MIX_FLOOR + .85 * intensity)));
6158
6932
  const extent = (0, react.useMemo)(() => getValueExtent(data), [data]);
@@ -6228,19 +7002,21 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6228
7002
  ]);
6229
7003
  const handleCellMouseMove = (0, react.useCallback)((event) => {
6230
7004
  if (!withTooltips) return;
7005
+ const origin = getTooltipOrigin();
7006
+ if (!origin) return;
6231
7007
  const target = event.currentTarget;
6232
7008
  const columnIndex = Number(target.dataset.column);
6233
7009
  const rowIndex = Number(target.dataset.row);
6234
- const bounds = containerBoundsRef.current;
6235
7010
  showTooltip({
6236
- tooltipLeft: event.clientX - bounds.left,
6237
- tooltipTop: event.clientY - bounds.top,
7011
+ tooltipLeft: event.clientX - origin.left,
7012
+ tooltipTop: event.clientY - origin.top,
6238
7013
  tooltipData: buildTooltipData(columnIndex, rowIndex)
6239
7014
  });
6240
7015
  }, [
6241
7016
  withTooltips,
6242
7017
  showTooltip,
6243
- buildTooltipData
7018
+ buildTooltipData,
7019
+ getTooltipOrigin
6244
7020
  ]);
6245
7021
  const handleCellMouseLeave = (0, react.useCallback)(() => {
6246
7022
  if (withTooltips && selectedIndex === void 0) hideTooltip();
@@ -6251,13 +7027,14 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6251
7027
  ]);
6252
7028
  (0, react.useEffect)(() => {
6253
7029
  if (!withTooltips || selectedIndex === void 0) return;
7030
+ const origin = getTooltipOrigin();
7031
+ if (!origin) return;
6254
7032
  const col = Math.floor(selectedIndex / rows);
6255
7033
  const row = selectedIndex % rows;
6256
7034
  const rect = (typeof document !== "undefined" ? document.getElementById(`${chartId}-cell-${col}-${row}`) : null)?.getBoundingClientRect();
6257
- const bounds = containerBoundsRef.current;
6258
7035
  showTooltip({
6259
- tooltipLeft: rect ? rect.left + rect.width / 2 - bounds.left : 0,
6260
- tooltipTop: rect ? rect.top + rect.height / 2 - bounds.top : 0,
7036
+ tooltipLeft: rect ? rect.left + rect.width / 2 - origin.left : 0,
7037
+ tooltipTop: rect ? rect.top + rect.height / 2 - origin.top : 0,
6261
7038
  tooltipData: buildTooltipData(col, row)
6262
7039
  });
6263
7040
  }, [
@@ -6266,7 +7043,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6266
7043
  rows,
6267
7044
  chartId,
6268
7045
  buildTooltipData,
6269
- showTooltip
7046
+ showTooltip,
7047
+ getTooltipOrigin
6270
7048
  ]);
6271
7049
  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
7050
  if (!columns || !rows) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
@@ -6284,14 +7062,20 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6284
7062
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6285
7063
  const gridStyle = {
6286
7064
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6287
- "--a8c-charts-color-heatmap-background": theme.backgroundColor,
6288
- gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
7065
+ gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
6289
7066
  gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
6290
7067
  };
6291
7068
  if (compact) {
6292
7069
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
6293
7070
  gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
6294
7071
  }
7072
+ const summaryGaps = (columnIndex) => {
7073
+ if (!data[columnIndex]?.summary) return {};
7074
+ return {
7075
+ [heatmap_chart_module_default["heatmap-chart__gap-start"]]: columnIndex > 0 && !data[columnIndex - 1]?.summary,
7076
+ [heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
7077
+ };
7078
+ };
6295
7079
  const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
6296
7080
  const heightCapped = !compact && Boolean(maxCellHeight);
6297
7081
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatmapContext.Provider, {
@@ -6329,7 +7113,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6329
7113
  "aria-hidden": "true",
6330
7114
  className: heatmap_chart_module_default["heatmap-chart__row"],
6331
7115
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6332
- className: heatmap_chart_module_default["heatmap-chart__col-label"],
7116
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
7117
+ [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
7118
+ ...summaryGaps(columnIndex)
7119
+ }),
6333
7120
  children: column.label
6334
7121
  }, `col-${columnIndex}`))]
6335
7122
  }), Array.from({ length: rows }).map((_row, rowIndex) => {
@@ -6354,7 +7141,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6354
7141
  }, `cell-${columnIndex}-${rowIndex}`);
6355
7142
  const value = cell?.value ?? null;
6356
7143
  const present = isPresent(value);
6357
- const normalized = present ? getNormalizedValue(value, extent) : 0;
7144
+ const filled = present && !column.summary;
7145
+ const normalized = filled ? getNormalizedValue(value, extent) : 0;
6358
7146
  const flatIndex = columnIndex * rows + rowIndex;
6359
7147
  const info = buildTooltipData(columnIndex, rowIndex);
6360
7148
  const accessibleLabel = `${info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim()}: ${info.value === null ? (0, _wordpress_i18n.__)("No data", "jetpack-charts") : (0, _automattic_number_formatters.formatNumber)(info.value)}`;
@@ -6367,14 +7155,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6367
7155
  "data-column": columnIndex,
6368
7156
  "data-row": rowIndex,
6369
7157
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
6370
- [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: present,
6371
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: present && cellHasLightText(normalized),
7158
+ [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
7159
+ [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
7160
+ [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
7161
+ ...summaryGaps(columnIndex),
6372
7162
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
6373
7163
  }),
6374
- style: present ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
7164
+ style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6375
7165
  onMouseMove: handleCellMouseMove,
6376
7166
  onMouseLeave: handleCellMouseLeave,
6377
- children: drawValues && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7167
+ children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6378
7168
  className: heatmap_chart_module_default["heatmap-chart__cell-value"],
6379
7169
  children: (0, _automattic_number_formatters.formatNumberCompact)(value)
6380
7170
  })
@@ -6382,11 +7172,11 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6382
7172
  })]
6383
7173
  }, `row-${rowIndex}`);
6384
7174
  })]
6385
- }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipInPortal, {
7175
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
6386
7176
  top: tooltipTop,
6387
7177
  left: tooltipLeft,
6388
7178
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6389
- className: "a8c-charts-scope",
7179
+ className: standaloneScopeClass,
6390
7180
  role: "tooltip",
6391
7181
  tabIndex: -1,
6392
7182
  children: (renderTooltip ?? defaultRenderTooltip)(tooltipData)
@@ -6410,7 +7200,43 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ (0, react_jsx_run
6410
7200
  HeatmapChartResponsiveInner.displayName = "HeatmapChart";
6411
7201
  const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
6412
7202
  //#endregion
6413
- //#region ../../../node_modules/.pnpm/is-plain-object@5.0.0/node_modules/is-plain-object/dist/is-plain-object.mjs
7203
+ //#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
7204
+ /**
7205
+ * `buildCalendarHeatmapData` with the locale and zone taken from
7206
+ * `GlobalChartsProvider` where the caller names neither.
7207
+ *
7208
+ * @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
7209
+ * @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
7210
+ * @return Columns and row labels for `HeatmapChart`.
7211
+ */
7212
+ const useCalendarHeatmapData = (series, options = {}) => {
7213
+ const formatting = useChartFormatting();
7214
+ const locale = options.locale ?? formatting.locale;
7215
+ const timeZone = options.timeZone ?? formatting.timeZone;
7216
+ const { weekStartsOn, hideOutOfRangeDays } = options;
7217
+ const gridStart = options.gridSpan?.start;
7218
+ const gridEnd = options.gridSpan?.end;
7219
+ return (0, react.useMemo)(() => buildCalendarHeatmapData(series, {
7220
+ locale,
7221
+ timeZone,
7222
+ weekStartsOn,
7223
+ hideOutOfRangeDays,
7224
+ gridSpan: {
7225
+ start: gridStart,
7226
+ end: gridEnd
7227
+ }
7228
+ }), [
7229
+ series,
7230
+ locale,
7231
+ timeZone,
7232
+ weekStartsOn,
7233
+ hideOutOfRangeDays,
7234
+ gridStart,
7235
+ gridEnd
7236
+ ]);
7237
+ };
7238
+ //#endregion
7239
+ //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6414
7240
  /*!
6415
7241
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
6416
7242
  *
@@ -6773,7 +7599,7 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
6773
7599
  tslib_1.__exportStar(require_dist$1(), exports);
6774
7600
  }));
6775
7601
  //#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
7602
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
6777
7603
  function useUpdateEffect(effect, deps) {
6778
7604
  const mountedRef = (0, react.useRef)(false);
6779
7605
  (0, react.useEffect)(() => {
@@ -8411,7 +9237,7 @@ _createEmotion.css;
8411
9237
  _createEmotion.sheet;
8412
9238
  _createEmotion.cache;
8413
9239
  //#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
9240
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
8415
9241
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
8416
9242
  var useCx = () => {
8417
9243
  const cache = __unsafe_useEmotionCache();
@@ -8518,7 +9344,7 @@ function memize(fn, options) {
8518
9344
  return memoized;
8519
9345
  }
8520
9346
  //#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
9347
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
8522
9348
  var white = "#fff";
8523
9349
  var GRAY = {
8524
9350
  900: "#1e1e1e",
@@ -8597,7 +9423,7 @@ var COLORS = Object.freeze({
8597
9423
  ui: UI
8598
9424
  });
8599
9425
  //#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
9426
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/config-values.mjs
8601
9427
  var CONTROL_HEIGHT = "36px";
8602
9428
  var CONTROL_PROPS = {
8603
9429
  controlPaddingX: 12,
@@ -8612,9 +9438,6 @@ var CONTROL_PROPS = {
8612
9438
  };
8613
9439
  var config_values_default = Object.assign({}, CONTROL_PROPS, {
8614
9440
  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
9441
  elevationIntensity: 1,
8619
9442
  radiusXSmall: "1px",
8620
9443
  radiusSmall: "2px",
@@ -8625,7 +9448,6 @@ var config_values_default = Object.assign({}, CONTROL_PROPS, {
8625
9448
  borderWidth: "1px",
8626
9449
  borderWidthFocus: "1.5px",
8627
9450
  borderWidthTab: "4px",
8628
- spinnerSize: 16,
8629
9451
  fontSize: "13px",
8630
9452
  fontSizeH1: "calc(2.44 * 13px)",
8631
9453
  fontSizeH2: "calc(1.95 * 13px)",
@@ -8702,10 +9524,10 @@ var require_es6 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8702
9524
  };
8703
9525
  }));
8704
9526
  //#endregion
8705
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.53.0/node_modules/@wordpress/warning/build-module/utils.mjs
9527
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/utils.mjs
8706
9528
  var logged = /* @__PURE__ */ new Set();
8707
9529
  //#endregion
8708
- //#region ../../../node_modules/.pnpm/@wordpress+warning@3.53.0/node_modules/@wordpress/warning/build-module/index.mjs
9530
+ //#region ../../../node_modules/.pnpm/@wordpress+warning@3.54.0/node_modules/@wordpress/warning/build-module/index.mjs
8709
9531
  function isDev() {
8710
9532
  return globalThis.SCRIPT_DEBUG === true;
8711
9533
  }
@@ -8719,7 +9541,7 @@ function warning(message) {
8719
9541
  logged.add(message);
8720
9542
  }
8721
9543
  //#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
9544
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
8723
9545
  var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
8724
9546
  var ComponentsContext = (0, react.createContext)(
8725
9547
  /** @type {Record<string, any>} */
@@ -8746,19 +9568,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
8746
9568
  };
8747
9569
  (0, react.memo)(BaseContextSystemProvider);
8748
9570
  //#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
9571
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/constants.mjs
8750
9572
  var COMPONENT_NAMESPACE = "data-wp-component";
8751
9573
  var CONNECTED_NAMESPACE = "data-wp-c16t";
8752
9574
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
8753
9575
  //#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
9576
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
8755
9577
  var import_dist = require_dist();
8756
9578
  function getStyledClassName(namespace) {
8757
9579
  return `components-${(0, import_dist.paramCase)(namespace)}`;
8758
9580
  }
8759
9581
  var getStyledClassNameFromKey = memize(getStyledClassName);
8760
9582
  //#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
9583
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/context-connect.mjs
8762
9584
  function contextConnect(Component, namespace) {
8763
9585
  return _contextConnect(Component, namespace, { forwardsRef: true });
8764
9586
  }
@@ -8775,7 +9597,7 @@ function _contextConnect(Component, namespace, options) {
8775
9597
  });
8776
9598
  }
8777
9599
  //#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
9600
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/utils.mjs
8779
9601
  function getNamespace(componentName) {
8780
9602
  return { [COMPONENT_NAMESPACE]: componentName };
8781
9603
  }
@@ -8783,7 +9605,7 @@ function getConnectedNamespace() {
8783
9605
  return { [CONNECTED_NAMESPACE]: true };
8784
9606
  }
8785
9607
  //#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
9608
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
8787
9609
  function useContextSystem(props, namespace) {
8788
9610
  const contextSystemProps = useComponentsContext();
8789
9611
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -8803,7 +9625,7 @@ function useContextSystem(props, namespace) {
8803
9625
  return finalComponentProps;
8804
9626
  }
8805
9627
  //#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
9628
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
8807
9629
  var visuallyHidden = {
8808
9630
  border: 0,
8809
9631
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -8819,7 +9641,7 @@ var visuallyHidden = {
8819
9641
  wordBreak: "normal"
8820
9642
  };
8821
9643
  //#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
9644
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
8823
9645
  var customAttributeRegExp = /^(data|aria|x)-/i;
8824
9646
  var eventHandlerRegExp = /^on[A-Z]/;
8825
9647
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -8909,7 +9731,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
8909
9731
  }
8910
9732
  var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
8911
9733
  //#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
9734
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/view/component.mjs
8913
9735
  function UnforwardedView({ css, ...restProps }, ref) {
8914
9736
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
8915
9737
  ref,
@@ -8918,7 +9740,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
8918
9740
  }
8919
9741
  var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
8920
9742
  //#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
9743
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
8922
9744
  function UnconnectedVisuallyHidden(props, forwardedRef) {
8923
9745
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
8924
9746
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -8933,7 +9755,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
8933
9755
  }
8934
9756
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
8935
9757
  //#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
9758
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
8937
9759
  var breakpoints = [
8938
9760
  "40em",
8939
9761
  "52em",
@@ -8967,7 +9789,7 @@ function useResponsiveValue(values, options = {}) {
8967
9789
  return array[index >= array.length ? array.length - 1 : index];
8968
9790
  }
8969
9791
  //#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
9792
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/utils.mjs
8971
9793
  var ALIGNMENTS = {
8972
9794
  bottom: {
8973
9795
  alignItems: "flex-end",
@@ -9015,7 +9837,7 @@ function getAlignmentProps(alignment) {
9015
9837
  return alignment ? ALIGNMENTS[alignment] : {};
9016
9838
  }
9017
9839
  //#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
9840
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/hook.mjs
9019
9841
  function useGrid(props) {
9020
9842
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
9021
9843
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -9036,7 +9858,7 @@ function useGrid(props) {
9036
9858
  justifyContent: justify,
9037
9859
  verticalAlign: isInline ? "middle" : void 0,
9038
9860
  ...alignmentProps
9039
- }, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBdURzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBFeHRlcm5hbCBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuXG4vKipcbiAqIFdvcmRQcmVzcyBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5cbi8qKlxuICogSW50ZXJuYWwgZGVwZW5kZW5jaWVzXG4gKi9cbmltcG9ydCB0eXBlIHsgV29yZFByZXNzQ29tcG9uZW50UHJvcHMgfSBmcm9tICcuLi9jb250ZXh0JztcbmltcG9ydCB7IHVzZUNvbnRleHRTeXN0ZW0gfSBmcm9tICcuLi9jb250ZXh0JztcbmltcG9ydCB7IGdldEFsaWdubWVudFByb3BzIH0gZnJvbSAnLi91dGlscyc7XG5pbXBvcnQgeyB1c2VSZXNwb25zaXZlVmFsdWUgfSBmcm9tICcuLi91dGlscy91c2UtcmVzcG9uc2l2ZS12YWx1ZSc7XG5pbXBvcnQgQ09ORklHIGZyb20gJy4uL3V0aWxzL2NvbmZpZy12YWx1ZXMnO1xuaW1wb3J0IHsgdXNlQ3ggfSBmcm9tICcuLi91dGlscy9ob29rcy91c2UtY3gnO1xuaW1wb3J0IHR5cGUgeyBHcmlkUHJvcHMgfSBmcm9tICcuL3R5cGVzJztcblxuZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gdXNlR3JpZChcblx0cHJvcHM6IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzPCBHcmlkUHJvcHMsICdkaXYnID5cbikge1xuXHRjb25zdCB7XG5cdFx0YWxpZ24sXG5cdFx0YWxpZ25tZW50LFxuXHRcdGNsYXNzTmFtZSxcblx0XHRjb2x1bW5HYXAsXG5cdFx0Y29sdW1ucyA9IDIsXG5cdFx0Z2FwID0gMyxcblx0XHRpc0lubGluZSA9IGZhbHNlLFxuXHRcdGp1c3RpZnksXG5cdFx0cm93R2FwLFxuXHRcdHJvd3MsXG5cdFx0dGVtcGxhdGVDb2x1bW5zLFxuXHRcdHRlbXBsYXRlUm93cyxcblx0XHQuLi5vdGhlclByb3BzXG5cdH0gPSB1c2VDb250ZXh0U3lzdGVtKCBwcm9wcywgJ0dyaWQnICk7XG5cblx0Y29uc3QgY29sdW1uc0FzQXJyYXkgPSBBcnJheS5pc0FycmF5KCBjb2x1bW5zICkgPyBjb2x1bW5zIDogWyBjb2x1bW5zIF07XG5cdGNvbnN0IGNvbHVtbiA9IHVzZVJlc3BvbnNpdmVWYWx1ZSggY29sdW1uc0FzQXJyYXkgKTtcblx0Y29uc3Qgcm93c0FzQXJyYXkgPSBBcnJheS5pc0FycmF5KCByb3dzICkgPyByb3dzIDogWyByb3dzIF07XG5cdGNvbnN0IHJvdyA9IHVzZVJlc3BvbnNpdmVWYWx1ZSggcm93c0FzQXJyYXkgKTtcblxuXHRjb25zdCBncmlkVGVtcGxhdGVDb2x1bW5zID1cblx0XHR0ZW1wbGF0ZUNvbHVtbnMgfHwgKCAhISBjb2x1bW5zICYmIGByZXBlYXQoICR7IGNvbHVtbiB9LCAxZnIgKWAgKTtcblx0Y29uc3QgZ3JpZFRlbXBsYXRlUm93cyA9XG5cdFx0dGVtcGxhdGVSb3dzIHx8ICggISEgcm93cyAmJiBgcmVwZWF0KCAkeyByb3cgfSwgMWZyIClgICk7XG5cblx0Y29uc3QgY3ggPSB1c2VDeCgpO1xuXG5cdGNvbnN0IGNsYXNzZXMgPSB1c2VNZW1vKCAoKSA9PiB7XG5cdFx0Y29uc3QgYWxpZ25tZW50UHJvcHMgPSBnZXRBbGlnbm1lbnRQcm9wcyggYWxpZ25tZW50ICk7XG5cblx0XHRjb25zdCBncmlkQ2xhc3NlcyA9IGNzcygge1xuXHRcdFx0YWxpZ25JdGVtczogYWxpZ24sXG5cdFx0XHRkaXNwbGF5OiBpc0lubGluZSA/ICdpbmxpbmUtZ3JpZCcgOiAnZ3JpZCcsXG5cdFx0XHRnYXA6IGBjYWxjKCAkeyBDT05GSUcuZ3JpZEJhc2UgfSAqICR7IGdhcCB9IClgLFxuXHRcdFx0Z3JpZFRlbXBsYXRlQ29sdW1uczogZ3JpZFRlbXBsYXRlQ29sdW1ucyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkVGVtcGxhdGVSb3dzOiBncmlkVGVtcGxhdGVSb3dzIHx8IHVuZGVmaW5lZCxcblx0XHRcdGdyaWRSb3dHYXA6IHJvd0dhcCxcblx0XHRcdGdyaWRDb2x1bW5HYXA6IGNvbHVtbkdhcCxcblx0XHRcdGp1c3RpZnlDb250ZW50OiBqdXN0aWZ5LFxuXHRcdFx0dmVydGljYWxBbGlnbjogaXNJbmxpbmUgPyAnbWlkZGxlJyA6IHVuZGVmaW5lZCxcblx0XHRcdC4uLmFsaWdubWVudFByb3BzLFxuXHRcdH0gKTtcblxuXHRcdHJldHVybiBjeCggZ3JpZENsYXNzZXMsIGNsYXNzTmFtZSApO1xuXHR9LCBbXG5cdFx0YWxpZ24sXG5cdFx0YWxpZ25tZW50LFxuXHRcdGNsYXNzTmFtZSxcblx0XHRjb2x1bW5HYXAsXG5cdFx0Y3gsXG5cdFx0Z2FwLFxuXHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnMsXG5cdFx0Z3JpZFRlbXBsYXRlUm93cyxcblx0XHRpc0lubGluZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XSApO1xuXG5cdHJldHVybiB7IC4uLm90aGVyUHJvcHMsIGNsYXNzTmFtZTogY2xhc3NlcyB9O1xufVxuIl19 */");
9861
+ }, process.env.NODE_ENV === "production" ? "" : ";label:gridClasses;", process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvb2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBNENzQiIsImZpbGUiOiJob29rLnRzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGVtb3Rpb24vcmVhY3QnO1xuaW1wb3J0IHsgdXNlTWVtbyB9IGZyb20gJ0B3b3JkcHJlc3MvZWxlbWVudCc7XG5pbXBvcnQgdHlwZSB7IFdvcmRQcmVzc0NvbXBvbmVudFByb3BzIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyB1c2VDb250ZXh0U3lzdGVtIH0gZnJvbSAnLi4vY29udGV4dCc7XG5pbXBvcnQgeyBnZXRBbGlnbm1lbnRQcm9wcyB9IGZyb20gJy4vdXRpbHMnO1xuaW1wb3J0IHsgdXNlUmVzcG9uc2l2ZVZhbHVlIH0gZnJvbSAnLi4vdXRpbHMvdXNlLXJlc3BvbnNpdmUtdmFsdWUnO1xuaW1wb3J0IENPTkZJRyBmcm9tICcuLi91dGlscy9jb25maWctdmFsdWVzJztcbmltcG9ydCB7IHVzZUN4IH0gZnJvbSAnLi4vdXRpbHMvaG9va3MvdXNlLWN4JztcbmltcG9ydCB0eXBlIHsgR3JpZFByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIHVzZUdyaWQoXG5cdHByb3BzOiBXb3JkUHJlc3NDb21wb25lbnRQcm9wczwgR3JpZFByb3BzLCAnZGl2JyA+XG4pIHtcblx0Y29uc3Qge1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGNvbHVtbnMgPSAyLFxuXHRcdGdhcCA9IDMsXG5cdFx0aXNJbmxpbmUgPSBmYWxzZSxcblx0XHRqdXN0aWZ5LFxuXHRcdHJvd0dhcCxcblx0XHRyb3dzLFxuXHRcdHRlbXBsYXRlQ29sdW1ucyxcblx0XHR0ZW1wbGF0ZVJvd3MsXG5cdFx0Li4ub3RoZXJQcm9wc1xuXHR9ID0gdXNlQ29udGV4dFN5c3RlbSggcHJvcHMsICdHcmlkJyApO1xuXG5cdGNvbnN0IGNvbHVtbnNBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggY29sdW1ucyApID8gY29sdW1ucyA6IFsgY29sdW1ucyBdO1xuXHRjb25zdCBjb2x1bW4gPSB1c2VSZXNwb25zaXZlVmFsdWUoIGNvbHVtbnNBc0FycmF5ICk7XG5cdGNvbnN0IHJvd3NBc0FycmF5ID0gQXJyYXkuaXNBcnJheSggcm93cyApID8gcm93cyA6IFsgcm93cyBdO1xuXHRjb25zdCByb3cgPSB1c2VSZXNwb25zaXZlVmFsdWUoIHJvd3NBc0FycmF5ICk7XG5cblx0Y29uc3QgZ3JpZFRlbXBsYXRlQ29sdW1ucyA9XG5cdFx0dGVtcGxhdGVDb2x1bW5zIHx8ICggISEgY29sdW1ucyAmJiBgcmVwZWF0KCAkeyBjb2x1bW4gfSwgMWZyIClgICk7XG5cdGNvbnN0IGdyaWRUZW1wbGF0ZVJvd3MgPVxuXHRcdHRlbXBsYXRlUm93cyB8fCAoICEhIHJvd3MgJiYgYHJlcGVhdCggJHsgcm93IH0sIDFmciApYCApO1xuXG5cdGNvbnN0IGN4ID0gdXNlQ3goKTtcblxuXHRjb25zdCBjbGFzc2VzID0gdXNlTWVtbyggKCkgPT4ge1xuXHRcdGNvbnN0IGFsaWdubWVudFByb3BzID0gZ2V0QWxpZ25tZW50UHJvcHMoIGFsaWdubWVudCApO1xuXG5cdFx0Y29uc3QgZ3JpZENsYXNzZXMgPSBjc3MoIHtcblx0XHRcdGFsaWduSXRlbXM6IGFsaWduLFxuXHRcdFx0ZGlzcGxheTogaXNJbmxpbmUgPyAnaW5saW5lLWdyaWQnIDogJ2dyaWQnLFxuXHRcdFx0Z2FwOiBgY2FsYyggJHsgQ09ORklHLmdyaWRCYXNlIH0gKiAkeyBnYXAgfSApYCxcblx0XHRcdGdyaWRUZW1wbGF0ZUNvbHVtbnM6IGdyaWRUZW1wbGF0ZUNvbHVtbnMgfHwgdW5kZWZpbmVkLFxuXHRcdFx0Z3JpZFRlbXBsYXRlUm93czogZ3JpZFRlbXBsYXRlUm93cyB8fCB1bmRlZmluZWQsXG5cdFx0XHRncmlkUm93R2FwOiByb3dHYXAsXG5cdFx0XHRncmlkQ29sdW1uR2FwOiBjb2x1bW5HYXAsXG5cdFx0XHRqdXN0aWZ5Q29udGVudDoganVzdGlmeSxcblx0XHRcdHZlcnRpY2FsQWxpZ246IGlzSW5saW5lID8gJ21pZGRsZScgOiB1bmRlZmluZWQsXG5cdFx0XHQuLi5hbGlnbm1lbnRQcm9wcyxcblx0XHR9ICk7XG5cblx0XHRyZXR1cm4gY3goIGdyaWRDbGFzc2VzLCBjbGFzc05hbWUgKTtcblx0fSwgW1xuXHRcdGFsaWduLFxuXHRcdGFsaWdubWVudCxcblx0XHRjbGFzc05hbWUsXG5cdFx0Y29sdW1uR2FwLFxuXHRcdGN4LFxuXHRcdGdhcCxcblx0XHRncmlkVGVtcGxhdGVDb2x1bW5zLFxuXHRcdGdyaWRUZW1wbGF0ZVJvd3MsXG5cdFx0aXNJbmxpbmUsXG5cdFx0anVzdGlmeSxcblx0XHRyb3dHYXAsXG5cdF0gKTtcblxuXHRyZXR1cm4geyAuLi5vdGhlclByb3BzLCBjbGFzc05hbWU6IGNsYXNzZXMgfTtcbn1cbiJdfQ== */");
9040
9862
  return cx(gridClasses, className);
9041
9863
  }, [
9042
9864
  align,
@@ -9057,7 +9879,7 @@ function useGrid(props) {
9057
9879
  };
9058
9880
  }
9059
9881
  //#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
9882
+ //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@date-fns+tz@1.4.1_@types+react-dom@18.3.7_@types+react@18_4546240ebdf94d561ebf2fde285113fe/node_modules/@wordpress/components/build-module/grid/component.mjs
9061
9883
  function UnconnectedGrid(props, forwardedRef) {
9062
9884
  const gridProps = useGrid(props);
9063
9885
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -9080,8 +9902,8 @@ const SUBPIXEL_TOLERANCE = .5;
9080
9902
  * Counts how many leading rows fit inside the content container.
9081
9903
  *
9082
9904
  * 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.
9905
+ * depends on `--a8c-charts-dimension-leaderboard-row-gap`, label wrapping, and
9906
+ * whatever a caller renders as a label, so any restated number would drift.
9085
9907
  *
9086
9908
  * Every row is a direct grid child marked with `data-row-index`; interactive rows
9087
9909
  * use a button wrapper and non-interactive rows use a div wrapper.
@@ -9166,14 +9988,13 @@ function useFittedRowCount(enabled, rowCount, data) {
9166
9988
  * @return Array of legend items for the leaderboard chart
9167
9989
  */
9168
9990
  function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withComparison = false, withOverlayLabel = false, legendLabels }) {
9169
- const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
9170
9991
  const { getElementStyles } = useGlobalChartsContext();
9171
9992
  return (0, react.useMemo)(() => {
9172
9993
  if (!data || data.length === 0) return [];
9173
9994
  const items = [];
9174
9995
  const { color: resolvedPrimaryColor } = getElementStyles({
9175
9996
  index: 0,
9176
- overrideColor: primaryColor || leaderboardChartSettings.primaryColor
9997
+ overrideColor: primaryColor
9177
9998
  });
9178
9999
  items.push({
9179
10000
  label: legendLabels?.primary || (0, _wordpress_i18n.__)("Current period", "jetpack-charts"),
@@ -9182,7 +10003,7 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
9182
10003
  if (withComparison && !withOverlayLabel) {
9183
10004
  const { color: resolvedSecondaryColor } = getElementStyles({
9184
10005
  index: 1,
9185
- overrideColor: secondaryColor || leaderboardChartSettings.secondaryColor
10006
+ overrideColor: secondaryColor
9186
10007
  });
9187
10008
  items.push({
9188
10009
  label: legendLabels?.comparison || (0, _wordpress_i18n.__)("Previous period", "jetpack-charts"),
@@ -9196,7 +10017,6 @@ function useLeaderboardLegendItems({ data, primaryColor, secondaryColor, withCom
9196
10017
  secondaryColor,
9197
10018
  withComparison,
9198
10019
  legendLabels,
9199
- leaderboardChartSettings,
9200
10020
  getElementStyles,
9201
10021
  withOverlayLabel
9202
10022
  ]);
@@ -9210,6 +10030,9 @@ var leaderboard_chart_module_default = {
9210
10030
  "chevron": "a8ccharts-GovfoW-chevron",
9211
10031
  "deltaPlaceholder": "a8ccharts-GovfoW-deltaPlaceholder",
9212
10032
  "deltaValue": "a8ccharts-GovfoW-deltaValue",
10033
+ "deltaValue--down": "a8ccharts-GovfoW-deltaValue--down",
10034
+ "deltaValue--neutral": "a8ccharts-GovfoW-deltaValue--neutral",
10035
+ "deltaValue--up": "a8ccharts-GovfoW-deltaValue--up",
9213
10036
  "emptyState": "a8ccharts-GovfoW-emptyState",
9214
10037
  "fitEmptyState": "a8ccharts-GovfoW-fitEmptyState",
9215
10038
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
@@ -9228,6 +10051,14 @@ var leaderboard_chart_module_default = {
9228
10051
  };
9229
10052
  //#endregion
9230
10053
  //#region src/charts/leaderboard-chart/leaderboard-chart.tsx
10054
+ const ROW_GAP = "var(--a8c-charts-dimension-leaderboard-row-gap, 12px)";
10055
+ const COLUMN_GAP = "var(--a8c-charts-dimension-leaderboard-column-gap, 4px)";
10056
+ /** Indexed by `Math.sign( delta ) + 1`, so [negative, neutral, positive]. */
10057
+ const DELTA_TREND_CLASS = [
10058
+ "deltaValue--down",
10059
+ "deltaValue--neutral",
10060
+ "deltaValue--up"
10061
+ ];
9231
10062
  /**
9232
10063
  * Default value formatter using formatMetricValue
9233
10064
  *
@@ -9330,15 +10161,15 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9330
10161
  ...legend.shapeStyles
9331
10162
  };
9332
10163
  const { legendChildren, nonLegendChildren } = useChartChildren(children, "LeaderboardChart");
9333
- const { labelSpacing, rowGap, columnGap, primaryColor: settingsPrimaryColor, secondaryColor: settingsSecondaryColor, deltaColors } = leaderboardChartSettings;
10164
+ const { labelSpacing } = leaderboardChartSettings;
9334
10165
  const { getElementStyles, isSeriesVisible } = useGlobalChartsContext();
9335
10166
  const { color: resolvedPrimaryColor } = getElementStyles({
9336
10167
  index: 0,
9337
- overrideColor: primaryColor || settingsPrimaryColor
10168
+ overrideColor: primaryColor
9338
10169
  });
9339
10170
  const { color: resolvedSecondaryColor } = getElementStyles({
9340
10171
  index: 1,
9341
- overrideColor: secondaryColor || settingsSecondaryColor
10172
+ overrideColor: secondaryColor
9342
10173
  });
9343
10174
  const legendItems = useLeaderboardLegendItems({
9344
10175
  data: data || [],
@@ -9394,7 +10225,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9394
10225
  legendPosition,
9395
10226
  legendElement: false,
9396
10227
  legendChildren,
9397
- className: (0, clsx.default)(leaderboard_chart_module_default.leaderboardChart, {
10228
+ className: (0, clsx.default)("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
9398
10229
  [leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
9399
10230
  [leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
9400
10231
  }, className),
@@ -9429,7 +10260,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9429
10260
  legendPosition,
9430
10261
  legendElement,
9431
10262
  legendChildren,
9432
- className: (0, clsx.default)(leaderboard_chart_module_default.leaderboardChart, {
10263
+ className: (0, clsx.default)("leaderboard-chart", leaderboard_chart_module_default.leaderboardChart, {
9433
10264
  [leaderboard_chart_module_default["leaderboardChart--responsive"]]: !propWidth && !propHeight,
9434
10265
  [leaderboard_chart_module_default["leaderboardChart--loading"]]: loading
9435
10266
  }, className),
@@ -9451,8 +10282,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9451
10282
  children: getAllHiddenMessage(legendInteractive, "series")
9452
10283
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default, {
9453
10284
  templateColumns: "minmax(0, 1fr) auto",
9454
- rowGap,
9455
- columnGap,
10285
+ rowGap: ROW_GAP,
10286
+ columnGap: COLUMN_GAP,
9456
10287
  "data-leaderboard-grid": true,
9457
10288
  children: data.map((entry, rowIndex) => {
9458
10289
  const rowStyle = shouldFitRows && rowIndex >= fittedCount ? { visibility: "hidden" } : void 0;
@@ -9461,8 +10292,7 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9461
10292
  const hasDelta = hasDeltaValue(entry);
9462
10293
  const showComparisonValue = showComparisonColumn && hasDelta;
9463
10294
  const showComparisonPlaceholder = showComparisonColumn && !hasDelta;
9464
- const colorIndex = showComparisonValue ? Math.sign(entry.delta) + 1 : 1;
9465
- const deltaColor = deltaColors[colorIndex];
10295
+ const deltaTrendClass = leaderboard_chart_module_default[DELTA_TREND_CLASS[showComparisonValue ? Math.sign(entry.delta) + 1 : 1]];
9466
10296
  const rowCells = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Stack, {
9467
10297
  direction: "column",
9468
10298
  gap: labelSpacing,
@@ -9483,13 +10313,11 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9483
10313
  children: [
9484
10314
  isPrimaryVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, { children: valueFormatter(entry.currentValue) }),
9485
10315
  showComparisonValue && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_ui.Text, {
9486
- className: leaderboard_chart_module_default.deltaValue,
9487
- style: { color: deltaColor },
10316
+ className: (0, clsx.default)(leaderboard_chart_module_default.deltaValue, deltaTrendClass),
9488
10317
  children: deltaFormatter(entry.delta)
9489
10318
  }),
9490
10319
  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 },
10320
+ className: (0, clsx.default)(leaderboard_chart_module_default.deltaValue, leaderboard_chart_module_default.deltaPlaceholder, deltaTrendClass),
9493
10321
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9494
10322
  "aria-hidden": "true",
9495
10323
  children: "—"
@@ -9573,7 +10401,9 @@ function RadialWipeAnimation({ id, radius, innerRadius = 0, durationMs = 1e3, wi
9573
10401
  //#endregion
9574
10402
  //#region src/charts/pie-chart/pie-chart.module.scss
9575
10403
  var pie_chart_module_default = {
9576
- "pie-chart": "a8ccharts-gnszbG-pie-chart",
10404
+ "pie-chart__label-plate": "a8ccharts-gnszbG-pie-chart__label-plate",
10405
+ "pie-chart__label-text": "a8ccharts-gnszbG-pie-chart__label-text",
10406
+ "pie-chart__plot": "a8ccharts-gnszbG-pie-chart__plot",
9577
10407
  "pie-chart--responsive": "a8ccharts-gnszbG-pie-chart--responsive"
9578
10408
  };
9579
10409
  //#endregion
@@ -9628,11 +10458,8 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9628
10458
  const providerTheme = useGlobalChartsTheme();
9629
10459
  const chartId = useChartId(providedChartId);
9630
10460
  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
- });
10461
+ const standaloneScopeClass = useStandaloneScopeClass();
10462
+ const containerRef = (0, react.useRef)(null);
9636
10463
  const onMouseLeave = (0, react.useCallback)(() => {
9637
10464
  if (!withTooltips) return;
9638
10465
  hideTooltip();
@@ -9715,19 +10542,7 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9715
10542
  width: propWidth || void 0,
9716
10543
  height: propHeight || void 0
9717
10544
  },
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
- ] }),
10545
+ trailingContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [htmlChildren, otherChildren] }),
9731
10546
  children: ({ contentWidth, contentHeight }) => {
9732
10547
  const availableSize = Math.min(contentWidth > 0 ? contentWidth : 300, contentHeight > 0 ? contentHeight : 300);
9733
10548
  const actualSize = size ? Math.min(size, availableSize) : availableSize;
@@ -9740,9 +10555,10 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9740
10555
  const innerRadius = thickness === 0 ? 0 : outerRadius * (1 - thickness);
9741
10556
  const maxCornerRadius = (outerRadius - innerRadius) / 2;
9742
10557
  const cornerRadius = cornerScale ? Math.min(cornerScale * outerRadius, maxCornerRadius) : 0;
9743
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
10558
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
9744
10559
  ref: containerRef,
9745
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10560
+ className: pie_chart_module_default["pie-chart__plot"],
10561
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
9746
10562
  viewBox: `0 0 ${width} ${height}`,
9747
10563
  preserveAspectRatio: "xMidYMid meet",
9748
10564
  width,
@@ -9774,11 +10590,12 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9774
10590
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= .25;
9775
10591
  const handleMouseMove = (event) => {
9776
10592
  if (!withTooltips) return;
9777
- if (containerBounds.width === 0 || containerBounds.height === 0) return;
10593
+ const bounds = containerRef.current?.getBoundingClientRect();
10594
+ if (!bounds) return;
9778
10595
  showTooltip({
9779
10596
  tooltipData: arc.data,
9780
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
9781
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY
10597
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
10598
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
9782
10599
  });
9783
10600
  };
9784
10601
  const pathProps = {
@@ -9801,20 +10618,20 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9801
10618
  const backgroundHeight = fontSize + 12;
9802
10619
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
9803
10620
  ...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", {
10621
+ 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", {
10622
+ className: pie_chart_module_default["pie-chart__label-plate"],
9805
10623
  x: centroidX - backgroundWidth / 2,
9806
10624
  y: centroidY - backgroundHeight / 2,
9807
10625
  width: backgroundWidth,
9808
10626
  height: backgroundHeight,
9809
- fill: providerTheme.labelBackgroundColor,
9810
10627
  rx: 4,
9811
10628
  ry: 4,
9812
10629
  pointerEvents: "none"
9813
10630
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
10631
+ className: pie_chart_module_default["pie-chart__label-text"],
9814
10632
  x: centroidX,
9815
10633
  y: centroidY,
9816
10634
  dy: ".33em",
9817
- fill: providerTheme.labelTextColor || "#333",
9818
10635
  fontSize,
9819
10636
  textAnchor: "middle",
9820
10637
  pointerEvents: "none",
@@ -9825,7 +10642,15 @@ const PieChartInternal = ({ data, chartId: providedChartId, withTooltips = false
9825
10642
  }
9826
10643
  }), !allSegmentsHidden && svgChildren]
9827
10644
  })]
9828
- })
10645
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
10646
+ top: tooltipTop || 0,
10647
+ left: tooltipLeft || 0,
10648
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10649
+ className: standaloneScopeClass,
10650
+ role: "tooltip",
10651
+ children: renderTooltip({ tooltipData })
10652
+ })
10653
+ })]
9829
10654
  });
9830
10655
  }
9831
10656
  })
@@ -9852,6 +10677,7 @@ var pie_semi_circle_chart_module_default = {
9852
10677
  "label": "a8ccharts-YtTOxW-label",
9853
10678
  "note": "a8ccharts-YtTOxW-note",
9854
10679
  "pie-semi-circle-chart": "a8ccharts-YtTOxW-pie-semi-circle-chart",
10680
+ "pie-semi-circle-chart__plot": "a8ccharts-YtTOxW-pie-semi-circle-chart__plot",
9855
10681
  "pie-semi-circle-chart--responsive": "a8ccharts-YtTOxW-pie-semi-circle-chart--responsive"
9856
10682
  };
9857
10683
  //#endregion
@@ -9901,23 +10727,17 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9901
10727
  const legendPosition = legend.position ?? "bottom";
9902
10728
  const chartId = useChartId(providedChartId);
9903
10729
  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
- });
10730
+ const standaloneScopeClass = useStandaloneScopeClass();
10731
+ const containerRef = (0, react.useRef)(null);
9909
10732
  const handleMouseMove = (0, react.useCallback)((event, arc) => {
9910
- if (containerBounds.width === 0 || containerBounds.height === 0) return;
10733
+ const bounds = containerRef.current?.getBoundingClientRect();
10734
+ if (!bounds) return;
9911
10735
  showTooltip({
9912
10736
  tooltipData: arc.data,
9913
- tooltipLeft: event.clientX - containerBounds.left + tooltipOffsetX,
9914
- tooltipTop: event.clientY - containerBounds.top + tooltipOffsetY
10737
+ tooltipLeft: event.clientX - bounds.left + tooltipOffsetX,
10738
+ tooltipTop: event.clientY - bounds.top + tooltipOffsetY
9915
10739
  });
9916
10740
  }, [
9917
- containerBounds.width,
9918
- containerBounds.height,
9919
- containerBounds.left,
9920
- containerBounds.top,
9921
10741
  showTooltip,
9922
10742
  tooltipOffsetX,
9923
10743
  tooltipOffsetY
@@ -9966,7 +10786,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
9966
10786
  const errorWidth = propHeight ? Math.min(propWidth || propHeight * 2, propHeight * 2) : effectiveWidth;
9967
10787
  const errorHeight = errorWidth / 2;
9968
10788
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9969
- className: pie_semi_circle_chart_module_default["pie-semi-circle-chart"],
10789
+ className: (0, clsx.default)("pie-semi-circle-chart", pie_semi_circle_chart_module_default["pie-semi-circle-chart"], className),
9970
10790
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
9971
10791
  width: errorWidth,
9972
10792
  height: errorHeight,
@@ -10013,19 +10833,7 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
10013
10833
  width: propWidth || void 0,
10014
10834
  height: propHeight || void 0
10015
10835
  },
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
- ] }),
10836
+ trailingContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [htmlChildren, otherChildren] }),
10029
10837
  children: ({ contentWidth, contentHeight }) => {
10030
10838
  const availableWidth = contentWidth > 0 ? contentWidth : effectiveWidth;
10031
10839
  const availableHeight = contentHeight > 0 ? contentHeight : propHeight || effectiveWidth / 2;
@@ -10033,9 +10841,10 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
10033
10841
  const height = width / 2;
10034
10842
  const radius = height;
10035
10843
  const innerRadius = radius * (1 - thickness);
10036
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Center, {
10844
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Center, {
10037
10845
  ref: containerRef,
10038
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10846
+ className: pie_semi_circle_chart_module_default["pie-semi-circle-chart__plot"],
10847
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10039
10848
  width,
10040
10849
  height,
10041
10850
  viewBox: `0 0 ${width} ${height}`,
@@ -10093,7 +10902,15 @@ const PieSemiCircleChartInternal = ({ data, chartId: providedChartId, width: pro
10093
10902
  !allSegmentsHidden && svgChildren
10094
10903
  ] })
10095
10904
  })]
10096
- })
10905
+ }), withTooltips && tooltipOpen && tooltipData && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundedTooltip, {
10906
+ top: tooltipTop || 0,
10907
+ left: tooltipLeft || 0,
10908
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10909
+ className: standaloneScopeClass,
10910
+ role: "tooltip",
10911
+ children: renderTooltip({ tooltipData })
10912
+ })
10913
+ })]
10097
10914
  });
10098
10915
  }
10099
10916
  })
@@ -10351,6 +11168,7 @@ exports.buildCalendarHeatmapData = buildCalendarHeatmapData;
10351
11168
  exports.defaultTheme = defaultTheme;
10352
11169
  exports.formatMetricValue = formatMetricValue;
10353
11170
  exports.formatPercentage = formatPercentage;
11171
+ exports.getBucketInfo = getBucketInfo;
10354
11172
  exports.getColorDistance = getColorDistance;
10355
11173
  exports.hexToRgba = hexToRgba;
10356
11174
  exports.isValidHexColor = isValidHexColor;
@@ -10360,10 +11178,11 @@ exports.mixHexColors = mixHexColors;
10360
11178
  exports.normalizeColorToHex = normalizeColorToHex;
10361
11179
  exports.parseAsLocalDate = parseAsLocalDate;
10362
11180
  exports.parseHslString = parseHslString;
10363
- exports.parseRgbString = parseRgbString;
10364
11181
  exports.prefersLightText = prefersLightText;
10365
11182
  exports.relativeLuminance = relativeLuminance;
10366
11183
  exports.resolveCssVariable = resolveCssVariable;
11184
+ exports.useCalendarHeatmapData = useCalendarHeatmapData;
11185
+ exports.useChartFormatting = useChartFormatting;
10367
11186
  exports.useChartLegendItems = useChartLegendItems;
10368
11187
  exports.useChartRegistration = useChartRegistration;
10369
11188
  exports.useChartScopeElement = useChartScopeElement;