@sproutsocial/seeds-react-data-viz 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{axis-WOeYkTO1.d.mts → axis-B61qI-nm.d.mts} +1 -15
  2. package/dist/{axis-WOeYkTO1.d.ts → axis-B78FtYLh.d.ts} +1 -15
  3. package/dist/bar/index.d.mts +2 -2
  4. package/dist/bar/index.d.ts +2 -2
  5. package/dist/bar/index.js +14 -14
  6. package/dist/bar/index.js.map +1 -1
  7. package/dist/bubble/index.d.mts +1 -1
  8. package/dist/bubble/index.d.ts +1 -1
  9. package/dist/bubble/index.js +4 -4
  10. package/dist/{chartBase-BClSc5EN.d.mts → chartBase-CptOt3V0.d.mts} +23 -1
  11. package/dist/{chartBase-CycEw2vy.d.ts → chartBase-Nl6mcTB7.d.ts} +23 -1
  12. package/dist/{chunk-GQOKCB3M.js → chunk-3P6MPBSZ.js} +212 -27
  13. package/dist/chunk-3P6MPBSZ.js.map +1 -0
  14. package/dist/{chunk-6SF6OYQA.js → chunk-MTGYEUZN.js} +3 -3
  15. package/dist/{chunk-6SF6OYQA.js.map → chunk-MTGYEUZN.js.map} +1 -1
  16. package/dist/chunk-OP6K5NDR.js +52 -0
  17. package/dist/chunk-OP6K5NDR.js.map +1 -0
  18. package/dist/donut/index.d.mts +1 -1
  19. package/dist/donut/index.d.ts +1 -1
  20. package/dist/donut/index.js +4 -4
  21. package/dist/esm/bar/index.js +10 -10
  22. package/dist/esm/bar/index.js.map +1 -1
  23. package/dist/esm/bubble/index.js +1 -1
  24. package/dist/esm/{chunk-EHKGDOMK.js → chunk-ISP4A622.js} +196 -11
  25. package/dist/esm/chunk-ISP4A622.js.map +1 -0
  26. package/dist/esm/{chunk-AEBP4NCY.js → chunk-MQDI7D54.js} +2 -2
  27. package/dist/esm/chunk-Y4RJJBJQ.js +52 -0
  28. package/dist/esm/chunk-Y4RJJBJQ.js.map +1 -0
  29. package/dist/esm/donut/index.js +1 -1
  30. package/dist/esm/line-area/index.js +10 -10
  31. package/dist/esm/line-area/index.js.map +1 -1
  32. package/dist/esm/sparkline/index.js +9 -9
  33. package/dist/esm/sparkline/index.js.map +1 -1
  34. package/dist/line-area/index.d.mts +2 -2
  35. package/dist/line-area/index.d.ts +2 -2
  36. package/dist/line-area/index.js +14 -14
  37. package/dist/line-area/index.js.map +1 -1
  38. package/dist/sparkline/index.d.mts +3 -3
  39. package/dist/sparkline/index.d.ts +3 -3
  40. package/dist/sparkline/index.js +10 -10
  41. package/dist/sparkline/index.js.map +1 -1
  42. package/package.json +5 -5
  43. package/dist/chunk-6D7P3IOT.js +0 -150
  44. package/dist/chunk-6D7P3IOT.js.map +0 -1
  45. package/dist/chunk-GQOKCB3M.js.map +0 -1
  46. package/dist/esm/chunk-EHKGDOMK.js.map +0 -1
  47. package/dist/esm/chunk-NAKJY5PA.js +0 -150
  48. package/dist/esm/chunk-NAKJY5PA.js.map +0 -1
  49. /package/dist/esm/{chunk-AEBP4NCY.js.map → chunk-MQDI7D54.js.map} +0 -0
@@ -135,6 +135,173 @@ var ChartAnnotationMarkerPortal = _react.memo.call(void 0,
135
135
 
136
136
  var _seedsreacttext = require('@sproutsocial/seeds-react-text');
137
137
 
138
+ // src/charts/shared/formatters/datetimeFormatter.ts
139
+ var DAY_MS = 864e5;
140
+ var WEEK_MS = 6048e5;
141
+ var MIN_MONTH_MS = 24192e5;
142
+ var MIN_YEAR_MS = 31536e6;
143
+ var getDatePartsInTimezone = (date, timezone) => {
144
+ const formatter = new Intl.DateTimeFormat("en-US", {
145
+ year: "numeric",
146
+ month: "numeric",
147
+ day: "numeric",
148
+ timeZone: timezone
149
+ });
150
+ const parts = formatter.formatToParts(date);
151
+ return {
152
+ day: parseInt(_nullishCoalesce(_optionalChain([parts, 'access', _6 => _6.find, 'call', _7 => _7((p) => p.type === "day"), 'optionalAccess', _8 => _8.value]), () => ( "0")), 10),
153
+ month: parseInt(_nullishCoalesce(_optionalChain([parts, 'access', _9 => _9.find, 'call', _10 => _10((p) => p.type === "month"), 'optionalAccess', _11 => _11.value]), () => ( "0")), 10),
154
+ year: parseInt(_nullishCoalesce(_optionalChain([parts, 'access', _12 => _12.find, 'call', _13 => _13((p) => p.type === "year"), 'optionalAccess', _14 => _14.value]), () => ( "0")), 10)
155
+ };
156
+ };
157
+ var deriveGranularity = (tickPositions) => {
158
+ if (tickPositions.length < 2) {
159
+ return "day";
160
+ }
161
+ const deltas = [];
162
+ for (let i = 1; i < tickPositions.length; i++) {
163
+ const curr = tickPositions[i];
164
+ const prev = tickPositions[i - 1];
165
+ if (curr === void 0 || prev === void 0) {
166
+ continue;
167
+ }
168
+ deltas.push(Math.abs(curr - prev));
169
+ }
170
+ deltas.sort((a, b) => a - b);
171
+ const mid = Math.floor(deltas.length / 2);
172
+ const upper = deltas[mid];
173
+ const lower = deltas[mid - 1];
174
+ const median = deltas.length % 2 === 0 && lower !== void 0 ? (lower + upper) / 2 : upper;
175
+ if (median < DAY_MS) {
176
+ return "hour";
177
+ }
178
+ if (median < WEEK_MS) {
179
+ return "day";
180
+ }
181
+ if (median < MIN_MONTH_MS) {
182
+ return "week";
183
+ }
184
+ if (median < MIN_YEAR_MS) {
185
+ return "month";
186
+ }
187
+ return "year";
188
+ };
189
+ function datetimeFormatter({
190
+ value,
191
+ tickPositions = [],
192
+ // optional
193
+ timezone = "UTC",
194
+ timeFormat = "12",
195
+ textLocale = "en-US"
196
+ }) {
197
+ if (typeof value === "string" || !Number.isFinite(value)) {
198
+ return { primary: String(value) };
199
+ }
200
+ const granularity = deriveGranularity(tickPositions);
201
+ const tickIndex = tickPositions.indexOf(value);
202
+ const isFirst = tickIndex === 0;
203
+ const previousValue = tickPositions[tickIndex - 1];
204
+ const valueDate = new Date(value);
205
+ const valueParts = getDatePartsInTimezone(valueDate, timezone);
206
+ const previousValueDate = previousValue ? new Date(previousValue) : void 0;
207
+ const previousValueParts = previousValueDate ? getDatePartsInTimezone(previousValueDate, timezone) : void 0;
208
+ let firstPartOptions = {};
209
+ let secondPartOptions = {};
210
+ switch (granularity) {
211
+ case "hour":
212
+ firstPartOptions = timeFormat === "24" ? { hour: "numeric", minute: "numeric", hour12: false } : { hour: "numeric" };
213
+ if (isFirst || valueParts.day !== _optionalChain([previousValueParts, 'optionalAccess', _15 => _15.day])) {
214
+ secondPartOptions = { day: "numeric", month: "short" };
215
+ }
216
+ break;
217
+ case "day":
218
+ case "week":
219
+ firstPartOptions = { day: "numeric" };
220
+ if (isFirst || valueParts.month !== _optionalChain([previousValueParts, 'optionalAccess', _16 => _16.month])) {
221
+ secondPartOptions = { month: "short" };
222
+ }
223
+ break;
224
+ case "month":
225
+ firstPartOptions = { month: "short" };
226
+ if (isFirst || valueParts.year !== _optionalChain([previousValueParts, 'optionalAccess', _17 => _17.year])) {
227
+ secondPartOptions = { year: "numeric" };
228
+ }
229
+ break;
230
+ case "year":
231
+ firstPartOptions = { year: "numeric" };
232
+ break;
233
+ default:
234
+ firstPartOptions = {};
235
+ break;
236
+ }
237
+ const primary = new Intl.DateTimeFormat(textLocale, {
238
+ ...firstPartOptions,
239
+ timeZone: timezone
240
+ }).format(valueDate);
241
+ if (Object.keys(secondPartOptions).length > 0) {
242
+ const secondary = new Intl.DateTimeFormat(textLocale, {
243
+ ...secondPartOptions,
244
+ timeZone: timezone
245
+ }).format(valueDate);
246
+ return { primary, secondary };
247
+ }
248
+ return { primary };
249
+ }
250
+ function datetimeTitleFormatter({
251
+ value,
252
+ tickPositions = [],
253
+ // optional
254
+ timezone = "UTC",
255
+ timeFormat = "12",
256
+ textLocale = "en-US"
257
+ }) {
258
+ if (typeof value === "string" || !Number.isFinite(value)) {
259
+ return String(value);
260
+ }
261
+ const granularity = deriveGranularity(tickPositions);
262
+ const valueDate = new Date(value);
263
+ let options;
264
+ switch (granularity) {
265
+ case "hour":
266
+ options = timeFormat === "24" ? {
267
+ weekday: "long",
268
+ month: "long",
269
+ day: "numeric",
270
+ year: "numeric",
271
+ hour: "numeric",
272
+ minute: "numeric",
273
+ hour12: false
274
+ } : {
275
+ weekday: "long",
276
+ month: "long",
277
+ day: "numeric",
278
+ year: "numeric",
279
+ hour: "numeric"
280
+ };
281
+ break;
282
+ case "month":
283
+ options = { month: "long", year: "numeric" };
284
+ break;
285
+ case "year":
286
+ options = { year: "numeric" };
287
+ break;
288
+ case "day":
289
+ case "week":
290
+ default:
291
+ options = {
292
+ weekday: "long",
293
+ year: "numeric",
294
+ month: "long",
295
+ day: "numeric"
296
+ };
297
+ break;
298
+ }
299
+ return new Intl.DateTimeFormat(textLocale, {
300
+ ...options,
301
+ timeZone: timezone
302
+ }).format(valueDate);
303
+ }
304
+
138
305
  // src/charts/shared/formatters/valueFormatter.ts
139
306
  var _seedsreactduration = require('@sproutsocial/seeds-react-duration');
140
307
  var _seedsreactnumeral = require('@sproutsocial/seeds-react-numeral');
@@ -177,14 +344,20 @@ function valueFormatter({
177
344
 
178
345
  // src/charts/shared/DefaultChartTooltip.tsx
179
346
 
180
- function formatTitle(position, timezone) {
347
+ function formatTitle({
348
+ position,
349
+ timezone,
350
+ timeFormat,
351
+ tickPositions,
352
+ textLocale = "en-US"
353
+ }) {
181
354
  if (typeof position === "string") return position;
182
- return new Date(position).toLocaleDateString(void 0, {
183
- year: "numeric",
184
- month: "long",
185
- day: "numeric",
186
- weekday: "long",
187
- timeZone: timezone
355
+ return datetimeTitleFormatter({
356
+ value: position,
357
+ tickPositions,
358
+ timezone,
359
+ timeFormat,
360
+ textLocale
188
361
  });
189
362
  }
190
363
  function AnnotationHeader({
@@ -209,6 +382,9 @@ function DefaultChartTooltip({
209
382
  hasOnClick,
210
383
  tooltipClickLabel,
211
384
  timezone,
385
+ timeFormat,
386
+ tickPositions,
387
+ textLocale,
212
388
  invalidNumberLabel,
213
389
  valueFormat = {},
214
390
  hideTitle,
@@ -265,7 +441,13 @@ function DefaultChartTooltip({
265
441
  description: annotation.description
266
442
  }
267
443
  ) : null,
268
- hideTitle ? null : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2H2Z7PKZjs.ChartTooltipTitle, { children: formatTitle(position, timezone) }),
444
+ hideTitle ? null : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2H2Z7PKZjs.ChartTooltipTitle, { children: formatTitle({
445
+ position,
446
+ timezone,
447
+ timeFormat,
448
+ tickPositions,
449
+ textLocale
450
+ }) }),
269
451
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
270
452
  _chunk2H2Z7PKZjs.ChartTooltipTable,
271
453
  {
@@ -376,7 +558,7 @@ function buildExportChartOptions({
376
558
  colors,
377
559
  exportTitle: rawTitle
378
560
  }) {
379
- const exportTitle = _optionalChain([rawTitle, 'optionalAccess', _6 => _6.trim, 'call', _7 => _7()]);
561
+ const exportTitle = _optionalChain([rawTitle, 'optionalAccess', _18 => _18.trim, 'call', _19 => _19()]);
380
562
  const axisOptions = {
381
563
  lineColor: theme.colors.container.border.base,
382
564
  gridLineColor: theme.colors.container.border.base,
@@ -448,7 +630,7 @@ function buildExportChartOptions({
448
630
  };
449
631
  }
450
632
  function createChartExportHandle(chart, getExportChartOptions) {
451
- const resolveName = (filename) => _nullishCoalesce(filename, () => ( defaultFilename(_optionalChain([chart, 'access', _8 => _8.options, 'access', _9 => _9.chart, 'optionalAccess', _10 => _10.type]), /* @__PURE__ */ new Date())));
633
+ const resolveName = (filename) => _nullishCoalesce(filename, () => ( defaultFilename(_optionalChain([chart, 'access', _20 => _20.options, 'access', _21 => _21.chart, 'optionalAccess', _22 => _22.type]), /* @__PURE__ */ new Date())));
452
634
  const exportImage = async (type, filename) => {
453
635
  chart.exportChartLocal(
454
636
  { type, filename: resolveName(filename) },
@@ -483,7 +665,7 @@ function ChartRenderer({
483
665
  annotationLookup,
484
666
  hasOnClick,
485
667
  tooltipClickLabel,
486
- timezone,
668
+ tooltipOptions,
487
669
  invalidNumberLabel,
488
670
  valueFormat,
489
671
  hideLegend = false,
@@ -498,7 +680,7 @@ function ChartRenderer({
498
680
  const exportThemeRef = _react.useRef.call(void 0, { theme, colors, exportTitle });
499
681
  exportThemeRef.current = { theme, colors, exportTitle };
500
682
  const captureChart = _react.useCallback.call(void 0, (chartInstance) => {
501
- const forExport = _optionalChain([chartInstance, 'access', _11 => _11.options, 'access', _12 => _12.chart, 'optionalAccess', _13 => _13.forExport]);
683
+ const forExport = _optionalChain([chartInstance, 'access', _23 => _23.options, 'access', _24 => _24.chart, 'optionalAccess', _25 => _25.forExport]);
502
684
  if (!forExport) {
503
685
  setChart(chartInstance);
504
686
  setPlotHeight(chartInstance.plotHeight);
@@ -508,7 +690,7 @@ function ChartRenderer({
508
690
  onReadyRef.current = onReady;
509
691
  _react.useEffect.call(void 0, () => {
510
692
  if (!chart) return;
511
- _optionalChain([onReadyRef, 'access', _14 => _14.current, 'optionalCall', _15 => _15(
693
+ _optionalChain([onReadyRef, 'access', _26 => _26.current, 'optionalCall', _27 => _27(
512
694
  createChartExportHandle(
513
695
  chart,
514
696
  () => buildExportChartOptions(exportThemeRef.current)
@@ -524,18 +706,18 @@ function ChartRenderer({
524
706
  return () => unbind();
525
707
  }, [chart]);
526
708
  const highchartsOptions = _react.useMemo.call(void 0, () => {
527
- const pointDescriptionFormatter = _optionalChain([options, 'access', _16 => _16.accessibility, 'optionalAccess', _17 => _17.point, 'optionalAccess', _18 => _18.descriptionFormatter]);
528
- const svgContainerLabel = _optionalChain([options, 'access', _19 => _19.accessibility, 'optionalAccess', _20 => _20.svgContainerLabel]);
529
- const packedBubbleDataLabelFormatter = _optionalChain([options, 'access', _21 => _21.plotOptions, 'optionalAccess', _22 => _22.packedbubble, 'optionalAccess', _23 => _23.dataLabels, 'optionalAccess', _24 => _24.formatter]);
709
+ const pointDescriptionFormatter = _optionalChain([options, 'access', _28 => _28.accessibility, 'optionalAccess', _29 => _29.point, 'optionalAccess', _30 => _30.descriptionFormatter]);
710
+ const svgContainerLabel = _optionalChain([options, 'access', _31 => _31.accessibility, 'optionalAccess', _32 => _32.svgContainerLabel]);
711
+ const packedBubbleDataLabelFormatter = _optionalChain([options, 'access', _33 => _33.plotOptions, 'optionalAccess', _34 => _34.packedbubble, 'optionalAccess', _35 => _35.dataLabels, 'optionalAccess', _36 => _36.formatter]);
530
712
  return {
531
713
  ...options,
532
714
  ...packedBubbleDataLabelFormatter ? {
533
715
  plotOptions: {
534
716
  ...options.plotOptions,
535
717
  packedbubble: {
536
- ..._optionalChain([options, 'access', _25 => _25.plotOptions, 'optionalAccess', _26 => _26.packedbubble]),
718
+ ..._optionalChain([options, 'access', _37 => _37.plotOptions, 'optionalAccess', _38 => _38.packedbubble]),
537
719
  dataLabels: {
538
- ..._optionalChain([options, 'access', _27 => _27.plotOptions, 'optionalAccess', _28 => _28.packedbubble, 'optionalAccess', _29 => _29.dataLabels]),
720
+ ..._optionalChain([options, 'access', _39 => _39.plotOptions, 'optionalAccess', _40 => _40.packedbubble, 'optionalAccess', _41 => _41.dataLabels]),
539
721
  formatter() {
540
722
  const bubblePoint = this.point;
541
723
  return packedBubbleDataLabelFormatter.call({
@@ -545,7 +727,7 @@ function ChartRenderer({
545
727
  index: bubblePoint.index,
546
728
  series: { index: bubblePoint.series.index }
547
729
  },
548
- radius: _nullishCoalesce(_optionalChain([bubblePoint, 'access', _30 => _30.marker, 'optionalAccess', _31 => _31.radius]), () => ( 0))
730
+ radius: _nullishCoalesce(_optionalChain([bubblePoint, 'access', _42 => _42.marker, 'optionalAccess', _43 => _43.radius]), () => ( 0))
549
731
  });
550
732
  }
551
733
  }
@@ -553,7 +735,7 @@ function ChartRenderer({
553
735
  }
554
736
  } : {},
555
737
  accessibility: {
556
- description: _optionalChain([options, 'access', _32 => _32.accessibility, 'optionalAccess', _33 => _33.description]),
738
+ description: _optionalChain([options, 'access', _44 => _44.accessibility, 'optionalAccess', _45 => _45.description]),
557
739
  ...pointDescriptionFormatter ? {
558
740
  point: {
559
741
  descriptionFormatter: (point) => {
@@ -580,7 +762,7 @@ function ChartRenderer({
580
762
  }, [options]);
581
763
  if (series.length === 0) return null;
582
764
  const chartWithAnnotations = chart;
583
- const hasRenderedAnnotations = !!_optionalChain([chartWithAnnotations, 'optionalAccess', _34 => _34.annotations, 'optionalAccess', _35 => _35.length]) && !!_optionalChain([annotationLookup, 'optionalAccess', _36 => _36.size]) && plotHeight > 0;
765
+ const hasRenderedAnnotations = !!_optionalChain([chartWithAnnotations, 'optionalAccess', _46 => _46.annotations, 'optionalAccess', _47 => _47.length]) && !!_optionalChain([annotationLookup, 'optionalAccess', _48 => _48.size]) && plotHeight > 0;
584
766
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
585
767
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2H2Z7PKZjs.GlobalChartStyleOverrides, {}),
586
768
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
@@ -606,7 +788,7 @@ function ChartRenderer({
606
788
  chart,
607
789
  renderContent: (context) => {
608
790
  const ctx = context;
609
- const isPackedBubble = !ctx.points && _optionalChain([ctx, 'access', _37 => _37.series, 'optionalAccess', _38 => _38.type]) === "packedbubble";
791
+ const isPackedBubble = !ctx.points && _optionalChain([ctx, 'access', _49 => _49.series, 'optionalAccess', _50 => _50.type]) === "packedbubble";
610
792
  const isPie = !ctx.points && !isPackedBubble;
611
793
  const data = isPackedBubble ? (
612
794
  // Mirrors v1's `transformBubbleChartTooltipData`: color comes from
@@ -616,7 +798,7 @@ function ChartRenderer({
616
798
  color: _nullishCoalesce((ctx.colorIndex !== void 0 ? colors[ctx.colorIndex] : void 0), () => ( "")),
617
799
  name: _nullishCoalesce(ctx.key, () => ( ctx.point.name)),
618
800
  value: ctx.point.y,
619
- seriesIndex: _optionalChain([ctx, 'access', _39 => _39.series, 'optionalAccess', _40 => _40.index]),
801
+ seriesIndex: _optionalChain([ctx, 'access', _51 => _51.series, 'optionalAccess', _52 => _52.index]),
620
802
  pointIndex: ctx.point.index
621
803
  }
622
804
  ]
@@ -644,7 +826,8 @@ function ChartRenderer({
644
826
  }))
645
827
  );
646
828
  const position = isPie || isPackedBubble ? _nullishCoalesce(ctx.key, () => ( ctx.point.name)) : context.x;
647
- const resolved = _optionalChain([annotationLookup, 'optionalAccess', _41 => _41.get, 'call', _42 => _42(context.point.x)]);
829
+ const tickPositions = _optionalChain([ctx, 'access', _53 => _53.points, 'optionalAccess', _54 => _54[0], 'optionalAccess', _55 => _55.series, 'optionalAccess', _56 => _56.xAxis, 'optionalAccess', _57 => _57.tickPositions]);
830
+ const resolved = _optionalChain([annotationLookup, 'optionalAccess', _58 => _58.get, 'call', _59 => _59(context.point.x)]);
648
831
  const annotation = resolved ? {
649
832
  icon: resolved.icon,
650
833
  color: resolved.color,
@@ -659,7 +842,8 @@ function ChartRenderer({
659
842
  annotation,
660
843
  hasOnClick,
661
844
  tooltipClickLabel,
662
- timezone,
845
+ ...tooltipOptions,
846
+ tickPositions,
663
847
  invalidNumberLabel,
664
848
  valueFormat
665
849
  });
@@ -718,5 +902,6 @@ function buildBaseChartOptions({
718
902
 
719
903
 
720
904
 
721
- exports.valueFormatter = valueFormatter; exports.useSeedsChartSetup = useSeedsChartSetup; exports.ChartRenderer = ChartRenderer; exports.buildBaseChartOptions = buildBaseChartOptions;
722
- //# sourceMappingURL=chunk-GQOKCB3M.js.map
905
+
906
+ exports.datetimeFormatter = datetimeFormatter; exports.valueFormatter = valueFormatter; exports.useSeedsChartSetup = useSeedsChartSetup; exports.ChartRenderer = ChartRenderer; exports.buildBaseChartOptions = buildBaseChartOptions;
907
+ //# sourceMappingURL=chunk-3P6MPBSZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/_work/seeds/seeds/seeds-react/seeds-react-data-viz/dist/chunk-3P6MPBSZ.js","../src/charts/shared/chartBase.tsx","../src/charts/shared/ChartAnnotationMarkerPortal.tsx","../src/charts/shared/DefaultChartTooltip.tsx","../src/charts/shared/formatters/datetimeFormatter.ts","../src/charts/shared/formatters/valueFormatter.ts","../src/charts/shared/SeriesLegend.tsx","../src/charts/shared/chartExport.ts","../src/charts/shared/baseChartOptions.ts"],"names":["ChartAnnotationMarkerPortal","Box","Icon","memo","jsx"],"mappings":"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","file":"/home/runner/_work/seeds/seeds/seeds-react/seeds-react-data-viz/dist/chunk-3P6MPBSZ.js","sourcesContent":[null,"// Engine boundary for the v2 chart architecture. The only file under `charts/`\n// that imports from \"highcharts\". See `charts/README.md` for invariants.\n\nimport Highcharts from \"highcharts\";\nimport { HighchartsReact } from \"highcharts-react-official\";\nimport highchartsAccessibility from \"highcharts/modules/accessibility\";\nimport highchartsAnnotations from \"highcharts/modules/annotations\";\nimport highchartsExporting from \"highcharts/modules/exporting\";\nimport highchartsOfflineExporting from \"highcharts/modules/offline-exporting\";\nimport highchartsExportData from \"highcharts/modules/export-data\";\nimport highchartsMore from \"highcharts/highcharts-more\";\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { useTheme } from \"styled-components\";\n\nimport { ChartTooltipPortal } from \"../../components/ChartTooltip/components/ChartTooltipPortal\";\nimport { transformTimeSeriesTooltipData } from \"../../helpers\";\nimport { GlobalChartStyleOverrides } from \"../../styles/chartStyles\";\nimport { ChartAnnotationMarkerPortal } from \"./ChartAnnotationMarkerPortal\";\nimport { DefaultChartTooltip } from \"./DefaultChartTooltip\";\nimport { SeriesLegend } from \"./SeriesLegend\";\nimport type { TypeIconName } from \"@sproutsocial/seeds-react-icon\";\nimport type { TypeTheme } from \"@sproutsocial/seeds-react-theme\";\nimport { defaultFilename } from \"./chartExport\";\nimport type { ChartAnnotationLookup } from \"./annotations\";\nimport type { DatetimeTimeFormat } from \"./formatters/datetimeFormatter\";\nimport type { ValueFormat } from \"./formatters/valueFormatter\";\nimport type { ChartExportHandle } from \"./chartExport\";\nimport type { TypeChartStyleColor, TypeChartStylePattern } from \"../../types\";\n\nhighchartsAccessibility(Highcharts);\nhighchartsAnnotations(Highcharts);\n// `exporting` must register before `offline-exporting` and `export-data`, which\n// extend it. `exporting` adds a native context (hamburger) button by default;\n// `ChartRenderer` disables it on the chart options so v2 charts expose export\n// only through the `onReady` handle. Export is client-side/offline only —\n// we never call the Highcharts export server.\nhighchartsExporting(Highcharts);\nhighchartsOfflineExporting(Highcharts);\nhighchartsExportData(Highcharts);\n// `highcharts-more` adds the packedbubble series type. Bubble is the first v2\n// family to need it — no other family registers or depends on this module.\nhighchartsMore(Highcharts);\n\n/** Highcharts series type. `column` is vertical bars; `bar` is horizontal bars; the line/area family adds the rest; `pie` is donut/pie; `packedbubble` is the clustered bubble family. */\nexport type SeedsChartType =\n | \"column\"\n | \"bar\"\n | \"line\"\n | \"spline\"\n | \"area\"\n | \"areaspline\"\n | \"pie\"\n | \"packedbubble\";\n\nexport interface SeedsChartMarkerOptions {\n enabled?: boolean;\n symbol?: \"circle\";\n}\n\n/** Object form of a series point — carries an optional per-point `marker` override. */\nexport interface SeedsChartDataPoint {\n x?: number;\n y: number | null;\n marker?: SeedsChartMarkerOptions;\n}\n\nexport interface SeedsChartSeriesOptions {\n type: SeedsChartType;\n name: string;\n data: Array<number | null | [number, number | null] | SeedsChartDataPoint>;\n color?: string;\n}\n\n/**\n * Seeds-local shape of the `this` context Highcharts binds when invoking an\n * axis-label `formatter`. Models only the fields our default formatter reads\n * (`value` and `axis.tickPositions`).\n */\nexport interface SeedsAxisLabelFormatterContext {\n value: number | string;\n axis: { tickPositions?: ReadonlyArray<number> };\n}\n\nexport interface SeedsChartAxisLabelOptions {\n useHTML?: boolean;\n /** Show the axis tick labels. Maps to Highcharts' `labels.enabled`; defaults to shown. */\n enabled?: boolean;\n /** `this`-bound tick-label formatter returning the rendered string. */\n formatter?: (this: SeedsAxisLabelFormatterContext) => string;\n}\n\n/**\n * Highcharts datetime tick-label formats, keyed by the auto-selected tick unit.\n * Only the units the chart families set are modeled; Highcharts merges the rest\n * with its own defaults.\n */\nexport interface SeedsChartDateTimeLabelFormats {\n hour?: string;\n day?: string;\n week?: string;\n month?: string;\n year?: string;\n}\n\nexport interface SeedsChartAxisTitleOptions {\n text: string;\n}\n\nexport interface SeedsChartXAxisOptions {\n type?: \"category\" | \"datetime\" | \"linear\";\n categories?: Array<string>;\n crosshair?: boolean;\n min?: number;\n max?: number;\n /** Hide the axis line, ticks, and labels entirely (e.g. sparklines). Maps to Highcharts' `visible`. */\n visible?: boolean;\n labels?: SeedsChartAxisLabelOptions;\n dateTimeLabelFormats?: SeedsChartDateTimeLabelFormats;\n}\n\nexport interface SeedsChartYAxisOptions {\n min?: number;\n max?: number;\n /** Soft minimum — the axis won't start above this value, but may extend below it for negative data. */\n softMin?: number;\n /** Soft maximum — the axis won't end below this value, but may extend above it. */\n softMax?: number;\n gridLineWidth?: number;\n /** Hide the axis line, ticks, and labels entirely (e.g. sparklines). Maps to Highcharts' `visible`. */\n visible?: boolean;\n title?: SeedsChartAxisTitleOptions;\n labels?: SeedsChartAxisLabelOptions;\n /** Reference lines drawn across the plot area (e.g. the 0 baseline). `color` is only needed for the styledMode:false export clone, which can't reach scoped CSS. */\n plotLines?: Array<{\n value: number;\n className?: string;\n color?: string;\n zIndex?: number;\n }>;\n}\n\nexport interface SeedsChartTooltipOptions {\n enabled?: boolean;\n // `useHTML` is intentionally not here — it's hardcoded `true` at the engine\n // boundary (ChartRenderer) because the tooltip portal can't work without it.\n outside?: boolean;\n padding?: number;\n shape?: \"rect\" | \"callout\";\n shared?: boolean;\n hideDelay?: number;\n}\n\nexport interface SeedsChartSeriesPlotOptions {\n animation?: boolean;\n /** Stacking applied across all series. Used by the line/area family for `stacked-area`. */\n stacking?: \"normal\" | \"percent\";\n /** Point-level event wiring (e.g. click). The events shape is family-agnostic. */\n point?: { events?: SeedsChartPointEvents };\n /** Default point-marker visibility. The line/area family disables markers and re-enables them per isolated point. */\n marker?: SeedsChartMarkerOptions;\n}\n\nexport interface SeedsChartPointEventContext {\n x: number | string;\n series: {\n name: string;\n index: number;\n chart: {\n series: ReadonlyArray<{\n type: string;\n data: ReadonlyArray<{\n x: number | string;\n graphic?: { element?: Element };\n }>;\n }>;\n };\n };\n}\n\nexport interface SeedsChartPointEvents {\n mouseOver?: (this: SeedsChartPointEventContext) => void;\n mouseOut?: (this: SeedsChartPointEventContext) => void;\n click?: (event: { point: { x: number | string } }) => void;\n}\n\nexport interface SeedsChartColumnPointOptions {\n events?: SeedsChartPointEvents;\n}\n\nexport interface SeedsChartColumnPlotOptions {\n stacking?: \"normal\" | \"percent\";\n pointPadding?: number;\n groupPadding?: number;\n pointWidth?: number;\n borderRadius?: number;\n borderWidth?: number;\n point?: SeedsChartColumnPointOptions;\n}\n\n/** Type-scoped stacking for filled line-area variants (parity with `column`). */\nexport interface SeedsChartAreaPlotOptions {\n stacking?: \"normal\" | \"percent\";\n}\n\nexport interface SeedsChartPiePlotOptions {\n /** `\"60%\"` for donut (hollow center), `0` for a solid pie. */\n innerSize?: string | number;\n animation?: boolean;\n /** Must be set in JS (not CSS) — Highcharts computes the arc path before CSS is applied. */\n borderRadius?: number;\n dataLabels?: { enabled?: boolean };\n states?: { inactive?: { opacity?: number } };\n /** Pie point events: `this` is the Highcharts Point object (carries `name`, etc.). */\n point?: {\n events?: {\n click?: (this: { name: string }) => void;\n mouseOver?: (this: { name: string }) => void;\n mouseOut?: (this: { name: string }) => void;\n };\n };\n}\n\n/** A single data point in a pie/donut series. Seeds-vocab — no Highcharts imports. */\nexport interface PiePoint {\n name: string;\n y: number | null;\n color?: string;\n}\n\n/** Series options for pie/donut charts. `data` is per-point objects, not x/y tuples. */\nexport interface SeedsChartPieSeriesOptions {\n type: \"pie\";\n name: string;\n data: Array<PiePoint>;\n color?: string;\n}\n\n/** A single inner bubble within a packedbubble outer (category) series. Seeds-vocab — no Highcharts imports. */\nexport interface SeedsChartBubbleDataPoint {\n name: string;\n value: number;\n}\n\n/**\n * One outer (category) series for a packedbubble chart. `color`/`colorIndex`\n * follow the same raw-override convention as `getChartContainerProps`: an\n * explicit `color` inlines a `--highcharts-color-N` override, otherwise the\n * CSS default takes over. `colorIndex` pins the styledMode CSS class per\n * category (mirrors v1) so inner bubbles inherit their outer category's color.\n */\nexport interface SeedsChartPackedBubbleSeriesOptions {\n type: \"packedbubble\";\n name: string;\n data: Array<SeedsChartBubbleDataPoint>;\n color?: string;\n colorIndex?: number;\n}\n\n/**\n * `this`-bound context for a packedbubble `dataLabels.formatter`. `point.index`/\n * `point.series.index` identify which inner bubble this is (same shape as\n * `SeedsChartBubbleClickEvent.point`, both real, typed Highcharts Point\n * fields). `radius` is genuine base-level extraction, not a pass-through: it\n * reads Highcharts' computed `point.marker.radius`, which is real at runtime\n * but untyped in Highcharts' own `.d.ts` (same field v1 read via its own\n * `@ts-expect-error`) — so `ChartRenderer` wraps the formatter to extract it,\n * the same pattern used for `accessibility.point.descriptionFormatter`.\n */\nexport interface SeedsChartBubbleDataLabelFormatterContext {\n point: {\n name: string;\n value: number;\n index: number;\n series: { index: number };\n };\n radius: number;\n}\n\n/**\n * Point-click event shape for a packedbubble point. Structurally compatible\n * with Highcharts' real click event (stopPropagation/type/target distinguish\n * keyboard activation from a mouse click; see the family adapter's transform).\n */\nexport interface SeedsChartBubbleClickEvent {\n type: string;\n pageX?: number;\n pageY?: number;\n stopPropagation?: () => void;\n target?: EventTarget | null;\n point: {\n name: string;\n value: number;\n index: number;\n series: { index: number };\n };\n}\n\nexport interface SeedsChartPackedBubblePlotOptions {\n maxSize?: string | number;\n minSize?: string | number;\n draggable?: boolean;\n layoutAlgorithm?: {\n bubblePadding?: number;\n splitSeries?: boolean;\n gravitationalConstant?: number;\n seriesInteraction?: boolean;\n parentNodeLimit?: boolean;\n enableSimulation?: boolean;\n };\n dataLabels?: {\n enabled?: boolean;\n // Known gap (mirrors bar's axis-label useHTML, see charts/README.md):\n // v1's data-label formatter returns HTML (icon + wrapped text), which SVG\n // <text> can't render. Kept for functional parity — not a portal target,\n // so it's an exception to the useHTML rule, not a new portal use.\n useHTML?: boolean;\n formatter?: (this: SeedsChartBubbleDataLabelFormatterContext) => string;\n };\n states?: { hover?: { enabled?: boolean } };\n /** Per-series accessibility config — required for Highcharts to emit focusable `.highcharts-a11y-proxy-button`s per point. */\n accessibility?: {\n enabled?: boolean;\n keyboardNavigation?: { enabled?: boolean };\n point?: { valueDescriptionFormat?: string };\n };\n point?: {\n events?: {\n click?: (event: SeedsChartBubbleClickEvent) => void;\n mouseOver?: (this: { name: string }) => void;\n mouseOut?: (this: { name: string }) => void;\n };\n };\n}\n\nexport interface SeedsChartPlotOptions {\n series?: SeedsChartSeriesPlotOptions;\n // `column` (vertical bars) and `bar` (horizontal bars) take the same option\n // shape; the bar adapter assigns one config under whichever key is active.\n column?: SeedsChartColumnPlotOptions;\n bar?: SeedsChartColumnPlotOptions;\n area?: SeedsChartAreaPlotOptions;\n areaspline?: SeedsChartAreaPlotOptions;\n pie?: SeedsChartPiePlotOptions;\n packedbubble?: SeedsChartPackedBubblePlotOptions;\n}\n\nexport interface SeedsChartChartOptions {\n type: SeedsChartType;\n styledMode?: boolean;\n animation?: boolean;\n marginTop?: number;\n height?: number;\n /** `[top, right, bottom, left]` chart spacing. Donut zeroes it so the ring fills the height; sparklines zero it so the plot fills the container. */\n spacing?: [number, number, number, number];\n}\n\nexport interface SeedsChartAnnotationLabelOptions {\n point: { x: number; y: number; xAxis: 0; yAxis: 0 };\n /** Highcharts shows a default label if this is empty; pass `\" \"` to suppress. */\n text?: string;\n}\n\n/**\n * Shared options applied to every label in a Highcharts annotation block.\n * `useHTML: true` is load-bearing — it makes Highcharts attach an HTML\n * `graphic.div` to each label, which `ChartAnnotationMarkerPortal` portals\n * React content into. Without it, labels render as SVG `<text>` and the\n * portal finds no containers.\n */\nexport interface SeedsChartAnnotationLabelOptionsShared {\n useHTML?: boolean;\n padding?: number;\n}\n\nexport interface SeedsChartAnnotationsOptions {\n labels?: SeedsChartAnnotationLabelOptions[];\n labelOptions?: SeedsChartAnnotationLabelOptionsShared;\n}\n\nexport interface SeedsChartAccessibilityOptions {\n description?: string;\n /** The chart region's accessible name. Maps to Highcharts' `lang.accessibility.svgContainerLabel`, not `accessibility.*` — `ChartRenderer` performs that redirection at the engine boundary. */\n svgContainerLabel?: string;\n point?: {\n /**\n * Seeds-vocab per-point description for keyboard/proxy-button activation.\n * `ChartRenderer` wraps this into Highcharts' real\n * `accessibility.point.descriptionFormatter`, extracting `name`/`value`\n * from the live `Point` (only `chartBase` may touch that type).\n */\n descriptionFormatter?: (point: { name: string; value: number }) => string;\n };\n}\n\nexport interface SeedsChartTitleOptions {\n text?: string;\n}\n\nexport interface SeedsChartCreditsOptions {\n enabled?: boolean;\n}\n\nexport interface SeedsChartLegendOptions {\n enabled?: boolean;\n}\n\n/** Maps to Highcharts `time` — chart-global; `timezone` only affects datetime axes. */\nexport interface SeedsChartTimeOptions {\n timezone?: string;\n}\n\nexport interface SeedsChartOptions {\n chart?: SeedsChartChartOptions;\n accessibility?: SeedsChartAccessibilityOptions;\n title?: SeedsChartTitleOptions;\n credits?: SeedsChartCreditsOptions;\n legend?: SeedsChartLegendOptions;\n tooltip?: SeedsChartTooltipOptions;\n time?: SeedsChartTimeOptions;\n xAxis?: SeedsChartXAxisOptions;\n yAxis?: SeedsChartYAxisOptions;\n plotOptions?: SeedsChartPlotOptions;\n series?: Array<\n | SeedsChartSeriesOptions\n | SeedsChartPieSeriesOptions\n | SeedsChartPackedBubbleSeriesOptions\n >;\n annotations?: SeedsChartAnnotationsOptions[];\n}\n\n/**\n * A summed Total is only meaningful when the chart stacks its series (a stack\n * is, by definition, an additive composition). Returns true if any plotOptions\n * slot (series/column/bar/area/areaspline) carries a truthy `stacking`. Kept\n * key-agnostic so new stacked families are covered without changes here.\n */\nexport function isChartStacked(options: SeedsChartOptions): boolean {\n const plotOptions = options.plotOptions;\n if (!plotOptions) return false;\n return Object.values(plotOptions).some(\n (slot) =>\n slot != null &&\n typeof slot === \"object\" &&\n Boolean((slot as { stacking?: unknown }).stacking)\n );\n}\n\nexport interface SeedsChartSetupSeriesInput {\n color?: string;\n pattern?: TypeChartStylePattern;\n}\n\nexport interface UseSeedsChartSetupParams<\n T extends SeedsChartSetupSeriesInput = SeedsChartSetupSeriesInput\n> {\n series: Array<T>;\n /** Opt-in for the pattern array. Families that apply patterns (line, area) pass `true`. */\n includePatterns?: boolean;\n /**\n * Resolved effective cap (the family default, or the consumer's explicit\n * value). Series beyond it are dropped. Omit to disable capping entirely.\n */\n seriesLimit?: number;\n /**\n * Whether the consumer set `seriesLimit` explicitly. Suppresses the over-limit\n * warning (an explicit cap is intentional, not an accidental overflow).\n * Default `false`.\n */\n isSeriesLimitOverridden?: boolean;\n /** Opt out of the over-limit warning. Default `false`. */\n hideSeriesLimitWarning?: boolean;\n /** Component name used in the over-limit warning message (kept generic for future families). */\n componentName?: string;\n}\n\nexport interface UseSeedsChartSetupResult<\n T extends SeedsChartSetupSeriesInput = SeedsChartSetupSeriesInput\n> {\n theme: TypeTheme;\n colors: Array<TypeChartStyleColor>;\n patterns: Array<TypeChartStylePattern>;\n /** Series after applying the (optional) cap. Referentially equal to the input when no cap applies. */\n series: Array<T>;\n}\n\nexport function useSeedsChartSetup<\n T extends SeedsChartSetupSeriesInput = SeedsChartSetupSeriesInput\n>({\n series,\n includePatterns = false,\n seriesLimit,\n isSeriesLimitOverridden = false,\n hideSeriesLimitWarning = false,\n componentName,\n}: UseSeedsChartSetupParams<T>): UseSeedsChartSetupResult<T> {\n const theme = useTheme() as TypeTheme;\n const palette = theme.colors.DATAVIZ_COLORS_LIST;\n\n // Cap the series at the resolved limit. Memoized so the input reference is\n // preserved when no cap applies, keeping the colors/patterns memos\n // referentially stable.\n const cappedSeries = useMemo<Array<T>>(\n () =>\n seriesLimit !== undefined &&\n seriesLimit >= 1 &&\n series.length > seriesLimit\n ? series.slice(0, seriesLimit)\n : series,\n [series, seriesLimit]\n );\n\n // Warn when the limit was exceeded, was not explicitly overridden by the\n // consumer, and the warning isn't suppressed. Keyed off the gating conditions\n // so it fires when those change rather than on every render.\n useEffect(() => {\n if (\n seriesLimit !== undefined &&\n seriesLimit >= 1 &&\n series.length > seriesLimit &&\n !isSeriesLimitOverridden &&\n !hideSeriesLimitWarning &&\n componentName != null\n ) {\n console.warn(\n `${componentName}: Maximum number of series (${seriesLimit}) exceeded. Only the first ${seriesLimit} series will be displayed.`\n );\n }\n }, [\n series.length,\n seriesLimit,\n isSeriesLimitOverridden,\n hideSeriesLimitWarning,\n componentName,\n ]);\n\n const colors = useMemo<Array<TypeChartStyleColor>>(() => {\n const out: TypeChartStyleColor[] = [];\n const paletteLen = palette.length;\n for (let i = 0; i < cappedSeries.length; i++) {\n const s = cappedSeries[i]!;\n out.push(s.color ?? palette[i % paletteLen] ?? \"\");\n }\n return out;\n }, [cappedSeries, palette]);\n\n const patterns = useMemo<Array<TypeChartStylePattern>>(() => {\n if (!includePatterns) return [];\n const out: TypeChartStylePattern[] = [];\n for (let i = 0; i < cappedSeries.length; i++) {\n const s = cappedSeries[i]!;\n out.push(s.pattern ?? \"solid\");\n }\n return out;\n }, [cappedSeries, includePatterns]);\n\n return { theme, colors, patterns, series: cappedSeries };\n}\n\nexport interface ChartRendererSeries {\n name: string;\n color?: string;\n /** Seeds icon or partner-logo name rendered before the series name in the legend label. */\n icon?: TypeIconName;\n}\n\nexport interface ChartTooltipDataRow {\n color: TypeChartStyleColor;\n name: string;\n value: number | null;\n /** Slice percentage — populated by pie/donut tooltips; `undefined` for bar/line-area rows. */\n percent?: number;\n /** Outer category index — populated by bubble tooltips only; `undefined` elsewhere. */\n seriesIndex?: number;\n /** Inner bubble index — populated by bubble tooltips only; `undefined` elsewhere. */\n pointIndex?: number;\n}\n\n/** Tooltip render-prop signature for v2 charts. */\nexport interface ChartTooltipProps {\n data: ChartTooltipDataRow[];\n /** Resolved x-axis position. Category name for category axes, numeric for time/linear. */\n position: number | string;\n /** Suppress the default tooltip's title row. Set for pie/donut, which have no x-axis position — the title would just duplicate the single slice's name. */\n hideTitle?: boolean;\n /** Resolved chart timezone — pass to date formatters when `position` is a numeric timestamp. Undefined on category axes. */\n timezone?: string;\n /** Resolved datetime-axis `timeFormat` (\"12\" | \"24\"). Governs the hour-granularity date title. Undefined on category axes. */\n timeFormat?: DatetimeTimeFormat;\n /** Full x-axis tick positions, extracted at the engine boundary — used to derive the title's granularity (day vs month vs hour …). Undefined on category axes. */\n tickPositions?: readonly number[];\n /** Locale for the date title's Intl formatting. Defaults to \"en-US\" (see CE-36); threaded internally, not a consumer-facing prop today (CE-35). */\n textLocale?: Intl.LocalesArgument;\n /** Declarative annotation anchored at this position, if any. Rendered in the tooltip header. */\n annotation?: {\n icon?: TypeIconName;\n color?: string;\n title?: string;\n description?: string;\n };\n /** True when the chart has an `onClick` handler — used by the default tooltip to render the click-label footer. */\n hasOnClick?: boolean;\n /** Footer content shown in the default tooltip when the chart is clickable. */\n tooltipClickLabel?: ReactNode;\n /** Content shown in place of a row's value when the value is null. Defaults to an em-dash. */\n invalidNumberLabel?: ReactNode;\n /** Declarative value format for the tooltip value cell. Omit for raw rendering. Applied at full precision (abbreviation off). */\n valueFormat?: ValueFormat;\n}\n\nexport interface ChartRendererProps {\n options: SeedsChartOptions;\n series: Array<ChartRendererSeries>;\n /** Theme-aware palette resolved by `useSeedsChartSetup`. Used as the swatch color for any series that doesn't carry an explicit `color`. */\n colors: ReadonlyArray<TypeChartStyleColor>;\n /** Override for the default tooltip content. Portal is always rendered — `GlobalChartStyleOverrides` makes Highcharts' native tooltip box transparent. */\n tooltip?: (args: ChartTooltipProps) => ReactNode;\n /**\n * Resolved annotations keyed by numeric x. Drives both the in-chart marker\n * portal and the tooltip-header `annotation` injection. The family component\n * builds this map (resolving category-string positions to indices); the\n * renderer is axis-type agnostic.\n */\n annotationLookup?: ChartAnnotationLookup;\n /** Forwarded into the default tooltip so it can render the click-label footer. */\n hasOnClick?: boolean;\n /** Forwarded into the default tooltip for click-label footer rendering. */\n tooltipClickLabel?: ReactNode;\n /** Resolved datetime-derived tooltip options (timezone / timeFormat / textLocale), forwarded to the tooltip so its date title matches the axis ticks. All undefined on category axes; omitted entirely by families with no datetime axis (pie/donut, bubble). */\n tooltipOptions?: Pick<\n ChartTooltipProps,\n \"timezone\" | \"timeFormat\" | \"textLocale\"\n >;\n /** Forwarded into the default tooltip as the null-value override. */\n invalidNumberLabel?: ReactNode;\n /** Forwarded into the default tooltip to format the value cell declaratively. */\n valueFormat?: ValueFormat;\n /**\n * Suppress the Seeds series legend beneath the chart. Default `false` (the\n * legend renders). Decorative charts (e.g. sparklines) pass `true` to hide it.\n */\n hideLegend?: boolean;\n /**\n * Chart title baked into the exported image only. The on-screen title is a\n * React sibling outside the chart SVG, so it can't be captured on export;\n * `exportTitle` threads the text into the `styledMode:false` export clone as a\n * themed native Highcharts title. It never renders on the live chart. Omit for\n * no title.\n */\n exportTitle?: string;\n /**\n * Fired once per chart instance with an opaque {@link ChartExportHandle} for\n * triggering PNG/SVG/PDF/CSV downloads. Re-fires only on a genuine remount,\n * never on data or prop updates.\n */\n onReady?: (handle: ChartExportHandle) => void;\n}\n\n/**\n * Runtime shape of one Highcharts annotation label — only the fields the\n * marker portal reads. Re-exported so the portal stays inside the engine\n * boundary (no direct \"highcharts\" import).\n */\nexport interface ChartAnnotationRuntimeLabel {\n graphic?: { div?: HTMLDivElement };\n options?: { point?: { x?: number } };\n}\n\n/** Runtime shape of one Highcharts annotation block (a group of labels). */\nexport interface ChartAnnotationRuntime {\n labels?: ChartAnnotationRuntimeLabel[];\n}\n\n/** Highcharts doesn't include the `annotations` array on its `Chart` type. */\ninterface ChartWithAnnotations extends Highcharts.Chart {\n annotations?: ChartAnnotationRuntime[];\n}\n\n/** Inputs for the `styledMode: false` export clone (see `buildExportChartOptions`). */\ninterface ExportChartOptionsParams {\n theme: TypeTheme;\n colors: ReadonlyArray<TypeChartStyleColor>;\n /** Title baked into the export clone only; omit/blank for no native title. */\n exportTitle?: string;\n}\n\n/**\n * Theme→options map for the `styledMode: false` export clone, which can't see\n * the scoped CSS v2 charts normally style with. Mirrors `styles/chartStyles`.\n */\nfunction buildExportChartOptions({\n theme,\n colors,\n exportTitle: rawTitle,\n}: ExportChartOptionsParams): Highcharts.Options {\n // Trim so a whitespace-only title never reserves title space in the export.\n const exportTitle = rawTitle?.trim();\n // Line, gridline, and label styling are identical for both axes.\n const axisOptions = {\n lineColor: theme.colors.container.border.base,\n gridLineColor: theme.colors.container.border.base,\n labels: {\n style: {\n color: theme.colors.text.subtext,\n fontFamily: theme.fontFamily,\n fontSize: theme.typography[100].fontSize,\n fontWeight: String(theme.fontWeights.normal),\n },\n },\n };\n return {\n chart: {\n styledMode: false,\n backgroundColor: theme.colors.container.background.base,\n },\n // Series without an explicit `color` fall through to this palette by index.\n colors: [...colors],\n // The on-screen legend is a React sibling (`SeriesLegend`) outside the chart\n // SVG, so the export can't capture it. Turn on Highcharts' native legend for\n // the clone instead, themed to approximate the Seeds `ChartLegend` (centered\n // row of swatch + `text.subtext` label). It can't reproduce per-series Seeds\n // icons — those series show colour + name only.\n legend: {\n enabled: true,\n symbolRadius: 4,\n symbolHeight: 12,\n symbolWidth: 12,\n itemStyle: {\n color: theme.colors.text.subtext,\n fontFamily: theme.fontFamily,\n fontSize: theme.typography[200].fontSize,\n fontWeight: String(theme.fontWeights.normal),\n },\n },\n xAxis: {\n ...axisOptions,\n crosshair: {\n color: theme.colors.container.border.decorative.neutral,\n width: 1,\n },\n },\n yAxis: {\n ...axisOptions,\n // The live chart's plotLines (set by the adapter) merge with this by\n // array index, so `className`/`value`/`zIndex` survive — this only adds\n // the color the `y-axis-zero-line` CSS class can't reach in styledMode:false.\n plotLines: [\n { value: 0, color: theme.colors.container.border.decorative.neutral },\n ],\n },\n // The on-screen title is a React sibling (`Text.SmallSubHeadline`) rendered\n // above the chart, outside the SVG, so the export can't capture it. Bake a\n // themed native Highcharts title into the clone instead (same treatment as\n // the legend above), styled to match the Seeds SmallSubHeadline. Guarded so a\n // chart with no title produces no native title in the export.\n ...(exportTitle\n ? {\n title: {\n text: exportTitle,\n style: {\n color: theme.colors.text.headline,\n fontFamily: theme.fontFamily,\n fontSize: theme.typography[200].fontSize,\n fontWeight: String(theme.fontWeights.bold),\n },\n },\n }\n : {}),\n } as Highcharts.Options;\n}\n\n/**\n * Build the opaque export handle for a chart instance. Lives here — not in\n * `chartExport.ts` — because it closes over the live `Highcharts.Chart`, which\n * never leaves the engine boundary. The returned {@link ChartExportHandle} is\n * Highcharts-free.\n *\n * PDF lazy-loads jspdf + svg2pdf.js (a deferred chunk) on first use.\n */\nfunction createChartExportHandle(\n chart: Highcharts.Chart,\n getExportChartOptions: () => Highcharts.Options\n): ChartExportHandle {\n const resolveName = (filename?: string) =>\n filename ?? defaultFilename(chart.options.chart?.type, new Date());\n\n const exportImage = async (\n type: Highcharts.ExportingMimeTypeValue,\n filename?: string\n ): Promise<void> => {\n // Read options at call time, not when the handle was built: a light/dark\n // toggle updates the chart in place without re-firing `onReady`, so the\n // export must reflect the current theme. 2nd arg is deep-merged, so\n // `labels.style` here merges with the live axis `formatter`, not clobbers it.\n chart.exportChartLocal(\n { type, filename: resolveName(filename) },\n getExportChartOptions()\n );\n };\n\n return {\n exportPNG: ({ filename } = {}) => exportImage(\"image/png\", filename),\n exportSVG: ({ filename } = {}) => exportImage(\"image/svg+xml\", filename),\n exportPDF: async ({ filename } = {}): Promise<void> => {\n // Lazy-load jspdf + svg2pdf.js (deferred chunk) and expose `window.jspdf`\n // before exporting, so Highcharts' offline-exporting renders the PDF\n // locally rather than fetching the libs from its CDN.\n const { loadPdfExportLibs } = await import(\"./pdfExportLibs\");\n loadPdfExportLibs();\n await exportImage(\"application/pdf\", filename);\n },\n exportCSV: async ({ filename } = {}): Promise<void> => {\n // `downloadCSV()` reads the filename from `chart.options.exporting.filename`\n // via `getFilename()`. Set it transiently (direct mutation — no `update()`,\n // so no redraw) so the per-call override and generated default both apply,\n // then restore the previous value.\n const exporting = (chart.options.exporting ??= {});\n const previous = exporting.filename;\n exporting.filename = resolveName(filename);\n try {\n chart.downloadCSV();\n } finally {\n exporting.filename = previous;\n }\n },\n };\n}\n\nexport function ChartRenderer({\n options,\n series,\n colors,\n tooltip,\n annotationLookup,\n hasOnClick,\n tooltipClickLabel,\n tooltipOptions,\n invalidNumberLabel,\n valueFormat,\n hideLegend = false,\n exportTitle,\n onReady,\n}: ChartRendererProps) {\n const [chart, setChart] = useState<Highcharts.Chart | null>(null);\n const [plotHeight, setPlotHeight] = useState(0);\n // Wrap the default tooltip so unstacked families (line/spline, unstacked\n // area, grouped bar) suppress the summed Total, which is meaningless there.\n // When a consumer supplies `tooltip`, DefaultChartTooltip is bypassed and\n // never sees `hideTotal` — the escape hatch and public props stay untouched.\n const chartIsStacked = isChartStacked(options);\n const renderTooltip =\n tooltip ??\n ((args) => DefaultChartTooltip({ ...args, hideTotal: !chartIsStacked }));\n const theme = useTheme() as TypeTheme;\n\n // Hold the live theme + palette in a ref so the export handle reads the\n // *current* theme at call time (see `createChartExportHandle`). A dark-mode\n // toggle updates this each render without re-firing `onReady`.\n const exportThemeRef = useRef({ theme, colors, exportTitle });\n exportThemeRef.current = { theme, colors, exportTitle };\n\n // Capture only the live chart. Highcharts reuses a chart's `callback` when it\n // builds the throwaway clone for export (`new chart.constructor(options,\n // chart.callback)` in exporting/offline-exporting, with `chart.forExport:\n // true`). That clone is destroyed the instant its SVG is captured, so binding\n // React state to it would leave a destroyed chart whose next `update()` reads\n // `options.responsive` on `undefined` and throws.\n const captureChart = useCallback((chartInstance: Highcharts.Chart) => {\n const forExport = (\n chartInstance.options.chart as { forExport?: boolean } | undefined\n )?.forExport;\n if (!forExport) {\n setChart(chartInstance);\n // Seed plotHeight in the same commit as the chart so markers render with\n // it, not a frame later (the pop-in). The redraw handler keeps it current.\n setPlotHeight(chartInstance.plotHeight);\n }\n }, []);\n\n // Hold `onReady` in a ref so an inline callback (new identity each render)\n // doesn't re-fire the effect below — it should fire on instance change only.\n const onReadyRef = useRef(onReady);\n onReadyRef.current = onReady;\n\n // Fire `onReady` once per chart instance. The chart callback runs only on\n // instance creation — data/prop changes call `chart.update()` on the same\n // instance and don't re-run it — so keying on `[chart]` gives once-per-instance\n // semantics: re-fires on a genuine remount, never on a data update.\n useEffect(() => {\n if (!chart) return;\n onReadyRef.current?.(\n createChartExportHandle(chart, () =>\n buildExportChartOptions(exportThemeRef.current)\n )\n );\n }, [chart]);\n\n // Track the chart's actual plot-area height so the annotation marker line\n // spans it. Re-reads on Highcharts' `redraw` event (covers window resizes,\n // data updates, anything that re-lays-out the plot area).\n useEffect(() => {\n if (!chart) return;\n setPlotHeight(chart.plotHeight);\n const unbind = Highcharts.addEvent(chart, \"redraw\", () => {\n setPlotHeight(chart.plotHeight);\n });\n return () => unbind();\n }, [chart]);\n\n // Stable ref across internal re-renders (setChart/setPlotHeight): HighchartsReact\n // keys its update effect on the options ref and redraws on change, so a fresh\n // object each render would redraw — and re-flicker the annotations — every time.\n const highchartsOptions = useMemo(() => {\n // Seeds groups svgContainerLabel/point.descriptionFormatter under\n // `accessibility` for a cleaner public shape, but Highcharts itself reads\n // svgContainerLabel from `lang.accessibility`, and `point.descriptionFormatter`\n // is called with the live `Point` instance — only `chartBase` may touch that\n // type, so the extraction (including v1's `.value` vs `.y` fallback) is\n // wrapped here rather than in a family adapter.\n const pointDescriptionFormatter =\n options.accessibility?.point?.descriptionFormatter;\n const svgContainerLabel = options.accessibility?.svgContainerLabel;\n // Same rationale as the accessibility wrap above: the family's formatter\n // is Seeds-vocab, but `radius` (`point.marker.radius`) is a genuine\n // Highcharts runtime field with no place in a structurally-typed\n // pass-through, so `chartBase` extracts it via a contained cast.\n const packedBubbleDataLabelFormatter =\n options.plotOptions?.packedbubble?.dataLabels?.formatter;\n return {\n ...options,\n ...(packedBubbleDataLabelFormatter\n ? {\n plotOptions: {\n ...options.plotOptions,\n packedbubble: {\n ...options.plotOptions?.packedbubble,\n dataLabels: {\n ...options.plotOptions?.packedbubble?.dataLabels,\n formatter(this: Highcharts.PointLabelObject) {\n const bubblePoint = this.point as Highcharts.Point & {\n value?: number;\n marker?: { radius?: number };\n };\n return packedBubbleDataLabelFormatter.call({\n point: {\n name: bubblePoint.name,\n value: bubblePoint.value ?? (bubblePoint.y as number),\n index: bubblePoint.index,\n series: { index: bubblePoint.series.index },\n },\n radius: bubblePoint.marker?.radius ?? 0,\n });\n },\n },\n },\n },\n }\n : {}),\n accessibility: {\n description: options.accessibility?.description,\n ...(pointDescriptionFormatter\n ? {\n point: {\n descriptionFormatter: (point: Highcharts.Point) => {\n const bubblePoint = point as Highcharts.Point & {\n value?: number;\n };\n return pointDescriptionFormatter({\n name: point.name,\n value: bubblePoint.value ?? (point.y as number),\n });\n },\n },\n }\n : {}),\n },\n ...(svgContainerLabel\n ? { lang: { accessibility: { svgContainerLabel } } }\n : {}),\n // useHTML is load-bearing: the tooltip portal's target div only mounts\n // when the label renders as HTML, not SVG (see `charts/README.md`).\n tooltip: { ...options.tooltip, useHTML: true },\n // Client-side/offline export only; native context button hidden (export\n // is exposed via the `onReady` handle).\n exporting: {\n fallbackToExportServer: false,\n buttons: { contextButton: { enabled: false } },\n },\n } as Highcharts.Options;\n }, [options]);\n\n // Safety net: no series → render nothing. Families are expected to early-return\n // before calling hooks to avoid wasted work, but this guards future families\n // that forget. See `charts/README.md` (\"Empty-data handling\").\n if (series.length === 0) return null;\n\n const chartWithAnnotations = chart as ChartWithAnnotations | null;\n const hasRenderedAnnotations =\n !!chartWithAnnotations?.annotations?.length &&\n !!annotationLookup?.size &&\n plotHeight > 0;\n\n return (\n <>\n <GlobalChartStyleOverrides />\n <HighchartsReact\n highcharts={Highcharts}\n // Structural cast at the engine seam — the only place we touch Highcharts' option types.\n options={highchartsOptions}\n callback={captureChart}\n />\n {hasRenderedAnnotations ? (\n <ChartAnnotationMarkerPortal\n annotationLookup={annotationLookup}\n annotationContext={chartWithAnnotations!.annotations![0]!}\n chartContainer={chartWithAnnotations!.container}\n plotHeight={plotHeight}\n />\n ) : null}\n {chart ? (\n <ChartTooltipPortal\n chart={chart}\n renderContent={(context) => {\n // Pie and packedbubble contexts have a single `point`, no `points`\n // array; time-series has `points`. Packedbubble is discriminated\n // from pie by `series.type`, since it needs colorIndex-based color\n // resolution (no per-point `.color`/`.percentage` the way pie has).\n const ctx = context as {\n point: {\n name: string;\n y: number | null;\n color: string;\n percentage: number;\n index: number;\n };\n // Cartesian (time-series) contexts carry a `points` array whose\n // members expose the shared x-axis; pie/packedbubble have none.\n // We read only the axis' `tickPositions`, used to derive the\n // tooltip title's datetime granularity.\n points?: Array<{\n series?: { xAxis?: { tickPositions?: number[] } };\n }>;\n key?: string;\n colorIndex?: number;\n series?: { type?: string; index: number };\n };\n const isPackedBubble =\n !ctx.points && ctx.series?.type === \"packedbubble\";\n const isPie = !ctx.points && !isPackedBubble;\n const data: ChartTooltipDataRow[] = isPackedBubble\n ? // Mirrors v1's `transformBubbleChartTooltipData`: color comes from\n // the outer category's colorIndex, not a per-point `.color`.\n [\n {\n color:\n (ctx.colorIndex !== undefined\n ? colors[ctx.colorIndex]\n : undefined) ?? \"\",\n name: ctx.key ?? ctx.point.name,\n value: ctx.point.y,\n seriesIndex: ctx.series?.index,\n pointIndex: ctx.point.index,\n },\n ]\n : isPie\n ? [\n {\n color: ctx.point.color,\n name: ctx.point.name,\n value: ctx.point.y,\n percent: ctx.point.percentage,\n },\n ]\n : // Drop v1's `readonly` array wrapper and `icon` field at the seam —\n // the v2 surface is mutable and doesn't expose row-level icons.\n transformTimeSeriesTooltipData({\n context,\n data: series.map((s, i) => ({\n name: s.name,\n points: [],\n styles: { color: s.color ?? colors[i] ?? \"\" },\n })),\n }).map((row) => ({\n color: row.color,\n name: row.name,\n value: row.value,\n }));\n // For pie/packedbubble, `context.x` is meaningless (no x-axis) —\n // use `context.key`/point name instead. For time-series,\n // `context.x` is the timestamp/category value. The annotation\n // lookup always uses `context.point.x` (numeric).\n const position =\n isPie || isPackedBubble\n ? ctx.key ?? ctx.point.name\n : (context.x as number | string);\n // Full tick array off the (guarded) cartesian x-axis — drives the\n // title's granularity. Undefined for pie/packedbubble (no `points`)\n // and for category axes (title passes the string through verbatim).\n const tickPositions = ctx.points?.[0]?.series?.xAxis?.tickPositions;\n const resolved = annotationLookup?.get(context.point.x as number);\n const annotation = resolved\n ? {\n icon: resolved.icon,\n color: resolved.color,\n title: resolved.title,\n description: resolved.description,\n }\n : undefined;\n return renderTooltip({\n data,\n position,\n // Pie/packedbubble have no x-axis position; a title would just repeat the point name.\n hideTitle: isPie || isPackedBubble,\n annotation,\n hasOnClick,\n tooltipClickLabel,\n ...tooltipOptions,\n tickPositions,\n invalidNumberLabel,\n valueFormat,\n });\n }}\n />\n ) : null}\n {hideLegend ? null : (\n <div className=\"seeds-chart-legend\">\n <SeriesLegend\n items={series.map((s, i) => ({\n name: s.name,\n icon: s.icon,\n color: s.color ?? colors[i] ?? \"\",\n }))}\n />\n </div>\n )}\n </>\n );\n}\n","// Portals DS-owned marker content into Highcharts annotation containers. The\n// marker shape (icon + vertical line in the configured color) is rendered\n// internally — consumers don't supply React nodes. See CE-7.\n\nimport { memo, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Icon } from \"@sproutsocial/seeds-react-icon\";\nimport type { ChartAnnotationLookup, ChartAnnotation } from \"./annotations\";\nimport type {\n ChartAnnotationRuntime,\n ChartAnnotationRuntimeLabel,\n} from \"./chartBase\";\n\nfunction ChartAnnotationMarker({\n annotation,\n lineHeight,\n}: {\n annotation: ChartAnnotation;\n /** Vertical pixel length of the marker line. Driven by `chart.plotHeight`\n * so the line spans the chart's full plot area regardless of chart size. */\n lineHeight: number;\n}) {\n return (\n <Box display=\"flex\" flexDirection=\"column\" alignItems=\"center\" gap={200}>\n {annotation.icon ? (\n <Box\n display=\"flex\"\n alignItems=\"center\"\n justifyContent=\"center\"\n width=\"20px\"\n height=\"20px\"\n bg=\"icon.base\"\n borderRadius=\"50%\"\n >\n <Icon name={annotation.icon} size=\"mini\" color=\"icon.inverse\" />\n </Box>\n ) : null}\n <Box\n height={`${lineHeight}px`}\n width=\"0.5px\"\n bg={annotation.color ?? \"neutral.200\"}\n />\n </Box>\n );\n}\n\n// v2-specific marker attribute. Distinct from v1's `data-annotation-marker`\n// so v1 and v2 portals don't sweep each other's containers when both render\n// side-by-side (e.g. in the v1↔v2 comparison story).\nconst MARKER_ATTR = \"data-v2-chart-annotation-marker\";\n\nexport const ChartAnnotationMarkerPortal = memo(\n function ChartAnnotationMarkerPortal({\n annotationLookup,\n annotationContext,\n chartContainer,\n plotHeight,\n }: {\n annotationLookup: ChartAnnotationLookup;\n annotationContext: ChartAnnotationRuntime;\n /** The chart's own DOM root — every query is scoped here to keep\n * multi-chart renders from interfering. */\n chartContainer: HTMLElement;\n /** Highcharts' current plot-area height in pixels. Drives the marker\n * line length so it spans the chart's plot area at any chart size. */\n plotHeight: number;\n }) {\n const annotationContainers = useRef<Record<number, HTMLDivElement>>({});\n const [, setRenderCount] = useState(0);\n const forceRender = () => setRenderCount((prev) => prev + 1);\n\n useEffect(() => {\n if (!annotationContext.labels) return;\n\n const newContainers: Record<number, HTMLDivElement> = {};\n const existingAnnotationContainers = chartContainer.querySelectorAll(\n `div[${MARKER_ATTR}='true']`\n );\n\n // 1. Mark this chart's existing markers as stale before updating.\n // Highcharts doesn't clean these up, so we sweep them below.\n existingAnnotationContainers.forEach((annotation) =>\n annotation.setAttribute(MARKER_ATTR, \"false\")\n );\n\n annotationContext.labels.forEach((label: ChartAnnotationRuntimeLabel) => {\n const labelElement = label.graphic?.div;\n if (!labelElement) return;\n\n // 2. Reuse this label's existing marker div when present — Highcharts\n // repositions (rather than recreates) annotation graphics across\n // redraws, so reusing keeps the React portal mounted and avoids the\n // teardown→recreate flash. Only create one when absent.\n let annotationDiv = labelElement.querySelector<HTMLDivElement>(\n `div[${MARKER_ATTR}]`\n );\n if (!annotationDiv) {\n annotationDiv = document.createElement(\"div\");\n labelElement.appendChild(annotationDiv);\n }\n annotationDiv.setAttribute(MARKER_ATTR, \"true\");\n\n const xValue = label.options?.point?.x as number;\n newContainers[xValue] = annotationDiv;\n });\n\n annotationContainers.current = newContainers;\n forceRender();\n\n // 4. Within this chart's container, remove `.highcharts-annotation`\n // parents that no longer have any live v2 marker child.\n requestAnimationFrame(() => {\n chartContainer\n .querySelectorAll(\"div.highcharts-annotation\")\n .forEach((annotationDiv) => {\n const reactAnnotations = annotationDiv.querySelectorAll(\n `div[${MARKER_ATTR}]`\n );\n const hasActiveAnnotations = Array.from(reactAnnotations).some(\n (div) => div.getAttribute(MARKER_ATTR) === \"true\"\n );\n if (!hasActiveAnnotations) annotationDiv.remove();\n });\n });\n }, [annotationContext.labels, annotationLookup, chartContainer]);\n\n return (\n <>\n {Object.entries(annotationContainers.current).map(\n ([xValue, container]) => {\n const annotation = annotationLookup.get(Number(xValue));\n return annotation\n ? createPortal(\n <ChartAnnotationMarker\n annotation={annotation}\n lineHeight={plotHeight}\n />,\n container\n )\n : null;\n }\n )}\n </>\n );\n }\n);\n","// Minimal: no locale / currency / duration formatting. Consumers needing those pass their own `tooltip` render prop.\n\nimport { Box } from \"@sproutsocial/seeds-react-box\";\nimport { Icon, type TypeIconName } from \"@sproutsocial/seeds-react-icon\";\nimport { Text } from \"@sproutsocial/seeds-react-text\";\n\nimport { ChartLegendLabel } from \"../../components/ChartLegend/components/ChartLegendLabel\";\nimport {\n ChartTooltip,\n ChartTooltipFooter,\n ChartTooltipHeader,\n ChartTooltipTable,\n ChartTooltipTitle,\n type TypeChartTooltipTableProps,\n} from \"../../components/ChartTooltip\";\nimport type { ChartTooltipProps } from \"./chartBase\";\nimport {\n datetimeTitleFormatter,\n type DatetimeTimeFormat,\n} from \"./formatters/datetimeFormatter\";\nimport { valueFormatter } from \"./formatters/valueFormatter\";\n\n// Derives the tooltip's verbose date title from the datetime-axis config via\n// the shared `datetimeTitleFormatter` (granularity from `tickPositions`,\n// `timeFormat`, timezone). `textLocale` is passed explicitly (default \"en-US\")\n// rather than the ambient browser locale, so the title can never disagree with\n// the localized numbers in the same tooltip (CE-36). A string `position`\n// (category axis) passes through verbatim, never date-formatted.\nfunction formatTitle({\n position,\n timezone,\n timeFormat,\n tickPositions,\n textLocale = \"en-US\",\n}: {\n position: number | string;\n timezone?: string;\n timeFormat?: DatetimeTimeFormat;\n tickPositions?: readonly number[];\n textLocale?: Intl.LocalesArgument;\n}): string {\n if (typeof position === \"string\") return position;\n return datetimeTitleFormatter({\n value: position,\n tickPositions,\n timezone,\n timeFormat,\n textLocale,\n });\n}\n\n// Inlined (not v1's `ChartXAnnotationDetails`) because that component requires\n// both iconName and text together — v2's `ChartAnnotation` documents icon,\n// title, and description as independently optional, so the renderer has to\n// match that contract instead of dropping fields silently.\nfunction AnnotationHeader({\n icon,\n title,\n description,\n}: {\n icon?: TypeIconName;\n title?: string;\n description?: string;\n}) {\n const hasIconOrTitle = Boolean(icon || title);\n if (!hasIconOrTitle && !description) return null;\n return (\n <>\n {hasIconOrTitle ? (\n <ChartTooltipHeader>\n <Box display=\"flex\" alignItems=\"center\" gap={300}>\n {icon ? <Icon name={icon} size=\"mini\" color=\"icon.base\" /> : null}\n {title ? (\n <Text color=\"text.body\" fontSize={200}>\n {title}\n </Text>\n ) : null}\n </Box>\n </ChartTooltipHeader>\n ) : null}\n {description ? (\n <ChartTooltipHeader>\n <Text fontSize={200} color=\"text.subtext\">\n {description}\n </Text>\n </ChartTooltipHeader>\n ) : null}\n </>\n );\n}\n\n// Local to the default tooltip: `hideTotal` opts out of the multi-series Total\n// row. Kept off the shared `ChartTooltipProps` render-prop contract (and off\n// `BarChart`'s public props) intentionally — the chart sets it internally; any\n// consumer-facing tooltip config is deferred to CE-35.\ntype DefaultChartTooltipProps = ChartTooltipProps & { hideTotal?: boolean };\n\nexport function DefaultChartTooltip({\n data,\n position,\n annotation,\n hasOnClick,\n tooltipClickLabel,\n timezone,\n timeFormat,\n tickPositions,\n textLocale,\n invalidNumberLabel,\n valueFormat = {},\n hideTitle,\n hideTotal = false,\n}: DefaultChartTooltipProps) {\n const rows = data.map((d) => {\n const formattedValue =\n d.value == null\n ? invalidNumberLabel ?? \"—\"\n : valueFormatter({ ...valueFormat, value: d.value, abbreviate: false });\n\n const nameCell = {\n content: (\n <ChartLegendLabel color={d.color}>\n <Text aria-label={`${d.name}: `}>{d.name}</Text>\n </ChartLegendLabel>\n ),\n };\n\n // When a row carries `percent` (pie/donut), show percent + value grouped\n // and flush right, matching v1's DonutChartTooltip: `inline-flex` shrinks\n // the pair to its content so the cell's `align: right` right-aligns the\n // whole group (a block `flex` stretched to fill the cell, leaving a gap\n // between percent and value — svbarker review, PR #2267). Guard NaN:\n // Highcharts emits NaN% when every slice value is null (total = 0).\n return {\n cells: [\n nameCell,\n {\n content:\n d.percent != null && !isNaN(d.percent) ? (\n <Box display=\"inline-flex\" alignItems=\"center\" gap={350}>\n <Text>{Math.round(d.percent)}%</Text>\n <Text fontWeight=\"semibold\">{formattedValue}</Text>\n </Box>\n ) : (\n <Text>{formattedValue}</Text>\n ),\n align: \"right\" as const,\n },\n ],\n };\n });\n\n // Multi-series parity with v1/Trellis: append a Total row summing the\n // contributing (non-null) values. Track contribution count separately from\n // the sum — `[5, -5]` legitimately totals 0 and must render \"0\", whereas an\n // all-null set must render the null placeholder, not a misleading 0.\n const contributing = data.filter((d) => d.value != null);\n const total =\n contributing.length > 0\n ? contributing.reduce((sum, d) => sum + (d.value as number), 0)\n : null;\n\n const totalRow =\n data.length > 1 && !hideTotal\n ? [\n {\n isAppendedRow: true,\n cells: [\n {\n content: (\n <Text fontWeight=\"semibold\" aria-label=\"Total: \">\n Total\n </Text>\n ),\n },\n {\n // Mirror the per-series value cell's formatting path exactly.\n content: (\n <Text fontWeight=\"bold\">\n {total == null\n ? invalidNumberLabel ?? \"—\"\n : valueFormatter({\n ...valueFormat,\n value: total,\n abbreviate: false,\n })}\n </Text>\n ),\n align: \"right\" as const,\n },\n ],\n },\n ]\n : [];\n\n return (\n <ChartTooltip>\n {annotation ? (\n <AnnotationHeader\n icon={annotation.icon}\n title={annotation.title}\n description={annotation.description}\n />\n ) : null}\n {hideTitle ? null : (\n <ChartTooltipTitle>\n {formatTitle({\n position,\n timezone,\n timeFormat,\n tickPositions,\n textLocale,\n })}\n </ChartTooltipTitle>\n )}\n <ChartTooltipTable\n rows={[...rows, ...totalRow] as TypeChartTooltipTableProps[\"rows\"]}\n />\n {hasOnClick && tooltipClickLabel ? (\n <ChartTooltipFooter>\n {typeof tooltipClickLabel === \"string\" ? (\n <Box display=\"flex\" alignItems=\"center\" gap={300}>\n <Icon name=\"hand-pointer-clicking-outline\" color=\"icon.base\" />\n <Text color=\"text.subtext\" fontSize={200}>\n {tooltipClickLabel}\n </Text>\n </Box>\n ) : (\n tooltipClickLabel\n )}\n </ChartTooltipFooter>\n ) : null}\n </ChartTooltip>\n );\n}\n","/**\n * Pure, Highcharts-free datetime axis label formatter for v2 chart families.\n *\n * Ports v1's `helpers/xAxisLabelFormatter` into a rendering-agnostic shape: it\n * returns a `{ primary, secondary? }` object instead of an HTML string, leaving\n * SVG rendering to the caller. The timezone- and DST-correct boundary detection\n * (calendar parts read in the target timezone) and the per-granularity\n * `Intl.DateTimeFormat` option sets are a near-verbatim port of v1.\n *\n * The one net-new piece versus v1 is granularity derivation: v1 receives\n * `unitName` from Highcharts, but `charts/` may not depend on Highcharts types\n * (see `charts/README.md` invariants), so granularity is derived from the\n * spacing of `tickPositions`.\n */\n\n// Local types — defined here rather than imported from the Highcharts package\n// or v1's src/types.ts, to keep charts/ self-contained per the README invariant.\nexport type DatetimeTimeFormat = \"12\" | \"24\";\n\nexport type DatetimeLabel = Readonly<{\n primary: string;\n /** Omitted (undefined) when the tick is not on a unit boundary. */\n secondary?: string;\n}>;\n\ntype DatetimeGranularity = \"hour\" | \"day\" | \"week\" | \"month\" | \"year\";\n\ntype DatetimeFormatterInput = Readonly<{\n /** ms-epoch tick value, or a categorical string passed through verbatim. */\n value: number | string;\n /**\n * Full tick array — used for boundary lookup and granularity derivation.\n * Optional: with fewer than two ticks, granularity defaults to \"day\".\n */\n tickPositions?: readonly number[];\n // optional\n /** IANA timezone. Defaults to \"UTC\". */\n timezone?: string;\n /** \"12\" | \"24\" hour clock for the hour granularity. Defaults to \"12\". */\n timeFormat?: DatetimeTimeFormat;\n /** Locale for Intl formatting. Defaults to \"en-US\". */\n textLocale?: Intl.LocalesArgument;\n}>;\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- kept alongside DAY_MS/WEEK_MS for upcoming duration formatting; stable numeric constant unlikely to drift\nconst HOUR_MS = 3_600_000;\nconst DAY_MS = 86_400_000;\nconst WEEK_MS = 604_800_000;\n// Granularity cutoffs use the *minimum* month/year length, not the average, so\n// calendar-aligned axes classify correctly: a real Highcharts month-unit axis\n// sits 28–31d apart and a year-unit axis 365–366d apart. Comparing the median\n// tick delta against the *average* month (30.44d) / year (365.25d) length\n// misclassified a 28-day February month gap as \"week\" and a 365-day non-leap\n// year as \"month\". Bias to the minimum so the shortest real span still reads as\n// \"month\" / \"year\".\nconst MIN_MONTH_MS = 2_419_200_000; // 28 days (shortest month, non-leap Feb)\nconst MIN_YEAR_MS = 31_536_000_000; // 365 days (shortest, non-leap year)\n\n/**\n * Reads the calendar day/month/year of a date *in the target timezone* via\n * `Intl.DateTimeFormat(...).formatToParts`. This is what makes boundary\n * detection timezone- and DST-correct (a 25-hour \"fall back\" day still rolls\n * the day over on the correct local-midnight tick). Ported verbatim from v1.\n */\nconst getDatePartsInTimezone = (date: Date, timezone: string) => {\n const formatter = new Intl.DateTimeFormat(\"en-US\", {\n year: \"numeric\",\n month: \"numeric\",\n day: \"numeric\",\n timeZone: timezone,\n });\n const parts = formatter.formatToParts(date);\n return {\n day: parseInt(parts.find((p) => p.type === \"day\")?.value ?? \"0\", 10),\n month: parseInt(parts.find((p) => p.type === \"month\")?.value ?? \"0\", 10),\n year: parseInt(parts.find((p) => p.type === \"year\")?.value ?? \"0\", 10),\n };\n};\n\n/**\n * Derives the axis granularity from the median delta between consecutive\n * `tickPositions`. Median (not mean) so an irregular first/last gap does not\n * skew the result. With fewer than two ticks there is no spacing to measure, so\n * we default to \"day\".\n *\n * Note: \"day\" and \"week\" produce identical output (same primary/secondary\n * option sets), so the day/week threshold is cosmetic and never changes a label.\n * The week↔month and month↔year cutoffs do change output, so they compare\n * against the *minimum* month/year length (not the average); see MIN_MONTH_MS /\n * MIN_YEAR_MS for why calendar-aligned axes would otherwise be misclassified.\n */\nconst deriveGranularity = (\n tickPositions: readonly number[]\n): DatetimeGranularity => {\n if (tickPositions.length < 2) {\n return \"day\";\n }\n\n const deltas: number[] = [];\n for (let i = 1; i < tickPositions.length; i++) {\n const curr = tickPositions[i];\n const prev = tickPositions[i - 1];\n // Both reads are in range by the loop bounds (1 <= i < length); the guard\n // only satisfies noUncheckedIndexedAccess and is unreachable at runtime.\n if (curr === undefined || prev === undefined) {\n continue;\n }\n deltas.push(Math.abs(curr - prev));\n }\n deltas.sort((a, b) => a - b);\n\n const mid = Math.floor(deltas.length / 2);\n // `length >= 2` is guaranteed by the early return above, so `deltas` has at\n // least one element and `deltas[mid]` is always defined; `deltas[mid - 1]` is\n // only read on the even-length branch, where it is guaranteed in range too.\n const upper = deltas[mid]!;\n const lower = deltas[mid - 1];\n const median =\n deltas.length % 2 === 0 && lower !== undefined\n ? (lower + upper) / 2\n : upper;\n\n if (median < DAY_MS) {\n return \"hour\";\n }\n if (median < WEEK_MS) {\n return \"day\";\n }\n if (median < MIN_MONTH_MS) {\n return \"week\";\n }\n if (median < MIN_YEAR_MS) {\n return \"month\";\n }\n return \"year\";\n};\n\n/**\n * Derives a datetime tick's two-line stacked label as a rendering-agnostic\n * `{ primary, secondary? }` object. `secondary` is emitted only on a unit\n * boundary (the first tick, or when the relevant calendar part changes versus\n * the previous tick), matching v1's two-line semantics.\n *\n * Categorical (string) values and non-finite numbers are passed through as\n * `{ primary: String(value) }` with no secondary, so callers always get a\n * stable, markup-free string rather than \"Invalid Date\".\n */\nexport function datetimeFormatter({\n value,\n tickPositions = [],\n // optional\n timezone = \"UTC\",\n timeFormat = \"12\",\n textLocale = \"en-US\",\n}: DatetimeFormatterInput): DatetimeLabel {\n // Categorical (string) data, and non-finite timestamps, pass through.\n if (typeof value === \"string\" || !Number.isFinite(value)) {\n return { primary: String(value) };\n }\n\n const granularity = deriveGranularity(tickPositions);\n\n const tickIndex = tickPositions.indexOf(value);\n const isFirst = tickIndex === 0;\n const previousValue = tickPositions[tickIndex - 1];\n\n const valueDate = new Date(value);\n const valueParts = getDatePartsInTimezone(valueDate, timezone);\n const previousValueDate = previousValue ? new Date(previousValue) : undefined;\n const previousValueParts = previousValueDate\n ? getDatePartsInTimezone(previousValueDate, timezone)\n : undefined;\n\n let firstPartOptions: Intl.DateTimeFormatOptions = {};\n let secondPartOptions: Intl.DateTimeFormatOptions = {};\n\n switch (granularity) {\n case \"hour\":\n firstPartOptions =\n timeFormat === \"24\"\n ? { hour: \"numeric\", minute: \"numeric\", hour12: false }\n : { hour: \"numeric\" };\n if (isFirst || valueParts.day !== previousValueParts?.day) {\n secondPartOptions = { day: \"numeric\", month: \"short\" };\n }\n break;\n case \"day\":\n case \"week\":\n firstPartOptions = { day: \"numeric\" };\n if (isFirst || valueParts.month !== previousValueParts?.month) {\n secondPartOptions = { month: \"short\" };\n }\n break;\n case \"month\":\n firstPartOptions = { month: \"short\" };\n if (isFirst || valueParts.year !== previousValueParts?.year) {\n secondPartOptions = { year: \"numeric\" };\n }\n break;\n case \"year\":\n firstPartOptions = { year: \"numeric\" };\n break;\n default:\n firstPartOptions = {};\n break;\n }\n\n const primary = new Intl.DateTimeFormat(textLocale, {\n ...firstPartOptions,\n timeZone: timezone,\n }).format(valueDate);\n\n if (Object.keys(secondPartOptions).length > 0) {\n const secondary = new Intl.DateTimeFormat(textLocale, {\n ...secondPartOptions,\n timeZone: timezone,\n }).format(valueDate);\n return { primary, secondary };\n }\n\n return { primary };\n}\n\n/**\n * Derives a datetime tooltip's *verbose one-line title* from the same\n * granularity/timezone/timeFormat inputs as {@link datetimeFormatter}, but\n * returns a single human-readable string rather than the two-line\n * `{ primary, secondary? }` tick shape. This is the shared, config-derived\n * title capability the product domains previously hand-rolled as their own\n * `TimeSeriesChartTooltipTitle` helpers.\n *\n * Verbosity follows granularity (derived from `tickPositions`):\n * - day/week (and the no-ticks default) → `\"Saturday, July 1, 2026\"`\n * - hour → the full date plus the tick's time, `timeFormat`-aware\n * - month → `\"July 2026\"`\n * - year → `\"2026\"`\n *\n * Categorical (string) values and non-finite numbers pass through verbatim, so\n * a category-axis tooltip never runs date formatting. The locale is explicit\n * (`textLocale`, default `\"en-US\"`) rather than the ambient browser locale, per\n * the CE-36 explicit-locale rule.\n */\nexport function datetimeTitleFormatter({\n value,\n tickPositions = [],\n // optional\n timezone = \"UTC\",\n timeFormat = \"12\",\n textLocale = \"en-US\",\n}: DatetimeFormatterInput): string {\n // Categorical (string) data, and non-finite timestamps, pass through.\n if (typeof value === \"string\" || !Number.isFinite(value)) {\n return String(value);\n }\n\n const granularity = deriveGranularity(tickPositions);\n const valueDate = new Date(value);\n\n let options: Intl.DateTimeFormatOptions;\n switch (granularity) {\n case \"hour\":\n // Full date + time; 24h adds leading-zero minutes, 12h is the bare hour —\n // matching the tick formatter's hour handling.\n options =\n timeFormat === \"24\"\n ? {\n weekday: \"long\",\n month: \"long\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"numeric\",\n hour12: false,\n }\n : {\n weekday: \"long\",\n month: \"long\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n };\n break;\n case \"month\":\n options = { month: \"long\", year: \"numeric\" };\n break;\n case \"year\":\n options = { year: \"numeric\" };\n break;\n case \"day\":\n case \"week\":\n default:\n // The default (and the no-ticks fallback) reproduces the historical full\n // date title exactly, so callers that don't thread ticks see no change.\n options = {\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n };\n break;\n }\n\n return new Intl.DateTimeFormat(textLocale, {\n ...options,\n timeZone: timezone,\n }).format(valueDate);\n}\n","import { formatDuration } from \"@sproutsocial/seeds-react-duration\";\nimport { formatNumeral } from \"@sproutsocial/seeds-react-numeral\";\n\nimport type { TypeChartNumberFormat } from \"../../../types\";\n\nexport type TypeValuePercentInput = \"decimal\" | \"whole\";\n\nexport type TypeValueFormatterOptions = Readonly<{\n /** The raw numeric value. For duration, milliseconds. */\n value: number;\n /** default \"decimal\" */\n numberFormat?: TypeChartNumberFormat;\n /** Only used when numberFormat === \"currency\". default \"USD\" */\n currency?: string;\n /** true → 10k threshold; number → max(1000, abs(n)); false → never. default true */\n abbreviate?: boolean | number;\n /** \"decimal\" scales 0.42 → \"42%\"; \"whole\" treats 42 → \"42%\". default \"decimal\" */\n percentInput?: TypeValuePercentInput;\n /** locale for decimal/currency/percent (formatNumeral) */\n numberLocale?: Intl.LocalesArgument;\n /** locale for duration (formatDuration) */\n textLocale?: Intl.LocalesArgument;\n}>;\n\n/**\n * Pure value formatter for v2 charts. Turns a numeric value plus declarative\n * config into a display string. Composes `formatNumeral` (decimal/currency/\n * percent) and `formatDuration` (duration). Contains no chart-engine types or\n * imports — it upholds the \"engine boundary is one file\" invariant documented\n * in `src/charts/README.md`.\n *\n * The axis-wide \"smart 1k/10k\" abbreviation scan that v1's `yAxisLabelFormatter`\n * derives from `tickPositions` is not available to a pure per-value function;\n * callers pass the lowered threshold via `abbreviate` (a number) instead.\n */\nexport function valueFormatter({\n value,\n numberFormat = \"decimal\",\n currency = \"USD\",\n abbreviate = true,\n percentInput = \"decimal\",\n numberLocale,\n textLocale,\n}: TypeValueFormatterOptions): string {\n const numberValue = Number(value);\n\n // v1 parity: render 0 as a bare \"0\" for every format (clean axis origin) —\n // not \"$0.00\" / \"0%\" / a duration-zero. Matches v1's yAxisLabelFormatter,\n // which checks zero first, before any format branch. Revisit if design\n // wants per-format zero rendering later.\n if (numberValue === 0) {\n return formatNumeral({ locale: numberLocale as string, number: 0 });\n }\n\n if (numberFormat === \"duration\") {\n return formatDuration({\n display: \"narrow\",\n locale: textLocale,\n milliseconds: numberValue,\n });\n }\n\n if (numberFormat === \"percent\") {\n const whole = percentInput === \"decimal\" ? numberValue * 100 : numberValue;\n return formatNumeral({\n format: \"percent\",\n locale: numberLocale as string,\n number: whole,\n });\n }\n\n // decimal | currency\n return formatNumeral({\n abbreviate,\n currency,\n format: numberFormat,\n locale: numberLocale as string,\n number: numberValue,\n });\n}\n\n/**\n * Declarative value-format config for axis ticks and the default tooltip — the\n * full {@link TypeValueFormatterOptions} surface minus `value`, which is\n * supplied per-tick / per-row by the caller (the adapter and tooltip).\n */\nexport type ValueFormat = Omit<TypeValueFormatterOptions, \"value\">;\n","import { memo } from \"react\";\nimport { Icon, type TypeIconName } from \"@sproutsocial/seeds-react-icon\";\nimport { ChartLegend } from \"../../components/ChartLegend\";\nimport { ChartLegendLabelContentWithIcon } from \"../../components/ChartLegend/components/ChartLegendLabelContentWithIcon\";\n\nexport interface SeriesLegendItem {\n name: string;\n /** Resolved swatch color — caller is responsible for resolving against the active theme. */\n color: string;\n /** Seeds icon or partner-logo name rendered before the series name. */\n icon?: TypeIconName;\n}\n\nexport interface SeriesLegendProps {\n items: ReadonlyArray<SeriesLegendItem>;\n}\n\n// Internal to `ChartRenderer` — not intended for standalone use. Swatch\n// colors must be resolved by the caller (the chart base does this via\n// `useSeedsChartSetup`) so we keep a single source of truth for the\n// theme-aware palette. If this ever becomes a publicly-exposed primitive,\n// add a `useTheme()` fallback here and relax `item.color` to optional.\nexport const SeriesLegend = memo<SeriesLegendProps>(function SeriesLegend({\n items,\n}) {\n const labels = items.map((item) => ({\n content: (\n <ChartLegendLabelContentWithIcon\n icon={\n item.icon ? (\n <Icon name={item.icon} fixedWidth aria-hidden />\n ) : undefined\n }\n >\n {item.name}\n </ChartLegendLabelContentWithIcon>\n ),\n color: item.color,\n }));\n return <ChartLegend legendLabels={labels} />;\n});\n","// Public, Highcharts-free surface for the v2 chart export API. This module\n// imports nothing — it is a leaf so it can never form an import cycle with\n// `chartBase`. The `createChartExportHandle` factory that closes over the live\n// `Highcharts.Chart` lives in `chartBase.tsx` (the one file allowed to import\n// \"highcharts\"); the instance never crosses this boundary.\n//\n/**\n * Opaque export API handed to consumers via `ChartRenderer`'s `onReady`. Each\n * callable closes over the chart instance and triggers a client-side download;\n * an optional `filename` overrides the generated default. No Highcharts type,\n * ref, or method shape crosses this boundary.\n */\nexport interface ChartExportHandle {\n exportPNG: (opts?: { filename?: string }) => Promise<void>;\n exportSVG: (opts?: { filename?: string }) => Promise<void>;\n exportPDF: (opts?: { filename?: string }) => Promise<void>;\n exportCSV: (opts?: { filename?: string }) => Promise<void>;\n}\n\n/**\n * Canonical client-side export formats offered by `ChartExportMenu`. Co-located\n * with `ChartExportHandle` so the format vocabulary lives next to the handle it\n * maps onto; this leaf imports nothing, so re-exporting it carries no cycle risk.\n */\nexport type ChartExportFormat = \"png\" | \"svg\" | \"pdf\" | \"csv\";\n\n/**\n * Default download filename when a consumer doesn't pass a `filename` override:\n * `chart-<type>-<YYYY-MM-DD-HH-mm-ss>`, lowercased with non-alphanumerics\n * collapsed to hyphens. The full datetime (not just the date) avoids collisions\n * between charts exported on the same day. `type` comes from the chart's\n * `chart.type`; it falls back to `chart` when absent.\n */\nexport function defaultFilename(type: string | undefined, now: Date): string {\n const iso = now.toISOString();\n const stamp = `${iso.slice(0, 10)}-${iso.slice(11, 19).replace(/:/g, \"-\")}`;\n return slugify(type ? `chart-${type}-${stamp}` : `chart-${stamp}`);\n}\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n","import type { SeedsChartOptions, SeedsChartType } from \"./chartBase\";\n\ninterface BaseChartOptionsInput {\n /** Highcharts chart/series type for the family (`column`, `line`, `area`, `pie`, …). */\n type: SeedsChartType;\n /** Accessibility description (Highcharts `accessibility.description`). */\n description: string;\n /** The chart region's accessible name (Highcharts `lang.accessibility.svgContainerLabel`, wired by `ChartRenderer`). */\n svgContainerLabel?: string;\n /** Seeds-vocab per-point a11y description, wrapped to Highcharts' `Point` by `ChartRenderer`. */\n point?: {\n descriptionFormatter?: (point: { name: string; value: number }) => string;\n };\n /** Reserve top margin for content drawn above the plot area (e.g. annotation markers). */\n reserveTopMargin?: boolean;\n /**\n * Resolved IANA timezone for the chart (`time.timezone`) — governs datetime\n * ticks and the tooltip date. Pass the value from `resolveTooltipTimezone`;\n * `undefined` (a category axis) omits `time` entirely.\n */\n timezone?: string;\n}\n\n/**\n * The chart options shared by *every* v2 chart family, independent of axis type:\n * styled-mode + animation-off, the accessibility description, Highcharts' native\n * title/credits/legend disabled (Seeds renders its own legend), and the tooltip-\n * portal config. Families spread the result and add their own axes, `plotOptions`,\n * `series`, and `annotations`.\n *\n * This is the family-agnostic base layer — donut (no axes) consumes it just as\n * the X/Y families do. Axis/series/X-Y-specific sharing lives elsewhere\n * (`axisOptions` + the X/Y pattern-sharing spike), layered on top of this.\n */\nexport function buildBaseChartOptions({\n type,\n description,\n svgContainerLabel,\n point,\n reserveTopMargin = false,\n timezone,\n}: BaseChartOptionsInput): Pick<\n SeedsChartOptions,\n | \"chart\"\n | \"accessibility\"\n | \"title\"\n | \"credits\"\n | \"legend\"\n | \"tooltip\"\n | \"time\"\n> {\n return {\n chart: {\n type,\n styledMode: true,\n animation: false,\n ...(reserveTopMargin ? { marginTop: 24 } : {}),\n },\n accessibility: { description, svgContainerLabel, point },\n title: { text: \"\" },\n credits: { enabled: false },\n legend: { enabled: false },\n tooltip: {\n enabled: true,\n outside: true,\n padding: 0,\n shape: \"rect\",\n shared: true,\n hideDelay: 0,\n },\n // Chart-global in Highcharts; only meaningful for datetime axes, so it's\n // present only when a timezone was resolved (omitted for category axes).\n ...(timezone ? { time: { timezone } } : {}),\n };\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkGQOKCB3Mjs = require('./chunk-GQOKCB3M.js');
3
+ var _chunk3P6MPBSZjs = require('./chunk-3P6MPBSZ.js');
4
4
 
5
5
  // src/charts/shared/annotations.ts
6
6
  function buildAnnotationConfig(annotations) {
@@ -56,7 +56,7 @@ function makeValueAxisLabelFormatter(format) {
56
56
  return function() {
57
57
  const numberValue = Number(this.value);
58
58
  const abbreviate = format.abbreviate === false ? false : deriveAbbreviateFromTicks(this);
59
- return _chunkGQOKCB3Mjs.valueFormatter.call(void 0, { ...format, value: numberValue, abbreviate });
59
+ return _chunk3P6MPBSZjs.valueFormatter.call(void 0, { ...format, value: numberValue, abbreviate });
60
60
  };
61
61
  }
62
62
 
@@ -65,4 +65,4 @@ function makeValueAxisLabelFormatter(format) {
65
65
 
66
66
 
67
67
  exports.buildAnnotationConfig = buildAnnotationConfig; exports.defaultValueAxisLabelFormatter = defaultValueAxisLabelFormatter; exports.makeValueAxisLabelFormatter = makeValueAxisLabelFormatter;
68
- //# sourceMappingURL=chunk-6SF6OYQA.js.map
68
+ //# sourceMappingURL=chunk-MTGYEUZN.js.map