@automattic/charts 4.0.0 → 4.1.1

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 (61) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/SECURITY.md +1 -0
  3. package/TOKENS.md +7 -5
  4. package/dist/index.cjs +614 -259
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.css +30 -0
  7. package/dist/index.d.cts +88 -74
  8. package/dist/index.d.ts +88 -74
  9. package/dist/index.js +615 -261
  10. package/dist/index.js.map +1 -1
  11. package/package.json +5 -4
  12. package/src/charts/area-chart/area-chart.tsx +2 -1
  13. package/src/charts/area-chart/test/area-chart.test.tsx +31 -0
  14. package/src/charts/heatmap-chart/build-calendar-data.ts +189 -0
  15. package/src/charts/heatmap-chart/heatmap-chart.module.scss +31 -0
  16. package/src/charts/heatmap-chart/heatmap-chart.tsx +44 -9
  17. package/src/charts/heatmap-chart/index.ts +10 -3
  18. package/src/charts/heatmap-chart/private/civil-day.ts +174 -0
  19. package/src/charts/heatmap-chart/private/index.ts +0 -2
  20. package/src/charts/heatmap-chart/private/use-heatmap-colors.ts +5 -1
  21. package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +4 -1
  22. package/src/charts/heatmap-chart/test/civil-day.test.ts +70 -0
  23. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +91 -0
  24. package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +118 -0
  25. package/src/charts/heatmap-chart/test/use-heatmap-colors.test.ts +8 -0
  26. package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +248 -0
  27. package/src/charts/heatmap-chart/types.ts +35 -0
  28. package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +42 -0
  29. package/src/charts/leaderboard-chart/types.ts +2 -2
  30. package/src/charts/line-chart/line-chart.tsx +41 -7
  31. package/src/charts/line-chart/test/crosshair-style-types.test.ts +31 -0
  32. package/src/charts/line-chart/test/line-chart.test.tsx +178 -38
  33. package/src/charts/line-chart/test/zoned-date-string.test.tsx +37 -0
  34. package/src/charts/line-chart/types.ts +13 -2
  35. package/src/charts/pie-chart/test/pie-chart.test.tsx +3 -12
  36. package/src/components/tooltip/accessible-tooltip.tsx +40 -12
  37. package/src/components/tooltip/private/bounded-tooltip.tsx +71 -16
  38. package/src/components/tooltip/test/accessible-tooltip.test.tsx +60 -1
  39. package/src/components/tooltip/test/bounded-tooltip.test.tsx +102 -6
  40. package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +122 -3
  41. package/src/components/tooltip/xy-chart-tooltip.tsx +53 -14
  42. package/src/hooks/test/use-chart-data-transform-zone.test.tsx +36 -0
  43. package/src/hooks/test/use-chart-margin.test.tsx +173 -3
  44. package/src/hooks/use-chart-data-transform.ts +10 -3
  45. package/src/hooks/use-chart-margin.tsx +54 -9
  46. package/src/index.ts +4 -0
  47. package/src/styles/chart-scope.scss +11 -3
  48. package/src/styles/test/chart-scope.test.ts +35 -1
  49. package/src/test-utils/zoned-parsing-suite.ts +90 -0
  50. package/src/types.ts +2 -2
  51. package/src/utils/date-formatting.ts +1 -18
  52. package/src/utils/date-parsing.ts +103 -52
  53. package/src/utils/get-edge-tick-widths.ts +33 -0
  54. package/src/utils/index.ts +1 -0
  55. package/src/utils/test/date-parsing.auckland.test.ts +6 -0
  56. package/src/utils/test/date-parsing.santiago.test.ts +6 -0
  57. package/src/utils/test/date-parsing.test.ts +16 -1
  58. package/src/utils/test/get-edge-tick-widths.test.ts +55 -0
  59. package/src/utils/warn-once.ts +23 -0
  60. package/src/visx/types.ts +27 -3
  61. package/src/charts/heatmap-chart/private/build-calendar-data.ts +0 -179
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@ import clsx from "clsx";
6
6
  import * as React from "react";
7
7
  import { Children, Fragment, createContext, createContext as createContext$1, createElement, forwardRef, forwardRef as forwardRef$1, isValidElement, memo, useCallback, useCallback as useCallback$1, useContext, useContext as useContext$1, useEffect, useEffect as useEffect$1, useId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useRef, useRef as useRef$1, useState, useState as useState$1 } from "react";
8
8
  import { color, hsl } from "@visx/vendor/d3-color";
9
- import { addDays, differenceInCalendarWeeks, differenceInHours, differenceInYears, format, isValid, parse, parseISO, startOfWeek } from "date-fns";
9
+ import { differenceInHours, differenceInYears, isValid, parse, parseISO } from "date-fns";
10
+ import { tzOffset } from "@date-fns/tz";
10
11
  import { Text, getStringWidth } from "@visx/text";
11
12
  import deepmerge from "deepmerge";
12
13
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -84,46 +85,35 @@ function attachSubComponents(Chart, subComponents) {
84
85
  return Object.assign(Chart, subComponents);
85
86
  }
86
87
  //#endregion
87
- //#region src/utils/date-parsing.ts
88
+ //#region src/utils/warn-once.ts
89
+ const warned = /* @__PURE__ */ new Set();
88
90
  /**
89
- * @file Date parsing utilities using date-fns for local timezone handling
90
- *
91
- * This module provides utilities for parsing various date string formats and converting
92
- * them to local timezone dates using the battle-tested date-fns library. For formats
93
- * without timezone info, they're treated as local. For formats with timezone info,
94
- * they're converted to the equivalent local time.
91
+ * Warns about a host-supplied value once per process, and never in production.
95
92
  *
96
- * Note: And specifically it prevents format `YYYY-MM-DD` being parsed as UTC date.
97
- *
98
- * Key Features:
99
- * - All parsed dates are in local timezone
100
- * - Converts timezone-aware strings to local equivalent
101
- * - Robust input validation and error handling using date-fns
102
- * - TypeScript type safety
103
- * - Much smaller codebase than custom parsing
93
+ * @param key - Identifies the value, so a bad prop on every point warns once.
94
+ * @param message - What the host got wrong and what happens instead.
95
+ */
96
+ const warnOnce = (key, message) => {
97
+ if (warned.has(key) || process.env.NODE_ENV === "production") return;
98
+ warned.add(key);
99
+ console.warn(`[Charts] ${message}`);
100
+ };
101
+ //#endregion
102
+ //#region src/utils/date-parsing.ts
103
+ /**
104
+ * @file Date parsing: a naive string is dated in a supplied IANA zone, or the runtime's own
104
105
  *
105
- * Supported Formats:
106
- * - YYYY-MM-DD (treated as local)
107
- * - YYYY-MM-DD HH:mm:ss (treated as local)
108
- * - YYYY-MM-DD HH:mm (treated as local)
109
- * - YYYY-MM-DDTHH:mm:ss (treated as local)
110
- * - YYYY-MM-DDTHH:mm:ss.SSS (treated as local)
111
- * - YYYY-MM-DDTHH:mm (treated as local)
112
- * - YYYY-MM-DDTHH:mm:ssZ (converted to local)
113
- * - YYYY-MM-DDTHH:mm:ss±HH:mm (converted to local)
106
+ * A string carrying an offset is already an instant and parses the same everywhere. A string
107
+ * without one is only a wall-clock reading, so it means nothing until a zone is named. See
108
+ * `parseAsLocalDate` for the supported formats.
114
109
  *
115
- * @example
116
- * ```typescript
117
- * parseAsLocalDate("2025-01-01"); // Local timezone
118
- * parseAsLocalDate("2025-01-01 14:30:00"); // Local timezone
119
- * parseAsLocalDate("2025-01-01 14:30"); // Local timezone
120
- * parseAsLocalDate("2025-01-01T14:30:45.123"); // Local timezone
121
- * parseAsLocalDate("2025-01-01T14:30:00Z"); // UTC 14:30 → Local equivalent
122
- * parseAsLocalDate("2025-01-01T14:30:00+05:00"); // +05:00 14:30 → Local equivalent
123
- * ```
110
+ * Note: this specifically avoids date-fns's default of parsing `YYYY-MM-DD` as a UTC date.
124
111
  */
125
112
  /**
126
113
  * Checks if a date string contains timezone information
114
+ *
115
+ * The minutes are optional because ISO 8601 allows a bare `±hh`, which `parseISO` reads.
116
+ *
127
117
  * @param {string} dateString - The date string to check for timezone information
128
118
  * @return {boolean} True if the date string contains timezone information, false otherwise
129
119
  */
@@ -131,28 +121,57 @@ const hasTimezone = (dateString) => {
131
121
  const tIndex = dateString.indexOf("T");
132
122
  if (tIndex === -1) return false;
133
123
  if (dateString.endsWith("Z")) return true;
134
- return /[+-]\d{2}:?\d{2}$/.test(dateString.slice(tIndex + 1));
124
+ return /[+-]\d{2}(?::?\d{2})?$/.test(dateString.slice(tIndex + 1));
125
+ };
126
+ const NAIVE = /^(\d{1,4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,3}))?)?)?$/;
127
+ const asUtcMs = ([, ...fields]) => {
128
+ const [year, month, day, hour, minute, second, ms] = fields.map((field) => Number(field ?? 0));
129
+ const date = /* @__PURE__ */ new Date(0);
130
+ date.setUTCFullYear(year, month - 1, day);
131
+ date.setUTCHours(hour, minute, second, ms);
132
+ return date.getTime();
135
133
  };
136
134
  /**
137
- * Parses any supported date string format and returns a local timezone date
135
+ * The instant a wall-clock reading names in a given zone.
138
136
  *
139
- * Uses date-fns for robust parsing and validation. For strings without timezone
140
- * info, treats as local timezone. For strings with timezone info, converts to
141
- * local timezone equivalent.
137
+ * A reading a spring-forward gap deleted moves forward past the gap, and one a fall-back
138
+ * repeated resolves to a single instant. Neither is configurable: see CHARTS-268.
142
139
  *
143
- * Supports:
144
- * - YYYY-MM-DD (local)
145
- * - YYYY-MM-DD HH:mm:ss (local)
146
- * - YYYY-MM-DD HH:mm (local)
147
- * - YYYY-MM-DDTHH:mm:ss (local)
148
- * - YYYY-MM-DDTHH:mm:ss.SSS (local)
149
- * - YYYY-MM-DDTHH:mm (local)
150
- * - YYYY-MM-DDTHH:mm:ssZ (UTC → local)
151
- * - YYYY-MM-DDTHH:mm:ss±HH:mm (offset → local)
152
- * @param {string} dateString - The date string to parse into a local timezone date
153
- * @return {Date} A Date object representing the parsed date in local timezone, or an invalid Date if parsing fails
140
+ * @param wallClock - The reading, as the milliseconds it would be if it were UTC.
141
+ * @param timeZone - IANA zone the reading is dated in.
142
+ * @return The instant, or `null` where `timeZone` is not a zone Intl accepts.
154
143
  */
155
- const parseAsLocalDate = (dateString) => {
144
+ const wallClockToInstant = (wallClock, timeZone) => {
145
+ const first = tzOffset(timeZone, new Date(wallClock));
146
+ if (Number.isNaN(first)) {
147
+ 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".`);
148
+ return null;
149
+ }
150
+ const guess = wallClock - first * 6e4;
151
+ const second = tzOffset(timeZone, new Date(guess));
152
+ if (first === second) return new Date(guess);
153
+ const corrected = wallClock - second * 6e4;
154
+ const third = tzOffset(timeZone, new Date(corrected));
155
+ if (second === third) return new Date(corrected);
156
+ return /* @__PURE__ */ new Date(wallClock - Math.min(second, third) * 6e4);
157
+ };
158
+ /**
159
+ * Parses any supported date string format into an instant, dated in `timeZone` or the runtime's own
160
+ *
161
+ * Uses date-fns for robust parsing and validation. A string carrying timezone info
162
+ * is already an instant and is returned as one, whatever `timeZone` says. A string
163
+ * without it is a wall-clock reading, dated in `timeZone` when one is supplied and
164
+ * in the runtime's own zone when none is.
165
+ *
166
+ * A wall clock a DST gap deleted is moved forward past the gap; one a fall-back repeated
167
+ * resolves to a single instant. Neither policy is selectable. A zone `Intl` rejects falls
168
+ * back to the runtime's own, and warns once outside production.
169
+ *
170
+ * @param {string} dateString - The date string to parse into a date
171
+ * @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
172
+ * @return {Date} A Date object representing the parsed instant, or an invalid Date if parsing fails
173
+ */
174
+ const parseAsLocalDate = (dateString, timeZone) => {
156
175
  const trimmedString = dateString.trim();
157
176
  if (hasTimezone(trimmedString)) {
158
177
  const isoDate = parseISO(trimmedString);
@@ -168,7 +187,10 @@ const parseAsLocalDate = (dateString) => {
168
187
  "yyyy-MM-dd'T'HH:mm"
169
188
  ]) {
170
189
  const result = parse(trimmedString, format, /* @__PURE__ */ new Date());
171
- if (isValid(result)) return result;
190
+ if (!isValid(result)) continue;
191
+ if (!timeZone) return result;
192
+ const fields = NAIVE.exec(trimmedString);
193
+ return (fields && wallClockToInstant(asUtcMs(fields), timeZone)) ?? result;
172
194
  }
173
195
  return /* @__PURE__ */ new Date(NaN);
174
196
  };
@@ -273,6 +295,31 @@ const formatPercentage = (value) => {
273
295
  } });
274
296
  };
275
297
  //#endregion
298
+ //#region src/utils/get-edge-tick-widths.ts
299
+ /**
300
+ * Rendered widths of the first and last tick labels on an axis.
301
+ *
302
+ * An unmeasurable label reserves nothing, which is what a width of 0 already
303
+ * means to every caller, so it is reported as 0 rather than as its own case.
304
+ *
305
+ * @param ticks - Tick values, in axis order.
306
+ * @param formatTick - Function to format a tick.
307
+ * @param {object} labelStyle - Style object for the label.
308
+ * @return {object} - Widths in pixels.
309
+ */
310
+ const getEdgeTickWidths = (ticks, formatTick, labelStyle) => {
311
+ if (!ticks.length) return {
312
+ first: 0,
313
+ last: 0
314
+ };
315
+ const lastIndex = ticks.length - 1;
316
+ const label = (tick, index) => String(formatTick ? formatTick(tick, index, []) ?? "" : tick);
317
+ return {
318
+ first: getStringWidth(label(ticks[0], 0), labelStyle) ?? 0,
319
+ last: getStringWidth(label(ticks[lastIndex], lastIndex), labelStyle) ?? 0
320
+ };
321
+ };
322
+ //#endregion
276
323
  //#region src/utils/get-longest-tick-width.ts
277
324
  /**
278
325
  * Returns the width of the longest tick.
@@ -659,12 +706,6 @@ const getFormatter$1 = (options, { locale, timeZone }) => {
659
706
  }
660
707
  return formatter;
661
708
  };
662
- const warned = /* @__PURE__ */ new Set();
663
- const warnOnce = (key, message) => {
664
- if (warned.has(key) || process.env.NODE_ENV === "production") return;
665
- warned.add(key);
666
- console.warn(`[Charts] ${message}`);
667
- };
668
709
  const isUsable = (formatting) => {
669
710
  try {
670
711
  Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
@@ -1290,13 +1331,28 @@ const useXYChartTheme = (data) => {
1290
1331
  ]);
1291
1332
  };
1292
1333
  //#endregion
1334
+ //#region src/providers/chart-context/hooks/use-chart-formatting.ts
1335
+ const RUNTIME_FORMATTING = {};
1336
+ /**
1337
+ * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
1338
+ *
1339
+ * Falls back to the runtime's own rather than throwing: charts render outside a
1340
+ * provider, and a host that sets neither gets exactly the browser-default
1341
+ * behavior this package had before the context existed.
1342
+ *
1343
+ * @return The host's formatting context.
1344
+ */
1345
+ const useChartFormatting = () => {
1346
+ return useContext(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
1347
+ };
1348
+ //#endregion
1293
1349
  //#region src/hooks/use-chart-data-transform.ts
1294
1350
  /**
1295
1351
  * Hook that transforms and sorts chart data, handling date parsing and sorting
1296
1352
  *
1297
1353
  * This hook extracts the common data transformation logic used in both line-chart
1298
1354
  * and bar-chart components. It:
1299
- * 1. Parses date strings into Date objects using parseAsLocalDate
1355
+ * 1. Parses date strings into Date objects, dated in the provider's time zone
1300
1356
  * 2. Sorts data points by date when date properties are present
1301
1357
  * 3. Returns the original data unchanged when no date properties are found
1302
1358
  *
@@ -1304,6 +1360,7 @@ const useXYChartTheme = (data) => {
1304
1360
  * @return {SeriesData[]} The transformed and sorted data
1305
1361
  */
1306
1362
  const useChartDataTransform = (data) => {
1363
+ const { timeZone } = useChartFormatting();
1307
1364
  return useMemo(() => {
1308
1365
  const firstPoint = data?.[0]?.data?.[0];
1309
1366
  if (!(firstPoint && ("date" in firstPoint || "dateString" in firstPoint))) return data;
@@ -1312,7 +1369,7 @@ const useChartDataTransform = (data) => {
1312
1369
  data: series.data.map((point) => {
1313
1370
  let date;
1314
1371
  if ("date" in point && point.date) date = point.date;
1315
- else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString);
1372
+ else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString, timeZone);
1316
1373
  return {
1317
1374
  ...point,
1318
1375
  date
@@ -1322,7 +1379,7 @@ const useChartDataTransform = (data) => {
1322
1379
  return a.date.getTime() - b.date.getTime();
1323
1380
  })
1324
1381
  }));
1325
- }, [data]);
1382
+ }, [data, timeZone]);
1326
1383
  };
1327
1384
  //#endregion
1328
1385
  //#region src/hooks/use-chart-margin.tsx
@@ -1364,11 +1421,32 @@ const DEFAULT_TICK_LENGTH = 8;
1364
1421
  * measure them via getLongestTickWidth.
1365
1422
  */
1366
1423
  const DEFAULT_Y_TICK_WIDTH = 40;
1424
+ /**
1425
+ * Copy a label style with its lengths spelled out in px.
1426
+ *
1427
+ * `getStringWidth` applies the style through CSSOM. Blink resolves a bare number
1428
+ * on an SVG `<text>`, but that is its own leniency rather than the CSS rule, and
1429
+ * `buildChartTheme` hands us bare numbers.
1430
+ *
1431
+ * @param style - Raw label style from the theme.
1432
+ * @return The same style with px-qualified lengths.
1433
+ */
1434
+ const toMeasurableStyle = (style) => {
1435
+ if (!style) return style;
1436
+ const fontSize = resolveFontSize(style.fontSize);
1437
+ const { letterSpacing } = style;
1438
+ return {
1439
+ ...style,
1440
+ ...fontSize === void 0 ? {} : { fontSize: `${fontSize}px` },
1441
+ ...typeof letterSpacing === "number" ? { letterSpacing: `${letterSpacing}px` } : {}
1442
+ };
1443
+ };
1367
1444
  const getXAxisLabelMetrics = (theme, orientation) => {
1368
1445
  const xAxisStyles = orientation === "top" ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
1369
1446
  return {
1370
1447
  fontSize: resolveFontSize(xAxisStyles?.axisLabel?.fontSize) || resolveFontSize(theme.svgLabelSmall?.fontSize) || DEFAULT_FONT_SIZE,
1371
- tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH
1448
+ tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH,
1449
+ tickLabelStyle: toMeasurableStyle(xAxisStyles?.tickLabel)
1372
1450
  };
1373
1451
  };
1374
1452
  const useChartMargin = (height, options, data, theme, horizontal = false) => {
@@ -1380,7 +1458,7 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1380
1458
  const maxY = Math.max(...allDataPoints.map((d) => d.value));
1381
1459
  const yScale = createScale({
1382
1460
  ...options.yScale,
1383
- domain: [minY, maxY],
1461
+ domain: options.yScale?.domain ?? [minY, maxY],
1384
1462
  range: [height, 0]
1385
1463
  });
1386
1464
  return getTicks(yScale, options.axis?.y?.numTicks);
@@ -1399,18 +1477,25 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1399
1477
  };
1400
1478
  const yAxisOrientation = options.axis?.y?.orientation;
1401
1479
  const yAxisStyles = yAxisOrientation === "right" ? theme.axisStyles.y.right : theme.axisStyles.y.left;
1402
- const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, yAxisStyles.axisLabel);
1480
+ const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, toMeasurableStyle(yAxisStyles.axisLabel));
1403
1481
  const yTickLabelFontSize = resolveFontSize(yAxisStyles?.tickLabel?.fontSize) || DEFAULT_FONT_SIZE;
1404
1482
  const yMarginValue = (yTickWidth ?? DEFAULT_Y_TICK_WIDTH) + (yAxisStyles?.tickLength ?? 0) + Math.ceil(yTickLabelFontSize * .25);
1405
- if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1406
- else defaultMargin.left = yMarginValue;
1483
+ if (options.axis?.y?.display !== false) {
1484
+ if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1485
+ else defaultMargin.left = yMarginValue;
1486
+ }
1407
1487
  const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1408
- const { fontSize, tickLength } = getXAxisLabelMetrics(theme, xOrientation);
1488
+ const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1409
1489
  const computedXMargin = fontSize + tickLength;
1410
1490
  if (xOrientation === "top") {
1411
1491
  defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1412
1492
  defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
1413
1493
  } else defaultMargin.bottom = Math.max(defaultMargin.bottom, computedXMargin);
1494
+ if (options.axis?.x?.display !== false) {
1495
+ const { first, last } = getEdgeTickWidths(options.axis?.x?.tickValues ?? [], options.axis?.x?.tickFormat, tickLabelStyle);
1496
+ defaultMargin.left = Math.max(defaultMargin.left, Math.ceil(first / 2));
1497
+ defaultMargin.right = Math.max(defaultMargin.right, Math.ceil(last / 2));
1498
+ }
1414
1499
  return defaultMargin;
1415
1500
  }, [
1416
1501
  options,
@@ -1686,21 +1771,6 @@ const useGlobalChartsTheme = () => {
1686
1771
  return useContext(GlobalChartsContext)?.theme ?? defaultTheme;
1687
1772
  };
1688
1773
  //#endregion
1689
- //#region src/providers/chart-context/hooks/use-chart-formatting.ts
1690
- const RUNTIME_FORMATTING = {};
1691
- /**
1692
- * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
1693
- *
1694
- * Falls back to the runtime's own rather than throwing: charts render outside a
1695
- * provider, and a host that sets neither gets exactly the browser-default
1696
- * behavior this package had before the context existed.
1697
- *
1698
- * @return The host's formatting context.
1699
- */
1700
- const useChartFormatting = () => {
1701
- return useContext(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
1702
- };
1703
- //#endregion
1704
1774
  //#region src/components/legend/utils/value-or-identity.ts
1705
1775
  /**
1706
1776
  * Returns an object's value if defined, or the object.
@@ -2140,6 +2210,8 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2140
2210
  //#endregion
2141
2211
  //#region src/components/tooltip/private/bounded-tooltip.tsx
2142
2212
  const DEFAULT_OFFSET = 10;
2213
+ const POINTER_HEIGHT = 6;
2214
+ const clamp = (position, min, max, size) => Math.min(Math.max(position, min), Math.max(min, max - size));
2143
2215
  const isClipping = (element) => {
2144
2216
  const { overflow, overflowX, overflowY } = getComputedStyle(element);
2145
2217
  return [
@@ -2157,33 +2229,49 @@ const findClippingAncestor = (wrapper) => {
2157
2229
  return null;
2158
2230
  };
2159
2231
  /**
2160
- * Where the box goes, in wrapper coordinates. Below and to the right of the
2161
- * anchor; flipped to the other side when that side clips less, as visx's
2162
- * `TooltipWithBounds` decides it; then clamped so the box stays inside
2163
- * `bounds` whenever it fits at all.
2232
+ * Where the box goes, in wrapper coordinates. Automatic placement flips to
2233
+ * the side that clips less, then clamps inside `bounds`; below-axis placement
2234
+ * is centered on the anchor and clamped horizontally.
2235
+ *
2236
+ * The flip measures against the visible part of the wrapper, its intersection
2237
+ * with `bounds`. Only the clamp is bound by the page: a box that fits inside the
2238
+ * chart on one side stays there, so it does not reach past the chart into
2239
+ * siblings that paint over it.
2164
2240
  *
2165
- * @param params - Anchor, offsets, the box size and the bounds to keep inside.
2241
+ * @param params - Anchor, offsets, the box size and the rects to keep inside.
2166
2242
  * @param params.left - Anchor x, in wrapper coordinates.
2167
2243
  * @param params.top - Anchor y, in wrapper coordinates.
2168
2244
  * @param params.offsetLeft - Gap between the anchor and the box, horizontally.
2169
2245
  * @param params.offsetTop - Gap between the anchor and the box, vertically.
2170
2246
  * @param params.box - Rendered size of the box.
2247
+ * @param params.wrapper - The wrapper's own edges, in wrapper coordinates.
2248
+ * @param params.placement - Fixed below-axis placement or automatic flipping.
2171
2249
  * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2172
- * @return The box's top-left corner, rounded to whole pixels.
2250
+ * @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
2173
2251
  */
2174
- const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) => {
2252
+ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, wrapper, bounds, placement = "auto" }) => {
2253
+ if (placement === "below-axis") return {
2254
+ x: Math.round(clamp(left - box.width / 2, bounds.left, bounds.right, box.width)),
2255
+ y: top + POINTER_HEIGHT
2256
+ };
2257
+ const fit = {
2258
+ left: Math.max(wrapper.left, bounds.left),
2259
+ top: Math.max(wrapper.top, bounds.top),
2260
+ right: Math.min(wrapper.right, bounds.right),
2261
+ bottom: Math.min(wrapper.bottom, bounds.bottom)
2262
+ };
2175
2263
  const rightX = left + offsetLeft;
2176
2264
  const leftX = left - offsetLeft - box.width;
2177
- const rightOverflow = rightX + box.width - bounds.right;
2178
- const leftOverflow = bounds.left - leftX;
2265
+ const rightOverflow = rightX + box.width - fit.right;
2266
+ const leftOverflow = fit.left - leftX;
2179
2267
  let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
2180
2268
  const downY = top + offsetTop;
2181
2269
  const upY = top - offsetTop - box.height;
2182
- const downOverflow = downY + box.height - bounds.bottom;
2183
- const upOverflow = bounds.top - upY;
2270
+ const downOverflow = downY + box.height - fit.bottom;
2271
+ const upOverflow = fit.top - upY;
2184
2272
  let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2185
- x = Math.min(Math.max(x, bounds.left), Math.max(bounds.left, bounds.right - box.width));
2186
- y = Math.min(Math.max(y, bounds.top), Math.max(bounds.top, bounds.bottom - box.height));
2273
+ x = clamp(x, bounds.left, bounds.right, box.width);
2274
+ y = clamp(y, bounds.top, bounds.bottom, box.height);
2187
2275
  return {
2188
2276
  x: Math.round(x),
2189
2277
  y: Math.round(y)
@@ -2197,7 +2285,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
2197
2285
  * alone would let the box spill into an `overflow: hidden` card and be cut off.
2198
2286
  *
2199
2287
  * Re-measures on every render, so a box whose content changes width between
2200
- * two hovers is placed for its current size.
2288
+ * two hovers is placed for its current size. See `getBoundedPosition` for the
2289
+ * below-axis exception.
2201
2290
  *
2202
2291
  * @param props - visx `Tooltip` props.
2203
2292
  * @param props.left - Anchor x, in wrapper coordinates.
@@ -2207,9 +2296,10 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
2207
2296
  * @param props.style - Box styles; visx's defaults unless `unstyled`.
2208
2297
  * @param props.unstyled - Skip `style` and leave the box bare.
2209
2298
  * @param props.children - Box content.
2299
+ * @param props.placement - Fixed below-axis placement or automatic flipping.
2210
2300
  * @return The tooltip box.
2211
2301
  */
2212
- const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, ...rest }) => {
2302
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2213
2303
  const nodeRef = useRef(null);
2214
2304
  const clipRef = useRef(null);
2215
2305
  const [position, setPosition] = useState(null);
@@ -2234,10 +2324,17 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2234
2324
  top,
2235
2325
  offsetLeft,
2236
2326
  offsetTop,
2327
+ placement,
2237
2328
  box: {
2238
2329
  width: own.width,
2239
2330
  height: own.height
2240
2331
  },
2332
+ wrapper: {
2333
+ left: 0,
2334
+ top: 0,
2335
+ right: wrapperRect.width,
2336
+ bottom: wrapperRect.height
2337
+ },
2241
2338
  bounds: {
2242
2339
  left: clipRect.left - wrapperRect.left,
2243
2340
  top: clipRect.top - wrapperRect.top,
@@ -2251,11 +2348,12 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2251
2348
  top,
2252
2349
  offsetLeft,
2253
2350
  offsetTop,
2254
- children
2351
+ children,
2352
+ placement
2255
2353
  ]);
2256
2354
  const x = position?.x ?? left + offsetLeft;
2257
- const y = position?.y ?? top + offsetTop;
2258
- return /* @__PURE__ */ jsx(Tooltip, {
2355
+ const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2356
+ return /* @__PURE__ */ jsxs(Tooltip, {
2259
2357
  ref: nodeRef,
2260
2358
  style: {
2261
2359
  position: "absolute",
@@ -2265,12 +2363,40 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2265
2363
  ...!unstyled && style
2266
2364
  },
2267
2365
  ...rest,
2268
- children
2366
+ children: [placement === "below-axis" && /* @__PURE__ */ jsx("span", {
2367
+ "aria-hidden": "true",
2368
+ style: {
2369
+ position: "absolute",
2370
+ left: left - x - POINTER_HEIGHT,
2371
+ top: -6,
2372
+ width: 12,
2373
+ height: POINTER_HEIGHT,
2374
+ background: "inherit",
2375
+ clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
2376
+ pointerEvents: "none"
2377
+ }
2378
+ }), children]
2269
2379
  });
2270
2380
  };
2271
2381
  //#endregion
2272
2382
  //#region src/components/tooltip/xy-chart-tooltip.tsx
2273
2383
  const CROSSHAIR_STROKE_WIDTH = 1.5;
2384
+ const CROSSHAIR_PAINT_PROPERTIES = /* @__PURE__ */ new Set([
2385
+ "stroke",
2386
+ "strokeWidth",
2387
+ "strokeOpacity",
2388
+ "strokeDasharray",
2389
+ "strokeLinecap",
2390
+ "opacity"
2391
+ ]);
2392
+ const crosshairPaintProps = (props = {}) => {
2393
+ const pickPaint = (value) => Object.fromEntries(Object.entries(value).filter(([key]) => CROSSHAIR_PAINT_PROPERTIES.has(key)));
2394
+ return {
2395
+ ...pickPaint(props),
2396
+ ...props.className === void 0 ? {} : { className: props.className },
2397
+ ...props.style === void 0 ? {} : { style: pickPaint(props.style) }
2398
+ };
2399
+ };
2274
2400
  const DEFAULT_GLYPH_RADIUS = 4;
2275
2401
  const FALLBACK_COLOR = "#222";
2276
2402
  const DEFAULT_TOOLTIP_Z_INDEX = 3;
@@ -2301,7 +2427,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ jsx(DefaultG
2301
2427
  seriesKey: key,
2302
2428
  ...props
2303
2429
  }, key);
2304
- const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, renderGlyph = defaultRenderGlyph$1, glyphStyle, snapTooltipToDatumX = false, snapTooltipToDatumY = false, showVerticalCrosshair = false, showHorizontalCrosshair = false, showDatumGlyph = false, showSeriesGlyphs = false, verticalCrosshairStyle, horizontalCrosshairStyle, detectBounds = true, zIndex = DEFAULT_TOOLTIP_Z_INDEX, ...rest }) => {
2430
+ 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 }) => {
2305
2431
  const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2306
2432
  const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = useContext(DataContext) || {};
2307
2433
  const tooltipContent = renderTooltip ? renderTooltip({
@@ -2320,9 +2446,9 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2320
2446
  };
2321
2447
  const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
2322
2448
  let { tooltipLeft, tooltipTop } = tooltipContext;
2323
- if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY)) {
2449
+ if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY || tooltipPlacement === "below-axis")) {
2324
2450
  const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
2325
- if (snapTooltipToDatumX && isValidNumber(left)) tooltipLeft = left;
2451
+ if ((snapTooltipToDatumX || tooltipPlacement === "below-axis") && isValidNumber(left)) tooltipLeft = left;
2326
2452
  if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
2327
2453
  }
2328
2454
  const glyphs = [];
@@ -2356,13 +2482,14 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2356
2482
  const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2357
2483
  const marginTop = margin?.top ?? 0;
2358
2484
  const marginLeft = margin?.left ?? 0;
2359
- const TooltipComponent = detectBounds ? BoundedTooltip : Tooltip;
2485
+ const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : Tooltip;
2360
2486
  const boxStyle = {
2361
2487
  ...defaultStyles,
2362
2488
  zIndex,
2363
- background: theme?.backgroundColor ?? "white",
2364
- boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2365
- ...theme?.htmlLabel
2489
+ backgroundColor: theme?.backgroundColor ?? "white",
2490
+ boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2491
+ ...theme?.htmlLabel,
2492
+ ...style
2366
2493
  };
2367
2494
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("g", {
2368
2495
  className: "visx-tooltip-overlay",
@@ -2370,32 +2497,33 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2370
2497
  children: [
2371
2498
  showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ jsx("line", {
2372
2499
  className: "visx-crosshair visx-crosshair-vertical",
2500
+ stroke: crosshairStroke,
2501
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2502
+ ...crosshairPaintProps(verticalCrosshairStyle),
2373
2503
  x1: tooltipLeft,
2374
2504
  x2: tooltipLeft,
2375
2505
  y1: marginTop,
2376
- y2: marginTop + innerHeight,
2377
- stroke: crosshairStroke,
2378
- strokeWidth: CROSSHAIR_STROKE_WIDTH,
2379
- ...verticalCrosshairStyle
2506
+ y2: marginTop + innerHeight
2380
2507
  }),
2381
2508
  showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ jsx("line", {
2382
2509
  className: "visx-crosshair visx-crosshair-horizontal",
2510
+ stroke: crosshairStroke,
2511
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2512
+ ...crosshairPaintProps(horizontalCrosshairStyle),
2383
2513
  x1: marginLeft,
2384
2514
  x2: marginLeft + innerWidth,
2385
2515
  y1: tooltipTop,
2386
- y2: tooltipTop,
2387
- stroke: crosshairStroke,
2388
- strokeWidth: CROSSHAIR_STROKE_WIDTH,
2389
- ...horizontalCrosshairStyle
2516
+ y2: tooltipTop
2390
2517
  }),
2391
2518
  glyphs
2392
2519
  ]
2393
2520
  }), container && createPortal(/* @__PURE__ */ jsx(TooltipComponent, {
2394
2521
  left: tooltipLeft,
2395
- top: tooltipTop,
2522
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
2396
2523
  style: boxStyle,
2397
2524
  applyPositionStyle: true,
2398
2525
  ...tooltipProps,
2526
+ ...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
2399
2527
  children: tooltipContent
2400
2528
  }), container)] });
2401
2529
  };
@@ -2416,7 +2544,8 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2416
2544
  * isolation keeps the box's `zIndex` from competing with page chrome. The box
2417
2545
  * flips and clamps to stay inside the nearest ancestor that clips its overflow
2418
2546
  * (or the viewport), so it may extend past the chart wrapper but is never cut
2419
- * off unless that ancestor is smaller than the box itself.
2547
+ * off unless that ancestor is smaller than the box itself. Below-axis placement
2548
+ * pins it beneath the x-axis and applies horizontal bounds only.
2420
2549
  *
2421
2550
  * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2422
2551
  * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
@@ -2533,12 +2662,16 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2533
2662
  renderTooltip: focusableRenderTooltip
2534
2663
  });
2535
2664
  };
2536
- const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate }) => {
2665
+ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
2537
2666
  return {
2538
2667
  tooltipRef: useCallback((element) => {
2539
- if (element && selectedIndex !== void 0) element.focus();
2540
- }, [selectedIndex]),
2541
- onChartFocus: useCallback(() => {
2668
+ if (element && selectedIndex !== void 0) {
2669
+ if (preventTooltipScroll) element.focus({ preventScroll: true });
2670
+ else element.focus();
2671
+ }
2672
+ }, [preventTooltipScroll, selectedIndex]),
2673
+ onChartFocus: useCallback((event) => {
2674
+ if (event.currentTarget.contains(event.relatedTarget)) return;
2542
2675
  if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
2543
2676
  }, [
2544
2677
  isNavigating,
@@ -2550,15 +2683,19 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2550
2683
  }, [setIsNavigating]),
2551
2684
  onChartKeyDown: useCallback((event) => {
2552
2685
  if (totalPoints === 0) return;
2686
+ const focusChart = () => {
2687
+ if (preventTooltipScroll) chartRef.current?.focus({ preventScroll: true });
2688
+ else chartRef.current?.focus();
2689
+ };
2553
2690
  if (event.key === "Tab") {
2554
- chartRef.current?.focus();
2691
+ focusChart();
2555
2692
  setSelectedIndex(void 0);
2556
2693
  setIsNavigating(false);
2557
2694
  return;
2558
2695
  }
2559
2696
  const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
2560
2697
  if (currentSelectedIndex + 1 >= totalPoints && ["ArrowRight"].includes(event.key)) {
2561
- chartRef.current?.focus();
2698
+ focusChart();
2562
2699
  setSelectedIndex(void 0);
2563
2700
  setIsNavigating(false);
2564
2701
  return;
@@ -2573,7 +2710,7 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2573
2710
  } else if (event.key === "Escape") {
2574
2711
  setSelectedIndex(void 0);
2575
2712
  setIsNavigating(false);
2576
- chartRef.current?.focus();
2713
+ focusChart();
2577
2714
  } else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
2578
2715
  }, [
2579
2716
  totalPoints,
@@ -2581,7 +2718,8 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2581
2718
  setSelectedIndex,
2582
2719
  setIsNavigating,
2583
2720
  chartRef,
2584
- onActivate
2721
+ onActivate,
2722
+ preventTooltipScroll
2585
2723
  ])
2586
2724
  };
2587
2725
  };
@@ -3869,10 +4007,11 @@ const TooltipDate = ({ date, displayResolution }) => {
3869
4007
  * one row per visible series (label + formatted value), sorted descending by
3870
4008
  * value. Reused by AreaChart, which has the same multi-series shape.
3871
4009
  *
3872
- * @param params - visx `RenderTooltipParams< DataPointDate >`, plus the chart's optional `bucketInfo`.
4010
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4011
+ * @param contentStyle - Explicit tooltip content color overrides.
3873
4012
  * @return Tooltip JSX, or `null` when no datum is hovered.
3874
4013
  */
3875
- const renderDefaultTooltip = (params) => {
4014
+ const renderDefaultTooltip = (params, contentStyle) => {
3876
4015
  const { tooltipData, bucketInfo } = params;
3877
4016
  const nearestDatum = tooltipData?.nearestDatum?.datum;
3878
4017
  if (!nearestDatum) return null;
@@ -3882,6 +4021,7 @@ const renderDefaultTooltip = (params) => {
3882
4021
  })).sort((a, b) => b.value - a.value);
3883
4022
  return /* @__PURE__ */ jsxs("div", {
3884
4023
  className: line_chart_module_default["line-chart__tooltip"],
4024
+ style: contentStyle,
3885
4025
  children: [/* @__PURE__ */ jsx("div", {
3886
4026
  className: line_chart_module_default["line-chart__tooltip-date"],
3887
4027
  children: /* @__PURE__ */ jsx(TooltipDate, {
@@ -3931,7 +4071,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
3931
4071
  ]);
3932
4072
  return null;
3933
4073
  };
3934
- const LineChartInternal = 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) => {
4074
+ const LineChartInternal = 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) => {
3935
4075
  const legendInteractive = legend.interactive ?? false;
3936
4076
  const legendCollapseGroups = legend.collapseGroups ?? false;
3937
4077
  const legendShape = legend.shape ?? "line";
@@ -4009,7 +4149,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4009
4149
  setIsNavigating,
4010
4150
  chartRef,
4011
4151
  totalPoints: dataSorted[0]?.data.length || 0,
4012
- onActivate: activateSelectedPoint
4152
+ onActivate: activateSelectedPoint,
4153
+ preventTooltipScroll: tooltipPlacement === "below-axis"
4013
4154
  });
4014
4155
  const chartOptions = useMemo(() => {
4015
4156
  return {
@@ -4118,10 +4259,29 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4118
4259
  xAccessor: (d) => d?.date,
4119
4260
  yAccessor: (d) => d?.value
4120
4261
  };
4121
- const tooltipRenderer = useMemo(() => (params) => renderTooltip({
4262
+ const resolvedTooltipStyle = useMemo(() => {
4263
+ if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
4264
+ if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
4265
+ return {
4266
+ backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
4267
+ ...tooltipStyle
4268
+ };
4269
+ }, [renderTooltip, tooltipStyle]);
4270
+ const tooltipRenderer = useMemo(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
4122
4271
  ...params,
4123
4272
  bucketInfo
4124
- }), [renderTooltip, bucketInfo]);
4273
+ }, {
4274
+ color: tooltipStyle?.color,
4275
+ background: tooltipStyle?.background,
4276
+ backgroundColor: tooltipStyle?.backgroundColor
4277
+ }) : renderTooltip({
4278
+ ...params,
4279
+ bucketInfo
4280
+ }), [
4281
+ renderTooltip,
4282
+ bucketInfo,
4283
+ tooltipStyle
4284
+ ]);
4125
4285
  if (error) return /* @__PURE__ */ jsx("div", {
4126
4286
  className: clsx("line-chart", line_chart_module_default["line-chart"]),
4127
4287
  children: error
@@ -4162,6 +4322,7 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4162
4322
  children: ({ contentHeight }) => {
4163
4323
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4164
4324
  return /* @__PURE__ */ jsx("div", {
4325
+ ref: chartRef,
4165
4326
  role: "grid",
4166
4327
  "aria-label": __("Line chart", "jetpack-charts"),
4167
4328
  tabIndex: 0,
@@ -4169,7 +4330,6 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4169
4330
  onFocus: onChartFocus,
4170
4331
  onBlur: onChartBlur,
4171
4332
  children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
4172
- ref: chartRef,
4173
4333
  className: xy_plot_module_default["xy-plot"],
4174
4334
  children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
4175
4335
  theme,
@@ -4260,6 +4420,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4260
4420
  withTooltips && /* @__PURE__ */ jsx(AccessibleTooltip, {
4261
4421
  detectBounds: true,
4262
4422
  snapTooltipToDatumX: true,
4423
+ tooltipPlacement,
4424
+ style: resolvedTooltipStyle,
4263
4425
  snapTooltipToDatumY: true,
4264
4426
  showSeriesGlyphs: true,
4265
4427
  renderTooltip: tooltipRenderer,
@@ -4267,6 +4429,8 @@ const LineChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4267
4429
  glyphStyle,
4268
4430
  showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
4269
4431
  showHorizontalCrosshair: withTooltipCrosshairs?.showHorizontal,
4432
+ verticalCrosshairStyle: withTooltipCrosshairs?.verticalStyle,
4433
+ horizontalCrosshairStyle: withTooltipCrosshairs?.horizontalStyle,
4270
4434
  selectedIndex,
4271
4435
  tooltipRef,
4272
4436
  keyboardFocusedClassName: line_chart_module_default["line-chart__tooltip--keyboard-focused"],
@@ -4663,6 +4827,7 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4663
4827
  children: ({ contentHeight }) => {
4664
4828
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4665
4829
  return /* @__PURE__ */ jsx("div", {
4830
+ ref: chartRef,
4666
4831
  role: "grid",
4667
4832
  "aria-label": __("Area chart", "jetpack-charts"),
4668
4833
  tabIndex: 0,
@@ -4670,7 +4835,6 @@ const AreaChartInternal = forwardRef(({ data, chartId: providedChartId, width, h
4670
4835
  onFocus: onChartFocus,
4671
4836
  onBlur: onChartBlur,
4672
4837
  children: chartHeight > 0 && /* @__PURE__ */ jsxs("div", {
4673
- ref: chartRef,
4674
4838
  className: xy_plot_module_default["xy-plot"],
4675
4839
  children: [zoomable && zoom.domain && /* @__PURE__ */ jsx(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ jsxs(XYChart, {
4676
4840
  theme,
@@ -6419,9 +6583,13 @@ var heatmap_chart_module_default = {
6419
6583
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
6420
6584
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
6421
6585
  "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
6586
+ "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
6422
6587
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
6423
6588
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
6589
+ "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
6424
6590
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
6591
+ "heatmap-chart__gap-end": "a8ccharts-O3YMOW-heatmap-chart__gap-end",
6592
+ "heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
6425
6593
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
6426
6594
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
6427
6595
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
@@ -6435,17 +6603,21 @@ var heatmap_chart_module_default = {
6435
6603
  //#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
6436
6604
  const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
6437
6605
  /**
6438
- * Get the min and max values from heatmap data, ignoring null/NaN.
6606
+ * Get the min and max values from heatmap data, ignoring null/NaN. Summary
6607
+ * columns stay out: a roll-up on the scale would flatten every real cell.
6439
6608
  * @param data - The heatmap columns
6440
6609
  * @return Tuple of [min, max] values
6441
6610
  */
6442
6611
  const getValueExtent = (data) => {
6443
6612
  let min = Infinity;
6444
6613
  let max = -Infinity;
6445
- for (const column of data) for (const cell of column.data) {
6446
- if (!isPresent(cell.value)) continue;
6447
- if (cell.value < min) min = cell.value;
6448
- if (cell.value > max) max = cell.value;
6614
+ for (const column of data) {
6615
+ if (column.summary) continue;
6616
+ for (const cell of column.data) {
6617
+ if (!isPresent(cell.value)) continue;
6618
+ if (cell.value < min) min = cell.value;
6619
+ if (cell.value > max) max = cell.value;
6620
+ }
6449
6621
  }
6450
6622
  if (min === Infinity) return [0, 0];
6451
6623
  return [min, max];
@@ -6466,101 +6638,6 @@ const getNormalizedValue = (value, extent) => {
6466
6638
  return Math.min(1, Math.max(0, (value - min) / (max - min)));
6467
6639
  };
6468
6640
  //#endregion
6469
- //#region src/charts/heatmap-chart/private/build-calendar-data.ts
6470
- /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
6471
- const LABELLED_ROWS = [
6472
- 0,
6473
- 2,
6474
- 4
6475
- ];
6476
- /**
6477
- * Resolve one grid bound: an unparseable or narrowing value falls back to the
6478
- * series' own bound, so the grid can only ever be widened.
6479
- *
6480
- * @param bound - Requested bound as `yyyy-MM-dd`, if any.
6481
- * @param fallback - The series' own bound on this side.
6482
- * @param direction - Which way the bound is allowed to move.
6483
- * @return The bound to draw to.
6484
- */
6485
- const widenTo = (bound, fallback, direction) => {
6486
- if (!bound) return fallback;
6487
- const parsed = parseISO(bound);
6488
- if (isNaN(parsed.getTime())) return fallback;
6489
- if (direction === "earlier") return parsed < fallback ? parsed : fallback;
6490
- return parsed > fallback ? parsed : fallback;
6491
- };
6492
- const toDate = (point) => {
6493
- if (point.date instanceof Date && !isNaN(point.date.getTime())) return point.date;
6494
- if (point.dateString) {
6495
- const parsed = parseISO(point.dateString);
6496
- if (!isNaN(parsed.getTime())) return parsed;
6497
- }
6498
- return null;
6499
- };
6500
- const buildCalendarHeatmapData = (series, options = {}) => {
6501
- const weekStartsOn = options.weekStartsOn ?? 1;
6502
- const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
6503
- const entries = series.map((point) => ({
6504
- date: toDate(point),
6505
- value: point.value
6506
- })).filter((entry) => entry.date !== null);
6507
- if (!entries.length) return {
6508
- data: [],
6509
- rowLabels: []
6510
- };
6511
- const valueByDay = /* @__PURE__ */ new Map();
6512
- let minDate = entries[0].date;
6513
- let maxDate = entries[0].date;
6514
- for (const { date, value } of entries) {
6515
- valueByDay.set(format(date, "yyyy-MM-dd"), value);
6516
- if (date < minDate) minDate = date;
6517
- if (date > maxDate) maxDate = date;
6518
- }
6519
- const minDayKey = format(minDate, "yyyy-MM-dd");
6520
- const maxDayKey = format(maxDate, "yyyy-MM-dd");
6521
- const gridMinDate = widenTo(options.gridSpan?.start, minDate, "earlier");
6522
- const gridMaxDate = widenTo(options.gridSpan?.end, maxDate, "later");
6523
- const gridMaxDayKey = format(gridMaxDate, "yyyy-MM-dd");
6524
- const gridStart = startOfWeek(gridMinDate, { weekStartsOn });
6525
- const gridMinDayKey = format(gridMinDate < minDate ? gridStart : gridMinDate, "yyyy-MM-dd");
6526
- const weekCount = differenceInCalendarWeeks(gridMaxDate, gridStart, { weekStartsOn }) + 1;
6527
- const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? format(addDays(gridStart, row), "EEE") : "");
6528
- const MIN_FIRST_MONTH_WEEKS = 2;
6529
- const firstMonth = gridStart.getMonth();
6530
- let firstMonthWeeks = 0;
6531
- while (firstMonthWeeks < weekCount && addDays(gridStart, firstMonthWeeks * 7).getMonth() === firstMonth) firstMonthWeeks++;
6532
- const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
6533
- const data = [];
6534
- let previousMonth = -1;
6535
- for (let week = 0; week < weekCount; week++) {
6536
- const columnStart = addDays(gridStart, week * 7);
6537
- const month = columnStart.getMonth();
6538
- const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? format(columnStart, "MMM") : "";
6539
- previousMonth = month;
6540
- const cells = [];
6541
- for (let row = 0; row < 7; row++) {
6542
- const day = addDays(gridStart, week * 7 + row);
6543
- const key = format(day, "yyyy-MM-dd");
6544
- const cell = {
6545
- label: format(day, "EEE, MMM d, yyyy"),
6546
- value: valueByDay.has(key) ? valueByDay.get(key) : null
6547
- };
6548
- if (key < gridMinDayKey || key > gridMaxDayKey) {
6549
- if (hideOutOfRangeDays) cell.hidden = true;
6550
- } else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
6551
- cells.push(cell);
6552
- }
6553
- data.push({
6554
- label,
6555
- data: cells
6556
- });
6557
- }
6558
- return {
6559
- data,
6560
- rowLabels
6561
- };
6562
- };
6563
- //#endregion
6564
6641
  //#region src/charts/heatmap-chart/private/heatmap-context.ts
6565
6642
  /** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
6566
6643
  const HeatmapContext = createContext(null);
@@ -6610,7 +6687,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6610
6687
  //#endregion
6611
6688
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
6612
6689
  const CELL_MIX_FLOOR = .15;
6613
- 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 }) => {
6690
+ const NO_ROW_LABELS = [];
6691
+ 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 }) => {
6614
6692
  const chartId = useChartId(providedChartId);
6615
6693
  const { getElementStyles, theme } = useGlobalChartsContext();
6616
6694
  const scopeElement = useChartScopeElement();
@@ -6761,13 +6839,20 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6761
6839
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6762
6840
  const gridStyle = {
6763
6841
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6764
- gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
6842
+ gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
6765
6843
  gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
6766
6844
  };
6767
6845
  if (compact) {
6768
6846
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
6769
6847
  gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
6770
6848
  }
6849
+ const summaryGaps = (columnIndex) => {
6850
+ if (!data[columnIndex]?.summary) return {};
6851
+ return {
6852
+ [heatmap_chart_module_default["heatmap-chart__gap-start"]]: columnIndex > 0 && !data[columnIndex - 1]?.summary,
6853
+ [heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
6854
+ };
6855
+ };
6771
6856
  const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
6772
6857
  const heightCapped = !compact && Boolean(maxCellHeight);
6773
6858
  return /* @__PURE__ */ jsx(HeatmapContext.Provider, {
@@ -6805,7 +6890,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6805
6890
  "aria-hidden": "true",
6806
6891
  className: heatmap_chart_module_default["heatmap-chart__row"],
6807
6892
  children: [/* @__PURE__ */ jsx("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ jsx("span", {
6808
- className: heatmap_chart_module_default["heatmap-chart__col-label"],
6893
+ className: clsx(heatmap_chart_module_default["heatmap-chart__col-label"], {
6894
+ [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
6895
+ ...summaryGaps(columnIndex)
6896
+ }),
6809
6897
  children: column.label
6810
6898
  }, `col-${columnIndex}`))]
6811
6899
  }), Array.from({ length: rows }).map((_row, rowIndex) => {
@@ -6830,7 +6918,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6830
6918
  }, `cell-${columnIndex}-${rowIndex}`);
6831
6919
  const value = cell?.value ?? null;
6832
6920
  const present = isPresent(value);
6833
- const normalized = present ? getNormalizedValue(value, extent) : 0;
6921
+ const filled = present && !column.summary;
6922
+ const normalized = filled ? getNormalizedValue(value, extent) : 0;
6834
6923
  const flatIndex = columnIndex * rows + rowIndex;
6835
6924
  const info = buildTooltipData(columnIndex, rowIndex);
6836
6925
  const accessibleLabel = `${info.cellLabel || `${info.columnLabel ?? ""} ${info.rowLabel ?? ""}`.trim()}: ${info.value === null ? __("No data", "jetpack-charts") : formatNumber(info.value)}`;
@@ -6843,14 +6932,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6843
6932
  "data-column": columnIndex,
6844
6933
  "data-row": rowIndex,
6845
6934
  className: clsx(heatmap_chart_module_default["heatmap-chart__cell"], {
6846
- [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: present,
6847
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: present && cellHasLightText(normalized),
6935
+ [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
6936
+ [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
6937
+ [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
6938
+ ...summaryGaps(columnIndex),
6848
6939
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
6849
6940
  }),
6850
- style: present ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6941
+ style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6851
6942
  onMouseMove: handleCellMouseMove,
6852
6943
  onMouseLeave: handleCellMouseLeave,
6853
- children: drawValues && present && /* @__PURE__ */ jsx("span", {
6944
+ children: (drawValues || column.summary) && present && /* @__PURE__ */ jsx("span", {
6854
6945
  className: heatmap_chart_module_default["heatmap-chart__cell-value"],
6855
6946
  children: formatNumberCompact(value)
6856
6947
  })
@@ -6886,6 +6977,269 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ jsx(HeatmapChartW
6886
6977
  HeatmapChartResponsiveInner.displayName = "HeatmapChart";
6887
6978
  const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
6888
6979
  //#endregion
6980
+ //#region src/charts/heatmap-chart/private/civil-day.ts
6981
+ const DAY_MS = 864e5;
6982
+ const KEY_OPTIONS = {
6983
+ calendar: "gregory",
6984
+ numberingSystem: "latn",
6985
+ year: "numeric",
6986
+ month: "2-digit",
6987
+ day: "2-digit"
6988
+ };
6989
+ const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
6990
+ const pad = (value, length) => String(value).padStart(length, "0");
6991
+ /**
6992
+ * The day a UTC proxy stands for.
6993
+ *
6994
+ * @param date - UTC proxy.
6995
+ * @return Its day key.
6996
+ */
6997
+ const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
6998
+ /**
6999
+ * A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
7000
+ * `DAY_MS`.
7001
+ *
7002
+ * @param key - Day key.
7003
+ * @return The proxy, or null when the key names no real day.
7004
+ */
7005
+ const civilDate = (key) => {
7006
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
7007
+ if (!match) return null;
7008
+ const date = /* @__PURE__ */ new Date(0);
7009
+ date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
7010
+ date.setUTCHours(0, 0, 0, 0);
7011
+ return civilKey(date) === key ? date : null;
7012
+ };
7013
+ /**
7014
+ * The day a written date names, ignoring any time and zone it carries.
7015
+ *
7016
+ * @param text - `yyyy-MM-dd`, optionally followed by a time.
7017
+ * @return Its day key, or null.
7018
+ */
7019
+ const writtenDayKey = (text) => {
7020
+ const match = DATE_PREFIX.exec(text);
7021
+ if (!match) return null;
7022
+ const key = `${match[1]}-${match[2]}-${match[3]}`;
7023
+ return civilDate(key) ? key : null;
7024
+ };
7025
+ /**
7026
+ * Reads the day an instant falls on in a zone.
7027
+ *
7028
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
7029
+ * @return Reader taking a `Date`, null for a day no key can hold.
7030
+ */
7031
+ const instantDayReader = (timeZone) => {
7032
+ const formatter = new Intl.DateTimeFormat("en-US", {
7033
+ ...KEY_OPTIONS,
7034
+ timeZone
7035
+ });
7036
+ return (date) => {
7037
+ const parts = formatter.formatToParts(date);
7038
+ const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
7039
+ const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
7040
+ return civilDate(key) ? key : null;
7041
+ };
7042
+ };
7043
+ /**
7044
+ * The day a series point belongs to.
7045
+ *
7046
+ * A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
7047
+ * zone. Anything else is a wall clock the host wrote, so its date part is the day.
7048
+ *
7049
+ * @param point - Series point.
7050
+ * @param readInstant - Reader from `instantDayReader`.
7051
+ * @return Its day key, or null when the point carries no usable date.
7052
+ */
7053
+ const pointDayKey = (point, readInstant) => {
7054
+ if (point.date instanceof Date && !isNaN(point.date.getTime())) {
7055
+ const key = readInstant(point.date);
7056
+ if (key) return key;
7057
+ }
7058
+ const text = point.dateString;
7059
+ if (!text) return null;
7060
+ if (!hasTimezone(text)) return writtenDayKey(text);
7061
+ const parsed = parseISO(text);
7062
+ return isNaN(parsed.getTime()) ? null : readInstant(parsed);
7063
+ };
7064
+ /**
7065
+ * Steps a proxy by whole days.
7066
+ *
7067
+ * @param date - UTC proxy.
7068
+ * @param days - Days to add; may be negative.
7069
+ * @return The shifted proxy.
7070
+ */
7071
+ const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
7072
+ /**
7073
+ * Rounds a proxy down to its week.
7074
+ *
7075
+ * @param date - UTC proxy.
7076
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
7077
+ * @return The proxy for that week's first day.
7078
+ */
7079
+ const startOfCivilWeek = (date, weekStartsOn) => {
7080
+ const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
7081
+ return addCivilDays(date, -daysSinceWeekStart);
7082
+ };
7083
+ /**
7084
+ * Counts the columns a span needs.
7085
+ *
7086
+ * @param from - Earlier UTC proxy.
7087
+ * @param to - Later UTC proxy.
7088
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
7089
+ * @return Columns needed to cover the span, inclusive.
7090
+ */
7091
+ const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
7092
+ //#endregion
7093
+ //#region src/charts/heatmap-chart/build-calendar-data.ts
7094
+ /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
7095
+ const LABELLED_ROWS = [
7096
+ 0,
7097
+ 2,
7098
+ 4
7099
+ ];
7100
+ /**
7101
+ * Resolve one grid bound: an unparseable or narrowing value falls back to the
7102
+ * series' own bound, so the grid can only ever be widened.
7103
+ *
7104
+ * @param bound - Requested bound as `yyyy-MM-dd`, if any.
7105
+ * @param fallback - The series' own bound on this side.
7106
+ * @param direction - Which way the bound is allowed to move.
7107
+ * @return The day key to draw to.
7108
+ */
7109
+ const widenTo = (bound, fallback, direction) => {
7110
+ const key = bound ? writtenDayKey(bound) : null;
7111
+ if (!key) {
7112
+ 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.`);
7113
+ return fallback;
7114
+ }
7115
+ if (direction === "earlier") return key < fallback ? key : fallback;
7116
+ return key > fallback ? key : fallback;
7117
+ };
7118
+ /**
7119
+ * Lay a day-bucketed series out as calendar columns.
7120
+ *
7121
+ * @param series - Points to bucket.
7122
+ * @param options - Grid shape, plus the locale and zone to read days in.
7123
+ * @return Columns and row labels for `HeatmapChart`.
7124
+ */
7125
+ const buildCalendarHeatmapData = (series, options = {}) => {
7126
+ const weekStartsOn = options.weekStartsOn ?? 1;
7127
+ const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
7128
+ const { locale, timeZone } = sanitizeFormatting({
7129
+ locale: options.locale,
7130
+ timeZone: options.timeZone
7131
+ });
7132
+ const readInstant = instantDayReader(timeZone);
7133
+ const valueByDay = /* @__PURE__ */ new Map();
7134
+ let minDayKey;
7135
+ let maxDayKey;
7136
+ for (const point of series) {
7137
+ const key = pointDayKey(point, readInstant);
7138
+ if (!key) {
7139
+ const offending = point.dateString ?? (point.date && String(point.date));
7140
+ 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.");
7141
+ continue;
7142
+ }
7143
+ valueByDay.set(key, point.value);
7144
+ if (!minDayKey || key < minDayKey) minDayKey = key;
7145
+ if (!maxDayKey || key > maxDayKey) maxDayKey = key;
7146
+ }
7147
+ if (!minDayKey || !maxDayKey) return {
7148
+ data: [],
7149
+ rowLabels: []
7150
+ };
7151
+ const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
7152
+ const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
7153
+ const requestedMinDate = civilDate(requestedMinDayKey);
7154
+ const gridMaxDate = civilDate(gridMaxDayKey);
7155
+ const labelFormatting = {
7156
+ locale,
7157
+ timeZone: "UTC"
7158
+ };
7159
+ const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
7160
+ const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
7161
+ const formatDay = createDateFormatter({
7162
+ weekday: "short",
7163
+ month: "short",
7164
+ day: "numeric",
7165
+ year: "numeric"
7166
+ }, labelFormatting);
7167
+ const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
7168
+ const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
7169
+ const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
7170
+ const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
7171
+ const MIN_FIRST_MONTH_WEEKS = 2;
7172
+ const firstMonth = gridStart.getUTCMonth();
7173
+ let firstMonthWeeks = 0;
7174
+ while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
7175
+ const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
7176
+ const data = [];
7177
+ let previousMonth = -1;
7178
+ for (let week = 0; week < weekCount; week++) {
7179
+ const columnStart = addCivilDays(gridStart, week * 7);
7180
+ const month = columnStart.getUTCMonth();
7181
+ const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
7182
+ previousMonth = month;
7183
+ const cells = [];
7184
+ for (let row = 0; row < 7; row++) {
7185
+ const day = addCivilDays(gridStart, week * 7 + row);
7186
+ const key = civilKey(day);
7187
+ const cell = {
7188
+ label: formatDay(day),
7189
+ value: valueByDay.has(key) ? valueByDay.get(key) : null
7190
+ };
7191
+ if (key < gridMinDayKey || key > gridMaxDayKey) {
7192
+ if (hideOutOfRangeDays) cell.hidden = true;
7193
+ } else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
7194
+ cells.push(cell);
7195
+ }
7196
+ data.push({
7197
+ label,
7198
+ data: cells
7199
+ });
7200
+ }
7201
+ return {
7202
+ data,
7203
+ rowLabels
7204
+ };
7205
+ };
7206
+ //#endregion
7207
+ //#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
7208
+ /**
7209
+ * `buildCalendarHeatmapData` with the locale and zone taken from
7210
+ * `GlobalChartsProvider` where the caller names neither.
7211
+ *
7212
+ * @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
7213
+ * @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
7214
+ * @return Columns and row labels for `HeatmapChart`.
7215
+ */
7216
+ const useCalendarHeatmapData = (series, options = {}) => {
7217
+ const formatting = useChartFormatting();
7218
+ const locale = options.locale ?? formatting.locale;
7219
+ const timeZone = options.timeZone ?? formatting.timeZone;
7220
+ const { weekStartsOn, hideOutOfRangeDays } = options;
7221
+ const gridStart = options.gridSpan?.start;
7222
+ const gridEnd = options.gridSpan?.end;
7223
+ return useMemo(() => buildCalendarHeatmapData(series, {
7224
+ locale,
7225
+ timeZone,
7226
+ weekStartsOn,
7227
+ hideOutOfRangeDays,
7228
+ gridSpan: {
7229
+ start: gridStart,
7230
+ end: gridEnd
7231
+ }
7232
+ }), [
7233
+ series,
7234
+ locale,
7235
+ timeZone,
7236
+ weekStartsOn,
7237
+ hideOutOfRangeDays,
7238
+ gridStart,
7239
+ gridEnd
7240
+ ]);
7241
+ };
7242
+ //#endregion
6889
7243
  //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6890
7244
  /*!
6891
7245
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
@@ -6953,7 +7307,7 @@ function paramCase(input, options) {
6953
7307
  return dotCase(input, __assign({ delimiter: "-" }, options));
6954
7308
  }
6955
7309
  //#endregion
6956
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
7310
+ //#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
6957
7311
  function useUpdateEffect(effect, deps) {
6958
7312
  const mountedRef = useRef$1(false);
6959
7313
  useEffect$1(() => {
@@ -8523,7 +8877,7 @@ _createEmotion.css;
8523
8877
  _createEmotion.sheet;
8524
8878
  _createEmotion.cache;
8525
8879
  //#endregion
8526
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
8880
+ //#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
8527
8881
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
8528
8882
  var useCx = () => {
8529
8883
  const cache = __unsafe_useEmotionCache();
@@ -8630,7 +8984,7 @@ function memize(fn, options) {
8630
8984
  return memoized;
8631
8985
  }
8632
8986
  //#endregion
8633
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
8987
+ //#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
8634
8988
  var white = "#fff";
8635
8989
  var GRAY = {
8636
8990
  900: "#1e1e1e",
@@ -8709,7 +9063,7 @@ var COLORS = Object.freeze({
8709
9063
  ui: UI
8710
9064
  });
8711
9065
  //#endregion
8712
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/config-values.mjs
9066
+ //#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
8713
9067
  var CONTROL_HEIGHT = "36px";
8714
9068
  var CONTROL_PROPS = {
8715
9069
  controlPaddingX: 12,
@@ -8827,7 +9181,7 @@ function warning(message) {
8827
9181
  logged.add(message);
8828
9182
  }
8829
9183
  //#endregion
8830
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
9184
+ //#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
8831
9185
  var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
8832
9186
  var ComponentsContext = createContext$1(
8833
9187
  /** @type {Record<string, any>} */
@@ -8854,18 +9208,18 @@ var BaseContextSystemProvider = ({ children, value }) => {
8854
9208
  };
8855
9209
  memo(BaseContextSystemProvider);
8856
9210
  //#endregion
8857
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/constants.mjs
9211
+ //#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
8858
9212
  var COMPONENT_NAMESPACE = "data-wp-component";
8859
9213
  var CONNECTED_NAMESPACE = "data-wp-c16t";
8860
9214
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
8861
9215
  //#endregion
8862
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
9216
+ //#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
8863
9217
  function getStyledClassName(namespace) {
8864
9218
  return `components-${paramCase(namespace)}`;
8865
9219
  }
8866
9220
  var getStyledClassNameFromKey = memize(getStyledClassName);
8867
9221
  //#endregion
8868
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/context-connect.mjs
9222
+ //#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
8869
9223
  function contextConnect(Component, namespace) {
8870
9224
  return _contextConnect(Component, namespace, { forwardsRef: true });
8871
9225
  }
@@ -8882,7 +9236,7 @@ function _contextConnect(Component, namespace, options) {
8882
9236
  });
8883
9237
  }
8884
9238
  //#endregion
8885
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/utils.mjs
9239
+ //#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
8886
9240
  function getNamespace(componentName) {
8887
9241
  return { [COMPONENT_NAMESPACE]: componentName };
8888
9242
  }
@@ -8890,7 +9244,7 @@ function getConnectedNamespace() {
8890
9244
  return { [CONNECTED_NAMESPACE]: true };
8891
9245
  }
8892
9246
  //#endregion
8893
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
9247
+ //#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
8894
9248
  function useContextSystem(props, namespace) {
8895
9249
  const contextSystemProps = useComponentsContext();
8896
9250
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -8910,7 +9264,7 @@ function useContextSystem(props, namespace) {
8910
9264
  return finalComponentProps;
8911
9265
  }
8912
9266
  //#endregion
8913
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
9267
+ //#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
8914
9268
  var visuallyHidden = {
8915
9269
  border: 0,
8916
9270
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -8926,7 +9280,7 @@ var visuallyHidden = {
8926
9280
  wordBreak: "normal"
8927
9281
  };
8928
9282
  //#endregion
8929
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
9283
+ //#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
8930
9284
  var customAttributeRegExp = /^(data|aria|x)-/i;
8931
9285
  var eventHandlerRegExp = /^on[A-Z]/;
8932
9286
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -9016,7 +9370,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
9016
9370
  }
9017
9371
  var PolymorphicElement = forwardRef$1(UnforwardedPolymorphicElement);
9018
9372
  //#endregion
9019
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/view/component.mjs
9373
+ //#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
9020
9374
  function UnforwardedView({ css, ...restProps }, ref) {
9021
9375
  return /* @__PURE__ */ jsx(PolymorphicElement, {
9022
9376
  ref,
@@ -9025,7 +9379,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
9025
9379
  }
9026
9380
  var component_default$2 = Object.assign(forwardRef$1(UnforwardedView), { selector: ".components-view" });
9027
9381
  //#endregion
9028
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
9382
+ //#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
9029
9383
  function UnconnectedVisuallyHidden(props, forwardedRef) {
9030
9384
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
9031
9385
  return /* @__PURE__ */ jsx(component_default$2, {
@@ -9040,7 +9394,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
9040
9394
  }
9041
9395
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
9042
9396
  //#endregion
9043
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
9397
+ //#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
9044
9398
  var breakpoints = [
9045
9399
  "40em",
9046
9400
  "52em",
@@ -9074,7 +9428,7 @@ function useResponsiveValue(values, options = {}) {
9074
9428
  return array[index >= array.length ? array.length - 1 : index];
9075
9429
  }
9076
9430
  //#endregion
9077
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/utils.mjs
9431
+ //#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
9078
9432
  var ALIGNMENTS = {
9079
9433
  bottom: {
9080
9434
  alignItems: "flex-end",
@@ -9122,7 +9476,7 @@ function getAlignmentProps(alignment) {
9122
9476
  return alignment ? ALIGNMENTS[alignment] : {};
9123
9477
  }
9124
9478
  //#endregion
9125
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/hook.mjs
9479
+ //#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
9126
9480
  function useGrid(props) {
9127
9481
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
9128
9482
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -9164,7 +9518,7 @@ function useGrid(props) {
9164
9518
  };
9165
9519
  }
9166
9520
  //#endregion
9167
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/component.mjs
9521
+ //#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
9168
9522
  function UnconnectedGrid(props, forwardedRef) {
9169
9523
  const gridProps = useGrid(props);
9170
9524
  return /* @__PURE__ */ jsx(component_default$2, {
@@ -10420,6 +10774,6 @@ function TrendIndicator({ direction, value, className, style, showIcon = true })
10420
10774
  });
10421
10775
  }
10422
10776
  //#endregion
10423
- export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, GoogleDataTableColumnRoleType, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getBucketInfo, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, prefersLightText, relativeLuminance, resolveCssVariable, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
10777
+ export { AccessibleTooltip, AreaChartResponsive as AreaChart, AreaChart as AreaChartUnresponsive, BarChartResponsive as BarChart, BarChart as BarChartUnresponsive, BarListChartResponsive as BarListChart, BarListChart as BarListChartUnresponsive, BaseTooltip, ConversionFunnelChartWithProvider as ConversionFunnelChart, GeoChartResponsive as GeoChart, GeoChartWithProvider as GeoChartUnresponsive, GlobalChartsContext, GlobalChartsProvider, GlobalChartsProvider as ThemeProvider, GoogleDataTableColumnRoleType, HeatmapChartResponsive as HeatmapChart, HeatmapChart as HeatmapChartUnresponsive, LeaderboardChartResponsive as LeaderboardChart, LeaderboardChart as LeaderboardChartUnresponsive, Legend, LineChartResponsive as LineChart, LineChart as LineChartUnresponsive, PieChartResponsive as PieChart, PieChart as PieChartUnresponsive, PieSemiCircleChartResponsive as PieSemiCircleChart, PieSemiCircleChart as PieSemiCircleChartUnresponsive, Sparkline, SparklineUnresponsive, TrendIndicator, buildCalendarHeatmapData, defaultTheme, formatMetricValue, formatPercentage, getBucketInfo, getColorDistance, hexToRgba, isValidHexColor, lightenHexColor, mergeThemes, mixHexColors, normalizeColorToHex, parseAsLocalDate, parseHslString, prefersLightText, relativeLuminance, resolveCssVariable, useCalendarHeatmapData, useChartFormatting, useChartLegendItems, useChartRegistration, useChartScopeElement, useGlobalChartsContext, useGlobalChartsTheme, useLeaderboardLegendItems, validateHexColor };
10424
10778
 
10425
10779
  //# sourceMappingURL=index.js.map