@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.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);
@@ -86,46 +87,35 @@ function attachSubComponents(Chart, subComponents) {
86
87
  return Object.assign(Chart, subComponents);
87
88
  }
88
89
  //#endregion
89
- //#region src/utils/date-parsing.ts
90
+ //#region src/utils/warn-once.ts
91
+ const warned = /* @__PURE__ */ new Set();
90
92
  /**
91
- * @file Date parsing utilities using date-fns for local timezone handling
92
- *
93
- * This module provides utilities for parsing various date string formats and converting
94
- * them to local timezone dates using the battle-tested date-fns library. For formats
95
- * without timezone info, they're treated as local. For formats with timezone info,
96
- * they're converted to the equivalent local time.
93
+ * Warns about a host-supplied value once per process, and never in production.
97
94
  *
98
- * Note: And specifically it prevents format `YYYY-MM-DD` being parsed as UTC date.
99
- *
100
- * Key Features:
101
- * - All parsed dates are in local timezone
102
- * - Converts timezone-aware strings to local equivalent
103
- * - Robust input validation and error handling using date-fns
104
- * - TypeScript type safety
105
- * - 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
106
107
  *
107
- * Supported Formats:
108
- * - YYYY-MM-DD (treated as local)
109
- * - YYYY-MM-DD HH:mm:ss (treated as local)
110
- * - YYYY-MM-DD HH:mm (treated as local)
111
- * - YYYY-MM-DDTHH:mm:ss (treated as local)
112
- * - YYYY-MM-DDTHH:mm:ss.SSS (treated as local)
113
- * - YYYY-MM-DDTHH:mm (treated as local)
114
- * - YYYY-MM-DDTHH:mm:ssZ (converted to local)
115
- * - 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.
116
111
  *
117
- * @example
118
- * ```typescript
119
- * parseAsLocalDate("2025-01-01"); // Local timezone
120
- * parseAsLocalDate("2025-01-01 14:30:00"); // Local timezone
121
- * parseAsLocalDate("2025-01-01 14:30"); // Local timezone
122
- * parseAsLocalDate("2025-01-01T14:30:45.123"); // Local timezone
123
- * parseAsLocalDate("2025-01-01T14:30:00Z"); // UTC 14:30 → Local equivalent
124
- * parseAsLocalDate("2025-01-01T14:30:00+05:00"); // +05:00 14:30 → Local equivalent
125
- * ```
112
+ * Note: this specifically avoids date-fns's default of parsing `YYYY-MM-DD` as a UTC date.
126
113
  */
127
114
  /**
128
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
+ *
129
119
  * @param {string} dateString - The date string to check for timezone information
130
120
  * @return {boolean} True if the date string contains timezone information, false otherwise
131
121
  */
@@ -133,28 +123,57 @@ const hasTimezone = (dateString) => {
133
123
  const tIndex = dateString.indexOf("T");
134
124
  if (tIndex === -1) return false;
135
125
  if (dateString.endsWith("Z")) return true;
136
- 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();
137
135
  };
138
136
  /**
139
- * Parses any supported date string format and returns a local timezone date
137
+ * The instant a wall-clock reading names in a given zone.
140
138
  *
141
- * Uses date-fns for robust parsing and validation. For strings without timezone
142
- * info, treats as local timezone. For strings with timezone info, converts to
143
- * 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.
144
141
  *
145
- * Supports:
146
- * - YYYY-MM-DD (local)
147
- * - YYYY-MM-DD HH:mm:ss (local)
148
- * - YYYY-MM-DD HH:mm (local)
149
- * - YYYY-MM-DDTHH:mm:ss (local)
150
- * - YYYY-MM-DDTHH:mm:ss.SSS (local)
151
- * - YYYY-MM-DDTHH:mm (local)
152
- * - YYYY-MM-DDTHH:mm:ssZ (UTC → local)
153
- * - YYYY-MM-DDTHH:mm:ss±HH:mm (offset → local)
154
- * @param {string} dateString - The date string to parse into a local timezone date
155
- * @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.
156
145
  */
157
- 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) => {
158
177
  const trimmedString = dateString.trim();
159
178
  if (hasTimezone(trimmedString)) {
160
179
  const isoDate = (0, date_fns.parseISO)(trimmedString);
@@ -170,7 +189,10 @@ const parseAsLocalDate = (dateString) => {
170
189
  "yyyy-MM-dd'T'HH:mm"
171
190
  ]) {
172
191
  const result = (0, date_fns.parse)(trimmedString, format, /* @__PURE__ */ new Date());
173
- 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;
174
196
  }
175
197
  return /* @__PURE__ */ new Date(NaN);
176
198
  };
@@ -275,6 +297,31 @@ const formatPercentage = (value) => {
275
297
  } });
276
298
  };
277
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
278
325
  //#region src/utils/get-longest-tick-width.ts
279
326
  /**
280
327
  * Returns the width of the longest tick.
@@ -661,12 +708,6 @@ const getFormatter$1 = (options, { locale, timeZone }) => {
661
708
  }
662
709
  return formatter;
663
710
  };
664
- const warned = /* @__PURE__ */ new Set();
665
- const warnOnce = (key, message) => {
666
- if (warned.has(key) || process.env.NODE_ENV === "production") return;
667
- warned.add(key);
668
- console.warn(`[Charts] ${message}`);
669
- };
670
711
  const isUsable = (formatting) => {
671
712
  try {
672
713
  Intl.DateTimeFormat(formatting.locale, { timeZone: formatting.timeZone });
@@ -1292,13 +1333,28 @@ const useXYChartTheme = (data) => {
1292
1333
  ]);
1293
1334
  };
1294
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
1295
1351
  //#region src/hooks/use-chart-data-transform.ts
1296
1352
  /**
1297
1353
  * Hook that transforms and sorts chart data, handling date parsing and sorting
1298
1354
  *
1299
1355
  * This hook extracts the common data transformation logic used in both line-chart
1300
1356
  * and bar-chart components. It:
1301
- * 1. Parses date strings into Date objects using parseAsLocalDate
1357
+ * 1. Parses date strings into Date objects, dated in the provider's time zone
1302
1358
  * 2. Sorts data points by date when date properties are present
1303
1359
  * 3. Returns the original data unchanged when no date properties are found
1304
1360
  *
@@ -1306,6 +1362,7 @@ const useXYChartTheme = (data) => {
1306
1362
  * @return {SeriesData[]} The transformed and sorted data
1307
1363
  */
1308
1364
  const useChartDataTransform = (data) => {
1365
+ const { timeZone } = useChartFormatting();
1309
1366
  return (0, react.useMemo)(() => {
1310
1367
  const firstPoint = data?.[0]?.data?.[0];
1311
1368
  if (!(firstPoint && ("date" in firstPoint || "dateString" in firstPoint))) return data;
@@ -1314,7 +1371,7 @@ const useChartDataTransform = (data) => {
1314
1371
  data: series.data.map((point) => {
1315
1372
  let date;
1316
1373
  if ("date" in point && point.date) date = point.date;
1317
- else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString);
1374
+ else if ("dateString" in point && point.dateString) date = parseAsLocalDate(point.dateString, timeZone);
1318
1375
  return {
1319
1376
  ...point,
1320
1377
  date
@@ -1324,7 +1381,7 @@ const useChartDataTransform = (data) => {
1324
1381
  return a.date.getTime() - b.date.getTime();
1325
1382
  })
1326
1383
  }));
1327
- }, [data]);
1384
+ }, [data, timeZone]);
1328
1385
  };
1329
1386
  //#endregion
1330
1387
  //#region src/hooks/use-chart-margin.tsx
@@ -1366,11 +1423,32 @@ const DEFAULT_TICK_LENGTH = 8;
1366
1423
  * measure them via getLongestTickWidth.
1367
1424
  */
1368
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
+ };
1369
1446
  const getXAxisLabelMetrics = (theme, orientation) => {
1370
1447
  const xAxisStyles = orientation === "top" ? theme.axisStyles?.x?.top : theme.axisStyles?.x?.bottom;
1371
1448
  return {
1372
1449
  fontSize: resolveFontSize(xAxisStyles?.axisLabel?.fontSize) || resolveFontSize(theme.svgLabelSmall?.fontSize) || DEFAULT_FONT_SIZE,
1373
- tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH
1450
+ tickLength: xAxisStyles?.tickLength ?? DEFAULT_TICK_LENGTH,
1451
+ tickLabelStyle: toMeasurableStyle(xAxisStyles?.tickLabel)
1374
1452
  };
1375
1453
  };
1376
1454
  const useChartMargin = (height, options, data, theme, horizontal = false) => {
@@ -1382,7 +1460,7 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1382
1460
  const maxY = Math.max(...allDataPoints.map((d) => d.value));
1383
1461
  const yScale = (0, _visx_scale.createScale)({
1384
1462
  ...options.yScale,
1385
- domain: [minY, maxY],
1463
+ domain: options.yScale?.domain ?? [minY, maxY],
1386
1464
  range: [height, 0]
1387
1465
  });
1388
1466
  return (0, _visx_scale.getTicks)(yScale, options.axis?.y?.numTicks);
@@ -1401,18 +1479,25 @@ const useChartMargin = (height, options, data, theme, horizontal = false) => {
1401
1479
  };
1402
1480
  const yAxisOrientation = options.axis?.y?.orientation;
1403
1481
  const yAxisStyles = yAxisOrientation === "right" ? theme.axisStyles.y.right : theme.axisStyles.y.left;
1404
- const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, yAxisStyles.axisLabel);
1482
+ const yTickWidth = getLongestTickWidth(yTicks, options.axis?.y?.tickFormat, toMeasurableStyle(yAxisStyles.axisLabel));
1405
1483
  const yTickLabelFontSize = resolveFontSize(yAxisStyles?.tickLabel?.fontSize) || DEFAULT_FONT_SIZE;
1406
1484
  const yMarginValue = (yTickWidth ?? DEFAULT_Y_TICK_WIDTH) + (yAxisStyles?.tickLength ?? 0) + Math.ceil(yTickLabelFontSize * .25);
1407
- if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1408
- else defaultMargin.left = yMarginValue;
1485
+ if (options.axis?.y?.display !== false) {
1486
+ if (yAxisOrientation === "right") defaultMargin.right = yMarginValue;
1487
+ else defaultMargin.left = yMarginValue;
1488
+ }
1409
1489
  const xOrientation = options.axis?.x?.orientation === "top" ? "top" : "bottom";
1410
- const { fontSize, tickLength } = getXAxisLabelMetrics(theme, xOrientation);
1490
+ const { fontSize, tickLength, tickLabelStyle } = getXAxisLabelMetrics(theme, xOrientation);
1411
1491
  const computedXMargin = fontSize + tickLength;
1412
1492
  if (xOrientation === "top") {
1413
1493
  defaultMargin.top = Math.max(defaultMargin.top, computedXMargin);
1414
1494
  defaultMargin.bottom = DEFAULT_BOTTOM_FOR_TOP_AXIS;
1415
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
+ }
1416
1501
  return defaultMargin;
1417
1502
  }, [
1418
1503
  options,
@@ -1688,21 +1773,6 @@ const useGlobalChartsTheme = () => {
1688
1773
  return (0, react.useContext)(GlobalChartsContext)?.theme ?? defaultTheme;
1689
1774
  };
1690
1775
  //#endregion
1691
- //#region src/providers/chart-context/hooks/use-chart-formatting.ts
1692
- const RUNTIME_FORMATTING = {};
1693
- /**
1694
- * The locale and time zone dates are rendered in, as set on `GlobalChartsProvider`.
1695
- *
1696
- * Falls back to the runtime's own rather than throwing: charts render outside a
1697
- * provider, and a host that sets neither gets exactly the browser-default
1698
- * behavior this package had before the context existed.
1699
- *
1700
- * @return The host's formatting context.
1701
- */
1702
- const useChartFormatting = () => {
1703
- return (0, react.useContext)(GlobalChartsContext)?.formatting ?? RUNTIME_FORMATTING;
1704
- };
1705
- //#endregion
1706
1776
  //#region src/components/legend/utils/value-or-identity.ts
1707
1777
  /**
1708
1778
  * Returns an object's value if defined, or the object.
@@ -2142,6 +2212,8 @@ const BaseTooltip = ({ data, top, left, component: Component = DefaultTooltipCon
2142
2212
  //#endregion
2143
2213
  //#region src/components/tooltip/private/bounded-tooltip.tsx
2144
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));
2145
2217
  const isClipping = (element) => {
2146
2218
  const { overflow, overflowX, overflowY } = getComputedStyle(element);
2147
2219
  return [
@@ -2159,33 +2231,49 @@ const findClippingAncestor = (wrapper) => {
2159
2231
  return null;
2160
2232
  };
2161
2233
  /**
2162
- * Where the box goes, in wrapper coordinates. Below and to the right of the
2163
- * anchor; flipped to the other side when that side clips less, as visx's
2164
- * `TooltipWithBounds` decides it; then clamped so the box stays inside
2165
- * `bounds` whenever it fits at all.
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.
2166
2242
  *
2167
- * @param params - Anchor, offsets, the box size and the bounds to keep inside.
2243
+ * @param params - Anchor, offsets, the box size and the rects to keep inside.
2168
2244
  * @param params.left - Anchor x, in wrapper coordinates.
2169
2245
  * @param params.top - Anchor y, in wrapper coordinates.
2170
2246
  * @param params.offsetLeft - Gap between the anchor and the box, horizontally.
2171
2247
  * @param params.offsetTop - Gap between the anchor and the box, vertically.
2172
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.
2173
2251
  * @param params.bounds - Edges the box must keep inside, in wrapper coordinates.
2174
- * @return The box's top-left corner, rounded to whole pixels.
2252
+ * @return The box's top-left corner; fixed placement preserves the axis's subpixel y.
2175
2253
  */
2176
- const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) => {
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
+ };
2177
2265
  const rightX = left + offsetLeft;
2178
2266
  const leftX = left - offsetLeft - box.width;
2179
- const rightOverflow = rightX + box.width - bounds.right;
2180
- const leftOverflow = bounds.left - leftX;
2267
+ const rightOverflow = rightX + box.width - fit.right;
2268
+ const leftOverflow = fit.left - leftX;
2181
2269
  let x = rightOverflow > 0 && rightOverflow > leftOverflow ? leftX : rightX;
2182
2270
  const downY = top + offsetTop;
2183
2271
  const upY = top - offsetTop - box.height;
2184
- const downOverflow = downY + box.height - bounds.bottom;
2185
- const upOverflow = bounds.top - upY;
2272
+ const downOverflow = downY + box.height - fit.bottom;
2273
+ const upOverflow = fit.top - upY;
2186
2274
  let y = downOverflow > 0 && downOverflow > upOverflow ? upY : downY;
2187
- x = Math.min(Math.max(x, bounds.left), Math.max(bounds.left, bounds.right - box.width));
2188
- y = Math.min(Math.max(y, bounds.top), Math.max(bounds.top, bounds.bottom - box.height));
2275
+ x = clamp(x, bounds.left, bounds.right, box.width);
2276
+ y = clamp(y, bounds.top, bounds.bottom, box.height);
2189
2277
  return {
2190
2278
  x: Math.round(x),
2191
2279
  y: Math.round(y)
@@ -2199,7 +2287,8 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
2199
2287
  * alone would let the box spill into an `overflow: hidden` card and be cut off.
2200
2288
  *
2201
2289
  * Re-measures on every render, so a box whose content changes width between
2202
- * two hovers is placed for its current size.
2290
+ * two hovers is placed for its current size. See `getBoundedPosition` for the
2291
+ * below-axis exception.
2203
2292
  *
2204
2293
  * @param props - visx `Tooltip` props.
2205
2294
  * @param props.left - Anchor x, in wrapper coordinates.
@@ -2209,9 +2298,10 @@ const getBoundedPosition = ({ left, top, offsetLeft, offsetTop, box, bounds }) =
2209
2298
  * @param props.style - Box styles; visx's defaults unless `unstyled`.
2210
2299
  * @param props.unstyled - Skip `style` and leave the box bare.
2211
2300
  * @param props.children - Box content.
2301
+ * @param props.placement - Fixed below-axis placement or automatic flipping.
2212
2302
  * @return The tooltip box.
2213
2303
  */
2214
- const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, ...rest }) => {
2304
+ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offsetTop = DEFAULT_OFFSET, style = _visx_tooltip.defaultStyles, unstyled = false, children, placement = "auto", ...rest }) => {
2215
2305
  const nodeRef = (0, react.useRef)(null);
2216
2306
  const clipRef = (0, react.useRef)(null);
2217
2307
  const [position, setPosition] = (0, react.useState)(null);
@@ -2236,10 +2326,17 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2236
2326
  top,
2237
2327
  offsetLeft,
2238
2328
  offsetTop,
2329
+ placement,
2239
2330
  box: {
2240
2331
  width: own.width,
2241
2332
  height: own.height
2242
2333
  },
2334
+ wrapper: {
2335
+ left: 0,
2336
+ top: 0,
2337
+ right: wrapperRect.width,
2338
+ bottom: wrapperRect.height
2339
+ },
2243
2340
  bounds: {
2244
2341
  left: clipRect.left - wrapperRect.left,
2245
2342
  top: clipRect.top - wrapperRect.top,
@@ -2253,11 +2350,12 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2253
2350
  top,
2254
2351
  offsetLeft,
2255
2352
  offsetTop,
2256
- children
2353
+ children,
2354
+ placement
2257
2355
  ]);
2258
2356
  const x = position?.x ?? left + offsetLeft;
2259
- const y = position?.y ?? top + offsetTop;
2260
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_tooltip.Tooltip, {
2357
+ const y = position?.y ?? top + (placement === "below-axis" ? POINTER_HEIGHT : offsetTop);
2358
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_tooltip.Tooltip, {
2261
2359
  ref: nodeRef,
2262
2360
  style: {
2263
2361
  position: "absolute",
@@ -2267,12 +2365,40 @@ const BoundedTooltip = ({ left = 0, top = 0, offsetLeft = DEFAULT_OFFSET, offset
2267
2365
  ...!unstyled && style
2268
2366
  },
2269
2367
  ...rest,
2270
- children
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]
2271
2381
  });
2272
2382
  };
2273
2383
  //#endregion
2274
2384
  //#region src/components/tooltip/xy-chart-tooltip.tsx
2275
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
+ };
2276
2402
  const DEFAULT_GLYPH_RADIUS = 4;
2277
2403
  const FALLBACK_COLOR = "#222";
2278
2404
  const DEFAULT_TOOLTIP_Z_INDEX = 3;
@@ -2303,7 +2429,7 @@ const defaultRenderGlyph$1 = ({ key, ...props }) => /* @__PURE__ */ (0, react_js
2303
2429
  seriesKey: key,
2304
2430
  ...props
2305
2431
  }, key);
2306
- const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, renderGlyph = defaultRenderGlyph$1, glyphStyle, snapTooltipToDatumX = false, snapTooltipToDatumY = false, showVerticalCrosshair = false, showHorizontalCrosshair = false, showDatumGlyph = false, showSeriesGlyphs = false, verticalCrosshairStyle, horizontalCrosshairStyle, detectBounds = true, zIndex = DEFAULT_TOOLTIP_Z_INDEX, ...rest }) => {
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 }) => {
2307
2433
  const tooltipProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !PORTAL_OPTIONS.has(key)));
2308
2434
  const { colorScale, theme, innerHeight = 0, innerWidth = 0, margin, xScale, yScale, dataRegistry } = (0, react.useContext)(_visx_xychart.DataContext) || {};
2309
2435
  const tooltipContent = renderTooltip ? renderTooltip({
@@ -2322,9 +2448,9 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2322
2448
  };
2323
2449
  const nearestDatum = tooltipContext.tooltipData?.nearestDatum;
2324
2450
  let { tooltipLeft, tooltipTop } = tooltipContext;
2325
- if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY)) {
2451
+ if (nearestDatum && (snapTooltipToDatumX || snapTooltipToDatumY || tooltipPlacement === "below-axis")) {
2326
2452
  const { left, top } = getDatumLeftTop(nearestDatum.key, nearestDatum.datum);
2327
- if (snapTooltipToDatumX && isValidNumber(left)) tooltipLeft = left;
2453
+ if ((snapTooltipToDatumX || tooltipPlacement === "below-axis") && isValidNumber(left)) tooltipLeft = left;
2328
2454
  if (snapTooltipToDatumY && isValidNumber(top)) tooltipTop = top;
2329
2455
  }
2330
2456
  const glyphs = [];
@@ -2358,13 +2484,14 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2358
2484
  const crosshairStroke = theme?.gridStyles?.stroke ?? theme?.htmlLabel?.color ?? FALLBACK_COLOR;
2359
2485
  const marginTop = margin?.top ?? 0;
2360
2486
  const marginLeft = margin?.left ?? 0;
2361
- const TooltipComponent = detectBounds ? BoundedTooltip : _visx_tooltip.Tooltip;
2487
+ const TooltipComponent = detectBounds || tooltipPlacement === "below-axis" ? BoundedTooltip : _visx_tooltip.Tooltip;
2362
2488
  const boxStyle = {
2363
2489
  ..._visx_tooltip.defaultStyles,
2364
2490
  zIndex,
2365
- background: theme?.backgroundColor ?? "white",
2366
- boxShadow: `0 1px 2px ${theme?.htmlLabel?.color ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2367
- ...theme?.htmlLabel
2491
+ backgroundColor: theme?.backgroundColor ?? "white",
2492
+ boxShadow: `0 1px 2px ${isValidHexColor(theme?.htmlLabel?.color) ? `${theme.htmlLabel.color}55` : "#22222255"}`,
2493
+ ...theme?.htmlLabel,
2494
+ ...style
2368
2495
  };
2369
2496
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
2370
2497
  className: "visx-tooltip-overlay",
@@ -2372,32 +2499,33 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2372
2499
  children: [
2373
2500
  showVerticalCrosshair && isValidNumber(tooltipLeft) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2374
2501
  className: "visx-crosshair visx-crosshair-vertical",
2502
+ stroke: crosshairStroke,
2503
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2504
+ ...crosshairPaintProps(verticalCrosshairStyle),
2375
2505
  x1: tooltipLeft,
2376
2506
  x2: tooltipLeft,
2377
2507
  y1: marginTop,
2378
- y2: marginTop + innerHeight,
2379
- stroke: crosshairStroke,
2380
- strokeWidth: CROSSHAIR_STROKE_WIDTH,
2381
- ...verticalCrosshairStyle
2508
+ y2: marginTop + innerHeight
2382
2509
  }),
2383
2510
  showHorizontalCrosshair && isValidNumber(tooltipTop) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
2384
2511
  className: "visx-crosshair visx-crosshair-horizontal",
2512
+ stroke: crosshairStroke,
2513
+ strokeWidth: CROSSHAIR_STROKE_WIDTH,
2514
+ ...crosshairPaintProps(horizontalCrosshairStyle),
2385
2515
  x1: marginLeft,
2386
2516
  x2: marginLeft + innerWidth,
2387
2517
  y1: tooltipTop,
2388
- y2: tooltipTop,
2389
- stroke: crosshairStroke,
2390
- strokeWidth: CROSSHAIR_STROKE_WIDTH,
2391
- ...horizontalCrosshairStyle
2518
+ y2: tooltipTop
2392
2519
  }),
2393
2520
  glyphs
2394
2521
  ]
2395
2522
  }), container && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipComponent, {
2396
2523
  left: tooltipLeft,
2397
- top: tooltipTop,
2524
+ top: tooltipPlacement === "below-axis" ? marginTop + innerHeight + (margin?.bottom ?? 0) : tooltipTop,
2398
2525
  style: boxStyle,
2399
2526
  applyPositionStyle: true,
2400
2527
  ...tooltipProps,
2528
+ ...tooltipPlacement === "below-axis" && { placement: tooltipPlacement },
2401
2529
  children: tooltipContent
2402
2530
  }), container)] });
2403
2531
  };
@@ -2418,7 +2546,8 @@ const XyChartTooltipContent = ({ tooltipContext, container, renderTooltip, rende
2418
2546
  * isolation keeps the box's `zIndex` from competing with page chrome. The box
2419
2547
  * flips and clamps to stay inside the nearest ancestor that clips its overflow
2420
2548
  * (or the viewport), so it may extend past the chart wrapper but is never cut
2421
- * off unless that ancestor is smaller than the box itself.
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.
2422
2551
  *
2423
2552
  * @param props - visx's `Tooltip` options. `scroll`, `debounce` and `resizeObserverPolyfill` are accepted and ignored.
2424
2553
  * @return An anchor in the SVG, plus the overlay and the tooltip box while the tooltip is open.
@@ -2535,12 +2664,16 @@ const AccessibleTooltip = ({ renderTooltip, selectedIndex, tooltipRef, keyboardF
2535
2664
  renderTooltip: focusableRenderTooltip
2536
2665
  });
2537
2666
  };
2538
- const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate }) => {
2667
+ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating, setIsNavigating, chartRef, totalPoints, onActivate, preventTooltipScroll = false }) => {
2539
2668
  return {
2540
2669
  tooltipRef: (0, react.useCallback)((element) => {
2541
- if (element && selectedIndex !== void 0) element.focus();
2542
- }, [selectedIndex]),
2543
- onChartFocus: (0, react.useCallback)(() => {
2670
+ if (element && selectedIndex !== void 0) {
2671
+ if (preventTooltipScroll) element.focus({ preventScroll: true });
2672
+ else element.focus();
2673
+ }
2674
+ }, [preventTooltipScroll, selectedIndex]),
2675
+ onChartFocus: (0, react.useCallback)((event) => {
2676
+ if (event.currentTarget.contains(event.relatedTarget)) return;
2544
2677
  if (!isNavigating && selectedIndex !== void 0) setSelectedIndex(0);
2545
2678
  }, [
2546
2679
  isNavigating,
@@ -2552,15 +2685,19 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2552
2685
  }, [setIsNavigating]),
2553
2686
  onChartKeyDown: (0, react.useCallback)((event) => {
2554
2687
  if (totalPoints === 0) return;
2688
+ const focusChart = () => {
2689
+ if (preventTooltipScroll) chartRef.current?.focus({ preventScroll: true });
2690
+ else chartRef.current?.focus();
2691
+ };
2555
2692
  if (event.key === "Tab") {
2556
- chartRef.current?.focus();
2693
+ focusChart();
2557
2694
  setSelectedIndex(void 0);
2558
2695
  setIsNavigating(false);
2559
2696
  return;
2560
2697
  }
2561
2698
  const currentSelectedIndex = selectedIndex === void 0 ? -1 : selectedIndex;
2562
2699
  if (currentSelectedIndex + 1 >= totalPoints && ["ArrowRight"].includes(event.key)) {
2563
- chartRef.current?.focus();
2700
+ focusChart();
2564
2701
  setSelectedIndex(void 0);
2565
2702
  setIsNavigating(false);
2566
2703
  return;
@@ -2575,7 +2712,7 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2575
2712
  } else if (event.key === "Escape") {
2576
2713
  setSelectedIndex(void 0);
2577
2714
  setIsNavigating(false);
2578
- chartRef.current?.focus();
2715
+ focusChart();
2579
2716
  } else if ((event.key === "Enter" || event.key === " ") && selectedIndex !== void 0) onActivate?.(selectedIndex);
2580
2717
  }, [
2581
2718
  totalPoints,
@@ -2583,7 +2720,8 @@ const useKeyboardNavigation = ({ selectedIndex, setSelectedIndex, isNavigating,
2583
2720
  setSelectedIndex,
2584
2721
  setIsNavigating,
2585
2722
  chartRef,
2586
- onActivate
2723
+ onActivate,
2724
+ preventTooltipScroll
2587
2725
  ])
2588
2726
  };
2589
2727
  };
@@ -3871,10 +4009,11 @@ const TooltipDate = ({ date, displayResolution }) => {
3871
4009
  * one row per visible series (label + formatted value), sorted descending by
3872
4010
  * value. Reused by AreaChart, which has the same multi-series shape.
3873
4011
  *
3874
- * @param params - visx `RenderTooltipParams< DataPointDate >`, plus the chart's optional `bucketInfo`.
4012
+ * @param params - visx tooltip data and the chart's optional `bucketInfo`.
4013
+ * @param contentStyle - Explicit tooltip content color overrides.
3875
4014
  * @return Tooltip JSX, or `null` when no datum is hovered.
3876
4015
  */
3877
- const renderDefaultTooltip = (params) => {
4016
+ const renderDefaultTooltip = (params, contentStyle) => {
3878
4017
  const { tooltipData, bucketInfo } = params;
3879
4018
  const nearestDatum = tooltipData?.nearestDatum?.datum;
3880
4019
  if (!nearestDatum) return null;
@@ -3884,6 +4023,7 @@ const renderDefaultTooltip = (params) => {
3884
4023
  })).sort((a, b) => b.value - a.value);
3885
4024
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3886
4025
  className: line_chart_module_default["line-chart__tooltip"],
4026
+ style: contentStyle,
3887
4027
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3888
4028
  className: line_chart_module_default["line-chart__tooltip-date"],
3889
4029
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipDate, {
@@ -3933,7 +4073,7 @@ const LineChartScalesRef = ({ chartRef, width, height, margin }) => {
3933
4073
  ]);
3934
4074
  return null;
3935
4075
  };
3936
- 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) => {
4076
+ 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) => {
3937
4077
  const legendInteractive = legend.interactive ?? false;
3938
4078
  const legendCollapseGroups = legend.collapseGroups ?? false;
3939
4079
  const legendShape = legend.shape ?? "line";
@@ -4011,7 +4151,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4011
4151
  setIsNavigating,
4012
4152
  chartRef,
4013
4153
  totalPoints: dataSorted[0]?.data.length || 0,
4014
- onActivate: activateSelectedPoint
4154
+ onActivate: activateSelectedPoint,
4155
+ preventTooltipScroll: tooltipPlacement === "below-axis"
4015
4156
  });
4016
4157
  const chartOptions = (0, react.useMemo)(() => {
4017
4158
  return {
@@ -4120,10 +4261,29 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4120
4261
  xAccessor: (d) => d?.date,
4121
4262
  yAccessor: (d) => d?.value
4122
4263
  };
4123
- const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip({
4264
+ const resolvedTooltipStyle = (0, react.useMemo)(() => {
4265
+ if (renderTooltip !== renderDefaultTooltip || !tooltipStyle) return tooltipStyle;
4266
+ if (!tooltipStyle.color || tooltipStyle.background || tooltipStyle.backgroundColor) return tooltipStyle;
4267
+ return {
4268
+ backgroundColor: "var(--a8c-charts-color-tooltip-surface, rgb(0 0 0 / 85%))",
4269
+ ...tooltipStyle
4270
+ };
4271
+ }, [renderTooltip, tooltipStyle]);
4272
+ const tooltipRenderer = (0, react.useMemo)(() => (params) => renderTooltip === renderDefaultTooltip ? renderDefaultTooltip({
4124
4273
  ...params,
4125
4274
  bucketInfo
4126
- }), [renderTooltip, bucketInfo]);
4275
+ }, {
4276
+ color: tooltipStyle?.color,
4277
+ background: tooltipStyle?.background,
4278
+ backgroundColor: tooltipStyle?.backgroundColor
4279
+ }) : renderTooltip({
4280
+ ...params,
4281
+ bucketInfo
4282
+ }), [
4283
+ renderTooltip,
4284
+ bucketInfo,
4285
+ tooltipStyle
4286
+ ]);
4127
4287
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4128
4288
  className: (0, clsx.default)("line-chart", line_chart_module_default["line-chart"]),
4129
4289
  children: error
@@ -4164,6 +4324,7 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4164
4324
  children: ({ contentHeight }) => {
4165
4325
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4166
4326
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4327
+ ref: chartRef,
4167
4328
  role: "grid",
4168
4329
  "aria-label": (0, _wordpress_i18n.__)("Line chart", "jetpack-charts"),
4169
4330
  tabIndex: 0,
@@ -4171,7 +4332,6 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4171
4332
  onFocus: onChartFocus,
4172
4333
  onBlur: onChartBlur,
4173
4334
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4174
- ref: chartRef,
4175
4335
  className: xy_plot_module_default["xy-plot"],
4176
4336
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
4177
4337
  theme,
@@ -4262,6 +4422,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4262
4422
  withTooltips && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccessibleTooltip, {
4263
4423
  detectBounds: true,
4264
4424
  snapTooltipToDatumX: true,
4425
+ tooltipPlacement,
4426
+ style: resolvedTooltipStyle,
4265
4427
  snapTooltipToDatumY: true,
4266
4428
  showSeriesGlyphs: true,
4267
4429
  renderTooltip: tooltipRenderer,
@@ -4269,6 +4431,8 @@ const LineChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4269
4431
  glyphStyle,
4270
4432
  showVerticalCrosshair: withTooltipCrosshairs?.showVertical,
4271
4433
  showHorizontalCrosshair: withTooltipCrosshairs?.showHorizontal,
4434
+ verticalCrosshairStyle: withTooltipCrosshairs?.verticalStyle,
4435
+ horizontalCrosshairStyle: withTooltipCrosshairs?.horizontalStyle,
4272
4436
  selectedIndex,
4273
4437
  tooltipRef,
4274
4438
  keyboardFocusedClassName: line_chart_module_default["line-chart__tooltip--keyboard-focused"],
@@ -4665,6 +4829,7 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4665
4829
  children: ({ contentHeight }) => {
4666
4830
  const chartHeight = contentHeight > 0 ? contentHeight : height;
4667
4831
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4832
+ ref: chartRef,
4668
4833
  role: "grid",
4669
4834
  "aria-label": (0, _wordpress_i18n.__)("Area chart", "jetpack-charts"),
4670
4835
  tabIndex: 0,
@@ -4672,7 +4837,6 @@ const AreaChartInternal = (0, react.forwardRef)(({ data, chartId: providedChartI
4672
4837
  onFocus: onChartFocus,
4673
4838
  onBlur: onChartBlur,
4674
4839
  children: chartHeight > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4675
- ref: chartRef,
4676
4840
  className: xy_plot_module_default["xy-plot"],
4677
4841
  children: [zoomable && zoom.domain && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ZoomResetButton, { onClick: zoom.reset }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_visx_xychart.XYChart, {
4678
4842
  theme,
@@ -6421,9 +6585,13 @@ var heatmap_chart_module_default = {
6421
6585
  "heatmap-chart__cell--placeholder": "a8ccharts-O3YMOW-heatmap-chart__cell--placeholder",
6422
6586
  "heatmap-chart__cell--selected": "a8ccharts-O3YMOW-heatmap-chart__cell--selected",
6423
6587
  "heatmap-chart__cell--strong": "a8ccharts-O3YMOW-heatmap-chart__cell--strong",
6588
+ "heatmap-chart__cell--summary": "a8ccharts-O3YMOW-heatmap-chart__cell--summary",
6424
6589
  "heatmap-chart__cell-value": "a8ccharts-O3YMOW-heatmap-chart__cell-value",
6425
6590
  "heatmap-chart__col-label": "a8ccharts-O3YMOW-heatmap-chart__col-label",
6591
+ "heatmap-chart__col-label--summary": "a8ccharts-O3YMOW-heatmap-chart__col-label--summary",
6426
6592
  "heatmap-chart__empty": "a8ccharts-O3YMOW-heatmap-chart__empty",
6593
+ "heatmap-chart__gap-end": "a8ccharts-O3YMOW-heatmap-chart__gap-end",
6594
+ "heatmap-chart__gap-start": "a8ccharts-O3YMOW-heatmap-chart__gap-start",
6427
6595
  "heatmap-chart__grid": "a8ccharts-O3YMOW-heatmap-chart__grid",
6428
6596
  "heatmap-chart__grid--compact": "a8ccharts-O3YMOW-heatmap-chart__grid--compact",
6429
6597
  "heatmap-chart__grid--height-capped": "a8ccharts-O3YMOW-heatmap-chart__grid--height-capped",
@@ -6437,17 +6605,21 @@ var heatmap_chart_module_default = {
6437
6605
  //#region src/charts/heatmap-chart/private/use-heatmap-colors.ts
6438
6606
  const isPresent = (value) => value !== null && value !== void 0 && !isNaN(value);
6439
6607
  /**
6440
- * Get the min and max values from heatmap data, ignoring null/NaN.
6608
+ * Get the min and max values from heatmap data, ignoring null/NaN. Summary
6609
+ * columns stay out: a roll-up on the scale would flatten every real cell.
6441
6610
  * @param data - The heatmap columns
6442
6611
  * @return Tuple of [min, max] values
6443
6612
  */
6444
6613
  const getValueExtent = (data) => {
6445
6614
  let min = Infinity;
6446
6615
  let max = -Infinity;
6447
- for (const column of data) for (const cell of column.data) {
6448
- if (!isPresent(cell.value)) continue;
6449
- if (cell.value < min) min = cell.value;
6450
- if (cell.value > max) max = cell.value;
6616
+ for (const column of data) {
6617
+ if (column.summary) continue;
6618
+ for (const cell of column.data) {
6619
+ if (!isPresent(cell.value)) continue;
6620
+ if (cell.value < min) min = cell.value;
6621
+ if (cell.value > max) max = cell.value;
6622
+ }
6451
6623
  }
6452
6624
  if (min === Infinity) return [0, 0];
6453
6625
  return [min, max];
@@ -6468,101 +6640,6 @@ const getNormalizedValue = (value, extent) => {
6468
6640
  return Math.min(1, Math.max(0, (value - min) / (max - min)));
6469
6641
  };
6470
6642
  //#endregion
6471
- //#region src/charts/heatmap-chart/private/build-calendar-data.ts
6472
- /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
6473
- const LABELLED_ROWS = [
6474
- 0,
6475
- 2,
6476
- 4
6477
- ];
6478
- /**
6479
- * Resolve one grid bound: an unparseable or narrowing value falls back to the
6480
- * series' own bound, so the grid can only ever be widened.
6481
- *
6482
- * @param bound - Requested bound as `yyyy-MM-dd`, if any.
6483
- * @param fallback - The series' own bound on this side.
6484
- * @param direction - Which way the bound is allowed to move.
6485
- * @return The bound to draw to.
6486
- */
6487
- const widenTo = (bound, fallback, direction) => {
6488
- if (!bound) return fallback;
6489
- const parsed = (0, date_fns.parseISO)(bound);
6490
- if (isNaN(parsed.getTime())) return fallback;
6491
- if (direction === "earlier") return parsed < fallback ? parsed : fallback;
6492
- return parsed > fallback ? parsed : fallback;
6493
- };
6494
- const toDate = (point) => {
6495
- if (point.date instanceof Date && !isNaN(point.date.getTime())) return point.date;
6496
- if (point.dateString) {
6497
- const parsed = (0, date_fns.parseISO)(point.dateString);
6498
- if (!isNaN(parsed.getTime())) return parsed;
6499
- }
6500
- return null;
6501
- };
6502
- const buildCalendarHeatmapData = (series, options = {}) => {
6503
- const weekStartsOn = options.weekStartsOn ?? 1;
6504
- const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
6505
- const entries = series.map((point) => ({
6506
- date: toDate(point),
6507
- value: point.value
6508
- })).filter((entry) => entry.date !== null);
6509
- if (!entries.length) return {
6510
- data: [],
6511
- rowLabels: []
6512
- };
6513
- const valueByDay = /* @__PURE__ */ new Map();
6514
- let minDate = entries[0].date;
6515
- let maxDate = entries[0].date;
6516
- for (const { date, value } of entries) {
6517
- valueByDay.set((0, date_fns.format)(date, "yyyy-MM-dd"), value);
6518
- if (date < minDate) minDate = date;
6519
- if (date > maxDate) maxDate = date;
6520
- }
6521
- const minDayKey = (0, date_fns.format)(minDate, "yyyy-MM-dd");
6522
- const maxDayKey = (0, date_fns.format)(maxDate, "yyyy-MM-dd");
6523
- const gridMinDate = widenTo(options.gridSpan?.start, minDate, "earlier");
6524
- const gridMaxDate = widenTo(options.gridSpan?.end, maxDate, "later");
6525
- const gridMaxDayKey = (0, date_fns.format)(gridMaxDate, "yyyy-MM-dd");
6526
- const gridStart = (0, date_fns.startOfWeek)(gridMinDate, { weekStartsOn });
6527
- const gridMinDayKey = (0, date_fns.format)(gridMinDate < minDate ? gridStart : gridMinDate, "yyyy-MM-dd");
6528
- const weekCount = (0, date_fns.differenceInCalendarWeeks)(gridMaxDate, gridStart, { weekStartsOn }) + 1;
6529
- const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? (0, date_fns.format)((0, date_fns.addDays)(gridStart, row), "EEE") : "");
6530
- const MIN_FIRST_MONTH_WEEKS = 2;
6531
- const firstMonth = gridStart.getMonth();
6532
- let firstMonthWeeks = 0;
6533
- while (firstMonthWeeks < weekCount && (0, date_fns.addDays)(gridStart, firstMonthWeeks * 7).getMonth() === firstMonth) firstMonthWeeks++;
6534
- const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
6535
- const data = [];
6536
- let previousMonth = -1;
6537
- for (let week = 0; week < weekCount; week++) {
6538
- const columnStart = (0, date_fns.addDays)(gridStart, week * 7);
6539
- const month = columnStart.getMonth();
6540
- const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? (0, date_fns.format)(columnStart, "MMM") : "";
6541
- previousMonth = month;
6542
- const cells = [];
6543
- for (let row = 0; row < 7; row++) {
6544
- const day = (0, date_fns.addDays)(gridStart, week * 7 + row);
6545
- const key = (0, date_fns.format)(day, "yyyy-MM-dd");
6546
- const cell = {
6547
- label: (0, date_fns.format)(day, "EEE, MMM d, yyyy"),
6548
- value: valueByDay.has(key) ? valueByDay.get(key) : null
6549
- };
6550
- if (key < gridMinDayKey || key > gridMaxDayKey) {
6551
- if (hideOutOfRangeDays) cell.hidden = true;
6552
- } else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
6553
- cells.push(cell);
6554
- }
6555
- data.push({
6556
- label,
6557
- data: cells
6558
- });
6559
- }
6560
- return {
6561
- data,
6562
- rowLabels
6563
- };
6564
- };
6565
- //#endregion
6566
6643
  //#region src/charts/heatmap-chart/private/heatmap-context.ts
6567
6644
  /** Shared by the chart and legend without importing back from `heatmap-chart.tsx`. */
6568
6645
  const HeatmapContext = (0, react.createContext)(null);
@@ -6612,7 +6689,8 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6612
6689
  //#endregion
6613
6690
  //#region src/charts/heatmap-chart/heatmap-chart.tsx
6614
6691
  const CELL_MIX_FLOOR = .15;
6615
- 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 }) => {
6692
+ const NO_ROW_LABELS = [];
6693
+ 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 }) => {
6616
6694
  const chartId = useChartId(providedChartId);
6617
6695
  const { getElementStyles, theme } = useGlobalChartsContext();
6618
6696
  const scopeElement = useChartScopeElement();
@@ -6763,13 +6841,20 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6763
6841
  const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6764
6842
  const gridStyle = {
6765
6843
  "--a8c-charts-color-heatmap-primary": primaryColorHex,
6766
- gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
6844
+ gridTemplateColumns: `auto ${data.some((column) => column.summary) ? data.map((column) => column.summary ? "minmax(auto, max-content)" : columnTrack).join(" ") : `repeat(${columns}, ${columnTrack})`}`,
6767
6845
  gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
6768
6846
  };
6769
6847
  if (compact) {
6770
6848
  gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
6771
6849
  gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
6772
6850
  }
6851
+ const summaryGaps = (columnIndex) => {
6852
+ if (!data[columnIndex]?.summary) return {};
6853
+ return {
6854
+ [heatmap_chart_module_default["heatmap-chart__gap-start"]]: columnIndex > 0 && !data[columnIndex - 1]?.summary,
6855
+ [heatmap_chart_module_default["heatmap-chart__gap-end"]]: columnIndex < columns - 1 && !data[columnIndex + 1]?.summary
6856
+ };
6857
+ };
6773
6858
  const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
6774
6859
  const heightCapped = !compact && Boolean(maxCellHeight);
6775
6860
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatmapContext.Provider, {
@@ -6807,7 +6892,10 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6807
6892
  "aria-hidden": "true",
6808
6893
  className: heatmap_chart_module_default["heatmap-chart__row"],
6809
6894
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), data.map((column, columnIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6810
- className: heatmap_chart_module_default["heatmap-chart__col-label"],
6895
+ className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__col-label"], {
6896
+ [heatmap_chart_module_default["heatmap-chart__col-label--summary"]]: column.summary,
6897
+ ...summaryGaps(columnIndex)
6898
+ }),
6811
6899
  children: column.label
6812
6900
  }, `col-${columnIndex}`))]
6813
6901
  }), Array.from({ length: rows }).map((_row, rowIndex) => {
@@ -6832,7 +6920,8 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6832
6920
  }, `cell-${columnIndex}-${rowIndex}`);
6833
6921
  const value = cell?.value ?? null;
6834
6922
  const present = isPresent(value);
6835
- const normalized = present ? getNormalizedValue(value, extent) : 0;
6923
+ const filled = present && !column.summary;
6924
+ const normalized = filled ? getNormalizedValue(value, extent) : 0;
6836
6925
  const flatIndex = columnIndex * rows + rowIndex;
6837
6926
  const info = buildTooltipData(columnIndex, rowIndex);
6838
6927
  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)}`;
@@ -6845,14 +6934,16 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6845
6934
  "data-column": columnIndex,
6846
6935
  "data-row": rowIndex,
6847
6936
  className: (0, clsx.default)(heatmap_chart_module_default["heatmap-chart__cell"], {
6848
- [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: present,
6849
- [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: present && cellHasLightText(normalized),
6937
+ [heatmap_chart_module_default["heatmap-chart__cell--filled"]]: filled,
6938
+ [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: filled && cellHasLightText(normalized),
6939
+ [heatmap_chart_module_default["heatmap-chart__cell--summary"]]: column.summary,
6940
+ ...summaryGaps(columnIndex),
6850
6941
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
6851
6942
  }),
6852
- style: present ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6943
+ style: filled ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6853
6944
  onMouseMove: handleCellMouseMove,
6854
6945
  onMouseLeave: handleCellMouseLeave,
6855
- children: drawValues && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6946
+ children: (drawValues || column.summary) && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6856
6947
  className: heatmap_chart_module_default["heatmap-chart__cell-value"],
6857
6948
  children: (0, _automattic_number_formatters.formatNumberCompact)(value)
6858
6949
  })
@@ -6888,6 +6979,269 @@ const HeatmapChartResponsiveInner = (props) => /* @__PURE__ */ (0, react_jsx_run
6888
6979
  HeatmapChartResponsiveInner.displayName = "HeatmapChart";
6889
6980
  const HeatmapChartResponsive = attachSubComponents(withResponsive(HeatmapChartResponsiveInner), { Legend: HeatmapLegend });
6890
6981
  //#endregion
6982
+ //#region src/charts/heatmap-chart/private/civil-day.ts
6983
+ const DAY_MS = 864e5;
6984
+ const KEY_OPTIONS = {
6985
+ calendar: "gregory",
6986
+ numberingSystem: "latn",
6987
+ year: "numeric",
6988
+ month: "2-digit",
6989
+ day: "2-digit"
6990
+ };
6991
+ const DATE_PREFIX = /^(\d{4})-(\d{2})-(\d{2})/;
6992
+ const pad = (value, length) => String(value).padStart(length, "0");
6993
+ /**
6994
+ * The day a UTC proxy stands for.
6995
+ *
6996
+ * @param date - UTC proxy.
6997
+ * @return Its day key.
6998
+ */
6999
+ const civilKey = (date) => `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`;
7000
+ /**
7001
+ * A day key as a UTC-midnight proxy. UTC has no DST, so every step below is exactly
7002
+ * `DAY_MS`.
7003
+ *
7004
+ * @param key - Day key.
7005
+ * @return The proxy, or null when the key names no real day.
7006
+ */
7007
+ const civilDate = (key) => {
7008
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
7009
+ if (!match) return null;
7010
+ const date = /* @__PURE__ */ new Date(0);
7011
+ date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
7012
+ date.setUTCHours(0, 0, 0, 0);
7013
+ return civilKey(date) === key ? date : null;
7014
+ };
7015
+ /**
7016
+ * The day a written date names, ignoring any time and zone it carries.
7017
+ *
7018
+ * @param text - `yyyy-MM-dd`, optionally followed by a time.
7019
+ * @return Its day key, or null.
7020
+ */
7021
+ const writtenDayKey = (text) => {
7022
+ const match = DATE_PREFIX.exec(text);
7023
+ if (!match) return null;
7024
+ const key = `${match[1]}-${match[2]}-${match[3]}`;
7025
+ return civilDate(key) ? key : null;
7026
+ };
7027
+ /**
7028
+ * Reads the day an instant falls on in a zone.
7029
+ *
7030
+ * @param timeZone - IANA zone name; absent leaves the runtime's own.
7031
+ * @return Reader taking a `Date`, null for a day no key can hold.
7032
+ */
7033
+ const instantDayReader = (timeZone) => {
7034
+ const formatter = new Intl.DateTimeFormat("en-US", {
7035
+ ...KEY_OPTIONS,
7036
+ timeZone
7037
+ });
7038
+ return (date) => {
7039
+ const parts = formatter.formatToParts(date);
7040
+ const read = (type) => parts.find((part) => part.type === type)?.value ?? "";
7041
+ const key = `${pad(Number(read("year")), 4)}-${read("month")}-${read("day")}`;
7042
+ return civilDate(key) ? key : null;
7043
+ };
7044
+ };
7045
+ /**
7046
+ * The day a series point belongs to.
7047
+ *
7048
+ * A `Date`, or a string carrying `Z` or an offset, is an instant read in the host's
7049
+ * zone. Anything else is a wall clock the host wrote, so its date part is the day.
7050
+ *
7051
+ * @param point - Series point.
7052
+ * @param readInstant - Reader from `instantDayReader`.
7053
+ * @return Its day key, or null when the point carries no usable date.
7054
+ */
7055
+ const pointDayKey = (point, readInstant) => {
7056
+ if (point.date instanceof Date && !isNaN(point.date.getTime())) {
7057
+ const key = readInstant(point.date);
7058
+ if (key) return key;
7059
+ }
7060
+ const text = point.dateString;
7061
+ if (!text) return null;
7062
+ if (!hasTimezone(text)) return writtenDayKey(text);
7063
+ const parsed = (0, date_fns.parseISO)(text);
7064
+ return isNaN(parsed.getTime()) ? null : readInstant(parsed);
7065
+ };
7066
+ /**
7067
+ * Steps a proxy by whole days.
7068
+ *
7069
+ * @param date - UTC proxy.
7070
+ * @param days - Days to add; may be negative.
7071
+ * @return The shifted proxy.
7072
+ */
7073
+ const addCivilDays = (date, days) => new Date(date.getTime() + days * DAY_MS);
7074
+ /**
7075
+ * Rounds a proxy down to its week.
7076
+ *
7077
+ * @param date - UTC proxy.
7078
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
7079
+ * @return The proxy for that week's first day.
7080
+ */
7081
+ const startOfCivilWeek = (date, weekStartsOn) => {
7082
+ const daysSinceWeekStart = (date.getUTCDay() - weekStartsOn + 7) % 7;
7083
+ return addCivilDays(date, -daysSinceWeekStart);
7084
+ };
7085
+ /**
7086
+ * Counts the columns a span needs.
7087
+ *
7088
+ * @param from - Earlier UTC proxy.
7089
+ * @param to - Later UTC proxy.
7090
+ * @param weekStartsOn - 0 for Sunday, 1 for Monday.
7091
+ * @return Columns needed to cover the span, inclusive.
7092
+ */
7093
+ const civilWeekSpan = (from, to, weekStartsOn) => Math.floor((startOfCivilWeek(to, weekStartsOn).getTime() - startOfCivilWeek(from, weekStartsOn).getTime()) / (7 * DAY_MS)) + 1;
7094
+ //#endregion
7095
+ //#region src/charts/heatmap-chart/build-calendar-data.ts
7096
+ /** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
7097
+ const LABELLED_ROWS = [
7098
+ 0,
7099
+ 2,
7100
+ 4
7101
+ ];
7102
+ /**
7103
+ * Resolve one grid bound: an unparseable or narrowing value falls back to the
7104
+ * series' own bound, so the grid can only ever be widened.
7105
+ *
7106
+ * @param bound - Requested bound as `yyyy-MM-dd`, if any.
7107
+ * @param fallback - The series' own bound on this side.
7108
+ * @param direction - Which way the bound is allowed to move.
7109
+ * @return The day key to draw to.
7110
+ */
7111
+ const widenTo = (bound, fallback, direction) => {
7112
+ const key = bound ? writtenDayKey(bound) : null;
7113
+ if (!key) {
7114
+ 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.`);
7115
+ return fallback;
7116
+ }
7117
+ if (direction === "earlier") return key < fallback ? key : fallback;
7118
+ return key > fallback ? key : fallback;
7119
+ };
7120
+ /**
7121
+ * Lay a day-bucketed series out as calendar columns.
7122
+ *
7123
+ * @param series - Points to bucket.
7124
+ * @param options - Grid shape, plus the locale and zone to read days in.
7125
+ * @return Columns and row labels for `HeatmapChart`.
7126
+ */
7127
+ const buildCalendarHeatmapData = (series, options = {}) => {
7128
+ const weekStartsOn = options.weekStartsOn ?? 1;
7129
+ const hideOutOfRangeDays = options.hideOutOfRangeDays ?? true;
7130
+ const { locale, timeZone } = sanitizeFormatting({
7131
+ locale: options.locale,
7132
+ timeZone: options.timeZone
7133
+ });
7134
+ const readInstant = instantDayReader(timeZone);
7135
+ const valueByDay = /* @__PURE__ */ new Map();
7136
+ let minDayKey;
7137
+ let maxDayKey;
7138
+ for (const point of series) {
7139
+ const key = pointDayKey(point, readInstant);
7140
+ if (!key) {
7141
+ const offending = point.dateString ?? (point.date && String(point.date));
7142
+ 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.");
7143
+ continue;
7144
+ }
7145
+ valueByDay.set(key, point.value);
7146
+ if (!minDayKey || key < minDayKey) minDayKey = key;
7147
+ if (!maxDayKey || key > maxDayKey) maxDayKey = key;
7148
+ }
7149
+ if (!minDayKey || !maxDayKey) return {
7150
+ data: [],
7151
+ rowLabels: []
7152
+ };
7153
+ const requestedMinDayKey = widenTo(options.gridSpan?.start, minDayKey, "earlier");
7154
+ const gridMaxDayKey = widenTo(options.gridSpan?.end, maxDayKey, "later");
7155
+ const requestedMinDate = civilDate(requestedMinDayKey);
7156
+ const gridMaxDate = civilDate(gridMaxDayKey);
7157
+ const labelFormatting = {
7158
+ locale,
7159
+ timeZone: "UTC"
7160
+ };
7161
+ const formatWeekday = createDateFormatter({ weekday: "short" }, labelFormatting);
7162
+ const formatMonth = createDateFormatter({ month: "short" }, labelFormatting);
7163
+ const formatDay = createDateFormatter({
7164
+ weekday: "short",
7165
+ month: "short",
7166
+ day: "numeric",
7167
+ year: "numeric"
7168
+ }, labelFormatting);
7169
+ const gridStart = startOfCivilWeek(requestedMinDate, weekStartsOn);
7170
+ const gridMinDayKey = requestedMinDayKey < minDayKey ? civilKey(gridStart) : requestedMinDayKey;
7171
+ const weekCount = civilWeekSpan(gridStart, gridMaxDate, weekStartsOn);
7172
+ const rowLabels = Array.from({ length: 7 }, (_, row) => LABELLED_ROWS.includes(row) ? formatWeekday(addCivilDays(gridStart, row)) : "");
7173
+ const MIN_FIRST_MONTH_WEEKS = 2;
7174
+ const firstMonth = gridStart.getUTCMonth();
7175
+ let firstMonthWeeks = 0;
7176
+ while (firstMonthWeeks < weekCount && addCivilDays(gridStart, firstMonthWeeks * 7).getUTCMonth() === firstMonth) firstMonthWeeks++;
7177
+ const showFirstMonthLabel = !(firstMonthWeeks < weekCount) || firstMonthWeeks >= MIN_FIRST_MONTH_WEEKS;
7178
+ const data = [];
7179
+ let previousMonth = -1;
7180
+ for (let week = 0; week < weekCount; week++) {
7181
+ const columnStart = addCivilDays(gridStart, week * 7);
7182
+ const month = columnStart.getUTCMonth();
7183
+ const label = month !== previousMonth && (week !== 0 || showFirstMonthLabel) ? formatMonth(columnStart) : "";
7184
+ previousMonth = month;
7185
+ const cells = [];
7186
+ for (let row = 0; row < 7; row++) {
7187
+ const day = addCivilDays(gridStart, week * 7 + row);
7188
+ const key = civilKey(day);
7189
+ const cell = {
7190
+ label: formatDay(day),
7191
+ value: valueByDay.has(key) ? valueByDay.get(key) : null
7192
+ };
7193
+ if (key < gridMinDayKey || key > gridMaxDayKey) {
7194
+ if (hideOutOfRangeDays) cell.hidden = true;
7195
+ } else if (key < minDayKey || key > maxDayKey) cell.placeholder = true;
7196
+ cells.push(cell);
7197
+ }
7198
+ data.push({
7199
+ label,
7200
+ data: cells
7201
+ });
7202
+ }
7203
+ return {
7204
+ data,
7205
+ rowLabels
7206
+ };
7207
+ };
7208
+ //#endregion
7209
+ //#region src/charts/heatmap-chart/use-calendar-heatmap-data.ts
7210
+ /**
7211
+ * `buildCalendarHeatmapData` with the locale and zone taken from
7212
+ * `GlobalChartsProvider` where the caller names neither.
7213
+ *
7214
+ * @param series - Points to bucket. Held by reference, so a caller that rebuilds it each render defeats the memo.
7215
+ * @param options - As for `buildCalendarHeatmapData`; `locale` and `timeZone` win over the provider.
7216
+ * @return Columns and row labels for `HeatmapChart`.
7217
+ */
7218
+ const useCalendarHeatmapData = (series, options = {}) => {
7219
+ const formatting = useChartFormatting();
7220
+ const locale = options.locale ?? formatting.locale;
7221
+ const timeZone = options.timeZone ?? formatting.timeZone;
7222
+ const { weekStartsOn, hideOutOfRangeDays } = options;
7223
+ const gridStart = options.gridSpan?.start;
7224
+ const gridEnd = options.gridSpan?.end;
7225
+ return (0, react.useMemo)(() => buildCalendarHeatmapData(series, {
7226
+ locale,
7227
+ timeZone,
7228
+ weekStartsOn,
7229
+ hideOutOfRangeDays,
7230
+ gridSpan: {
7231
+ start: gridStart,
7232
+ end: gridEnd
7233
+ }
7234
+ }), [
7235
+ series,
7236
+ locale,
7237
+ timeZone,
7238
+ weekStartsOn,
7239
+ hideOutOfRangeDays,
7240
+ gridStart,
7241
+ gridEnd
7242
+ ]);
7243
+ };
7244
+ //#endregion
6891
7245
  //#region ../../../node_modules/.pnpm/is-plain-object@5.1.0/node_modules/is-plain-object/dist/is-plain-object.mjs
6892
7246
  /*!
6893
7247
  * is-plain-object <https://github.com/jonschlinkert/is-plain-object>
@@ -7251,7 +7605,7 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
7251
7605
  tslib_1.__exportStar(require_dist$1(), exports);
7252
7606
  }));
7253
7607
  //#endregion
7254
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/hooks/use-update-effect.mjs
7608
+ //#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
7255
7609
  function useUpdateEffect(effect, deps) {
7256
7610
  const mountedRef = (0, react.useRef)(false);
7257
7611
  (0, react.useEffect)(() => {
@@ -8889,7 +9243,7 @@ _createEmotion.css;
8889
9243
  _createEmotion.sheet;
8890
9244
  _createEmotion.cache;
8891
9245
  //#endregion
8892
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/hooks/use-cx.mjs
9246
+ //#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
8893
9247
  var isSerializedStyles = (o) => typeof o !== "undefined" && o !== null && ["name", "styles"].every((p) => typeof o[p] !== "undefined");
8894
9248
  var useCx = () => {
8895
9249
  const cache = __unsafe_useEmotionCache();
@@ -8996,7 +9350,7 @@ function memize(fn, options) {
8996
9350
  return memoized;
8997
9351
  }
8998
9352
  //#endregion
8999
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/colors-values.mjs
9353
+ //#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
9000
9354
  var white = "#fff";
9001
9355
  var GRAY = {
9002
9356
  900: "#1e1e1e",
@@ -9075,7 +9429,7 @@ var COLORS = Object.freeze({
9075
9429
  ui: UI
9076
9430
  });
9077
9431
  //#endregion
9078
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/config-values.mjs
9432
+ //#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
9079
9433
  var CONTROL_HEIGHT = "36px";
9080
9434
  var CONTROL_PROPS = {
9081
9435
  controlPaddingX: 12,
@@ -9193,7 +9547,7 @@ function warning(message) {
9193
9547
  logged.add(message);
9194
9548
  }
9195
9549
  //#endregion
9196
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/context-system-provider.mjs
9550
+ //#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
9197
9551
  var import_es6 = /* @__PURE__ */ __toESM(require_es6(), 1);
9198
9552
  var ComponentsContext = (0, react.createContext)(
9199
9553
  /** @type {Record<string, any>} */
@@ -9220,19 +9574,19 @@ var BaseContextSystemProvider = ({ children, value }) => {
9220
9574
  };
9221
9575
  (0, react.memo)(BaseContextSystemProvider);
9222
9576
  //#endregion
9223
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/constants.mjs
9577
+ //#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
9224
9578
  var COMPONENT_NAMESPACE = "data-wp-component";
9225
9579
  var CONNECTED_NAMESPACE = "data-wp-c16t";
9226
9580
  var CONNECT_STATIC_NAMESPACE = "__contextSystemKey__";
9227
9581
  //#endregion
9228
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/get-styled-class-name-from-key.mjs
9582
+ //#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
9229
9583
  var import_dist = require_dist();
9230
9584
  function getStyledClassName(namespace) {
9231
9585
  return `components-${(0, import_dist.paramCase)(namespace)}`;
9232
9586
  }
9233
9587
  var getStyledClassNameFromKey = memize(getStyledClassName);
9234
9588
  //#endregion
9235
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/context-connect.mjs
9589
+ //#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
9236
9590
  function contextConnect(Component, namespace) {
9237
9591
  return _contextConnect(Component, namespace, { forwardsRef: true });
9238
9592
  }
@@ -9249,7 +9603,7 @@ function _contextConnect(Component, namespace, options) {
9249
9603
  });
9250
9604
  }
9251
9605
  //#endregion
9252
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/utils.mjs
9606
+ //#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
9253
9607
  function getNamespace(componentName) {
9254
9608
  return { [COMPONENT_NAMESPACE]: componentName };
9255
9609
  }
@@ -9257,7 +9611,7 @@ function getConnectedNamespace() {
9257
9611
  return { [CONNECTED_NAMESPACE]: true };
9258
9612
  }
9259
9613
  //#endregion
9260
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/context/use-context-system.mjs
9614
+ //#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
9261
9615
  function useContextSystem(props, namespace) {
9262
9616
  const contextSystemProps = useComponentsContext();
9263
9617
  if (typeof namespace === "undefined") globalThis.SCRIPT_DEBUG === true && warning("useContextSystem: Please provide a namespace");
@@ -9277,7 +9631,7 @@ function useContextSystem(props, namespace) {
9277
9631
  return finalComponentProps;
9278
9632
  }
9279
9633
  //#endregion
9280
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/visually-hidden/styles.mjs
9634
+ //#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
9281
9635
  var visuallyHidden = {
9282
9636
  border: 0,
9283
9637
  clip: "rect(1px, 1px, 1px, 1px)",
@@ -9293,7 +9647,7 @@ var visuallyHidden = {
9293
9647
  wordBreak: "normal"
9294
9648
  };
9295
9649
  //#endregion
9296
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/polymorphic-element.mjs
9650
+ //#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
9297
9651
  var customAttributeRegExp = /^(data|aria|x)-/i;
9298
9652
  var eventHandlerRegExp = /^on[A-Z]/;
9299
9653
  var svgElementNames = new Set(`animate animateMotion animateTransform circle clipPath defs desc ellipse
@@ -9383,7 +9737,7 @@ function UnforwardedPolymorphicElement({ as, ...props }, ref) {
9383
9737
  }
9384
9738
  var PolymorphicElement = (0, react.forwardRef)(UnforwardedPolymorphicElement);
9385
9739
  //#endregion
9386
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/view/component.mjs
9740
+ //#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
9387
9741
  function UnforwardedView({ css, ...restProps }, ref) {
9388
9742
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PolymorphicElement, {
9389
9743
  ref,
@@ -9392,7 +9746,7 @@ function UnforwardedView({ css, ...restProps }, ref) {
9392
9746
  }
9393
9747
  var component_default$2 = Object.assign((0, react.forwardRef)(UnforwardedView), { selector: ".components-view" });
9394
9748
  //#endregion
9395
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/visually-hidden/component.mjs
9749
+ //#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
9396
9750
  function UnconnectedVisuallyHidden(props, forwardedRef) {
9397
9751
  const { style: styleProp, ...contextProps } = useContextSystem(props, "VisuallyHidden");
9398
9752
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -9407,7 +9761,7 @@ function UnconnectedVisuallyHidden(props, forwardedRef) {
9407
9761
  }
9408
9762
  var component_default$1 = contextConnect(UnconnectedVisuallyHidden, "VisuallyHidden");
9409
9763
  //#endregion
9410
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/utils/use-responsive-value.mjs
9764
+ //#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
9411
9765
  var breakpoints = [
9412
9766
  "40em",
9413
9767
  "52em",
@@ -9441,7 +9795,7 @@ function useResponsiveValue(values, options = {}) {
9441
9795
  return array[index >= array.length ? array.length - 1 : index];
9442
9796
  }
9443
9797
  //#endregion
9444
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/utils.mjs
9798
+ //#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
9445
9799
  var ALIGNMENTS = {
9446
9800
  bottom: {
9447
9801
  alignItems: "flex-end",
@@ -9489,7 +9843,7 @@ function getAlignmentProps(alignment) {
9489
9843
  return alignment ? ALIGNMENTS[alignment] : {};
9490
9844
  }
9491
9845
  //#endregion
9492
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/hook.mjs
9846
+ //#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
9493
9847
  function useGrid(props) {
9494
9848
  const { align, alignment, className, columnGap, columns = 2, gap = 3, isInline = false, justify, rowGap, rows, templateColumns, templateRows, ...otherProps } = useContextSystem(props, "Grid");
9495
9849
  const column = useResponsiveValue(Array.isArray(columns) ? columns : [columns]);
@@ -9531,7 +9885,7 @@ function useGrid(props) {
9531
9885
  };
9532
9886
  }
9533
9887
  //#endregion
9534
- //#region ../../../node_modules/.pnpm/@wordpress+components@40.0.0_@types+react-dom@18.3.7_@types+react@18.3.31__@types+react_d122c1aea9dc8a45ffffc1d3c2f19116/node_modules/@wordpress/components/build-module/grid/component.mjs
9888
+ //#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
9535
9889
  function UnconnectedGrid(props, forwardedRef) {
9536
9890
  const gridProps = useGrid(props);
9537
9891
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$2, {
@@ -10833,6 +11187,7 @@ exports.parseHslString = parseHslString;
10833
11187
  exports.prefersLightText = prefersLightText;
10834
11188
  exports.relativeLuminance = relativeLuminance;
10835
11189
  exports.resolveCssVariable = resolveCssVariable;
11190
+ exports.useCalendarHeatmapData = useCalendarHeatmapData;
10836
11191
  exports.useChartFormatting = useChartFormatting;
10837
11192
  exports.useChartLegendItems = useChartLegendItems;
10838
11193
  exports.useChartRegistration = useChartRegistration;