@sproutsocial/seeds-react-data-viz 0.26.1 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/{axis-BWvZqdQr.d.ts → axis-DQXvjzBs.d.ts} +1 -1
  2. package/dist/{axis-3XKa6pW7.d.mts → axis-DVx6Mn1k.d.mts} +1 -1
  3. package/dist/bar/index.d.mts +2 -2
  4. package/dist/bar/index.d.ts +2 -2
  5. package/dist/bar/index.js +12 -12
  6. package/dist/bubble/index.d.mts +1 -1
  7. package/dist/bubble/index.d.ts +1 -1
  8. package/dist/bubble/index.js +9 -9
  9. package/dist/chart-styles.css +41 -0
  10. package/dist/{chartBase-BM97OBBE.d.ts → chartBase-BYipK7MZ.d.ts} +5 -1
  11. package/dist/{chartBase-D6WI2icR.d.mts → chartBase-Cd405GIA.d.mts} +5 -1
  12. package/dist/charts.css +41 -0
  13. package/dist/{chunk-LWDHU4QK.js → chunk-34OKRQET.js} +8 -49
  14. package/dist/chunk-34OKRQET.js.map +1 -0
  15. package/dist/{chunk-NU2RCATK.js → chunk-5TWNMPQU.js} +94 -31
  16. package/dist/chunk-5TWNMPQU.js.map +1 -0
  17. package/dist/{chunk-BRIU5SPJ.js → chunk-CV45MD47.js} +3 -3
  18. package/dist/{chunk-BRIU5SPJ.js.map → chunk-CV45MD47.js.map} +1 -1
  19. package/dist/{chunk-PIZ4MT2B.js → chunk-WMKO3RGT.js} +3 -3
  20. package/dist/{chunk-PIZ4MT2B.js.map → chunk-WMKO3RGT.js.map} +1 -1
  21. package/dist/donut/index.d.mts +1 -1
  22. package/dist/donut/index.d.ts +1 -1
  23. package/dist/donut/index.js +6 -6
  24. package/dist/esm/bar/index.js +4 -4
  25. package/dist/esm/bubble/index.js +2 -2
  26. package/dist/esm/{chunk-VCTZWYEM.js → chunk-332X77M5.js} +11 -52
  27. package/dist/esm/chunk-332X77M5.js.map +1 -0
  28. package/dist/esm/{chunk-OZ7SPMTQ.js → chunk-AL3OTBTT.js} +2 -2
  29. package/dist/esm/{chunk-XEFCGFCF.js → chunk-CYCKJ5ML.js} +81 -18
  30. package/dist/esm/chunk-CYCKJ5ML.js.map +1 -0
  31. package/dist/esm/{chunk-GHP4TT7H.js → chunk-MVNO5UV6.js} +2 -2
  32. package/dist/esm/donut/index.js +2 -2
  33. package/dist/esm/index.js +1 -1
  34. package/dist/esm/line-area/index.js +4 -4
  35. package/dist/esm/sparkline/index.js +3 -3
  36. package/dist/esm/wordcloud/index.js +2 -2
  37. package/dist/index.d.mts +6 -0
  38. package/dist/index.d.ts +6 -0
  39. package/dist/index.js +53 -53
  40. package/dist/line-area/index.d.mts +2 -2
  41. package/dist/line-area/index.d.ts +2 -2
  42. package/dist/line-area/index.js +12 -12
  43. package/dist/sparkline/index.d.mts +3 -3
  44. package/dist/sparkline/index.d.ts +3 -3
  45. package/dist/sparkline/index.js +7 -7
  46. package/dist/wordcloud/index.js +8 -8
  47. package/package.json +1 -1
  48. package/dist/chunk-LWDHU4QK.js.map +0 -1
  49. package/dist/chunk-NU2RCATK.js.map +0 -1
  50. package/dist/esm/chunk-VCTZWYEM.js.map +0 -1
  51. package/dist/esm/chunk-XEFCGFCF.js.map +0 -1
  52. /package/dist/esm/{chunk-OZ7SPMTQ.js.map → chunk-AL3OTBTT.js.map} +0 -0
  53. /package/dist/esm/{chunk-GHP4TT7H.js.map → chunk-MVNO5UV6.js.map} +0 -0
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
 
13
- var _chunkLWDHU4QKjs = require('./chunk-LWDHU4QK.js');
13
+ var _chunk34OKRQETjs = require('./chunk-34OKRQET.js');
14
14
 
15
15
  // src/charts/shared/baseChartOptions.ts
16
16
  function buildBaseChartOptions({
@@ -220,7 +220,7 @@ function buildWordCloudOptions(props, containerWidth, hasActiveNames = false) {
220
220
  chart: {
221
221
  ...base.chart,
222
222
  type: "wordcloud",
223
- height: _nullishCoalesce(props.height, () => ( _chunkLWDHU4QKjs.WORD_CLOUD_CHART_HEIGHT))
223
+ height: _nullishCoalesce(props.height, () => ( _chunk34OKRQETjs.WORD_CLOUD_CHART_HEIGHT))
224
224
  },
225
225
  // `ChartRenderer`'s default-tooltip routing only recognizes "pie" and
226
226
  // "packedbubble" as single-point (non-cartesian) series types
@@ -268,6 +268,9 @@ function buildWordCloudOptions(props, containerWidth, hasActiveNames = false) {
268
268
  // spiral stays registered in chartBase.tsx for now; drop it if
269
269
  // rectangular holds up in-app.
270
270
  spiral: "rectangular",
271
+ // Highcharts' wordcloud module defaults `style.fontWeight` to `"900"`
272
+ // (heavy bold) — overridden to normal weight.
273
+ style: { fontWeight: "normal" },
271
274
  // `seedsWordPadding` (a pure function above, registered onto
272
275
  // Highcharts in chartBase.tsx) approximates word spacing, which
273
276
  // Highcharts' wordcloud module has no native option for; it reads
@@ -673,11 +676,11 @@ function AnnotationHeader({
673
676
  const hasIconOrTitle = Boolean(icon || title);
674
677
  if (!hasIconOrTitle && !description) return null;
675
678
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
676
- hasIconOrTitle ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.ChartTooltipHeader, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreactbox.Box, { display: "flex", alignItems: "center", gap: 300, children: [
679
+ hasIconOrTitle ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.ChartTooltipHeader, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreactbox.Box, { display: "flex", alignItems: "center", gap: 300, children: [
677
680
  icon ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacticon.Icon, { name: icon, size: "mini", color: "icon.base" }) : null,
678
681
  title ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { color: "text.body", fontSize: 200, children: title }) : null
679
682
  ] }) }) : null,
680
- description ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.ChartTooltipHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { fontSize: 200, color: "text.subtext", children: description }) }) : null
683
+ description ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.ChartTooltipHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { fontSize: 200, color: "text.subtext", children: description }) }) : null
681
684
  ] });
682
685
  }
683
686
  function DefaultChartTooltip({
@@ -695,18 +698,42 @@ function DefaultChartTooltip({
695
698
  hideTitle,
696
699
  hideTotal = false
697
700
  }) {
701
+ const usePercentColumn = data.filter((d) => d.percent != null && !isNaN(d.percent)).length > 1;
698
702
  const rows = data.map((d) => {
699
703
  const formattedValue = d.value == null ? _nullishCoalesce(invalidNumberLabel, () => ( "\u2014")) : valueFormatter({ ...valueFormat, value: d.value, abbreviate: false });
700
704
  const nameCell = {
701
- content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.ChartLegendLabel, { color: d.color, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChartLabelWithIcon, { icon: d.icon, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { "aria-label": `${d.name}: `, children: d.name }) }) })
705
+ content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.ChartLegendLabel, { color: d.color, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChartLabelWithIcon, { icon: d.icon, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { "aria-label": `${d.name}: `, children: d.name }) }) })
702
706
  };
707
+ const percent = d.percent;
708
+ if (usePercentColumn) {
709
+ return {
710
+ cells: [
711
+ nameCell,
712
+ {
713
+ content: percent != null && !isNaN(percent) ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreacttext.Text, { children: [
714
+ Math.round(percent),
715
+ "%"
716
+ ] }) : null,
717
+ align: "right",
718
+ pr: 350,
719
+ style: { width: "1%", whiteSpace: "nowrap" }
720
+ },
721
+ {
722
+ content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { children: formattedValue }),
723
+ align: "right",
724
+ pl: 0,
725
+ style: { width: "1%", whiteSpace: "nowrap" }
726
+ }
727
+ ]
728
+ };
729
+ }
703
730
  return {
704
731
  cells: [
705
732
  nameCell,
706
733
  {
707
- content: d.percent != null && !isNaN(d.percent) ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreactbox.Box, { display: "inline-flex", alignItems: "center", gap: 350, children: [
734
+ content: percent != null && !isNaN(percent) ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreactbox.Box, { display: "inline-flex", alignItems: "center", gap: 350, children: [
708
735
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreacttext.Text, { children: [
709
- Math.round(d.percent),
736
+ Math.round(percent),
710
737
  "%"
711
738
  ] }),
712
739
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { fontWeight: "semibold", children: formattedValue })
@@ -723,6 +750,10 @@ function DefaultChartTooltip({
723
750
  isAppendedRow: true,
724
751
  cells: [
725
752
  {
753
+ // In column mode the Total has no percent, so span the label
754
+ // across the name + percent columns; the value then lands in
755
+ // the value column (col 3) without an empty <td>.
756
+ colSpan: usePercentColumn ? 2 : 1,
726
757
  content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { fontWeight: "semibold", "aria-label": "Total: ", children: "Total" })
727
758
  },
728
759
  {
@@ -732,12 +763,25 @@ function DefaultChartTooltip({
732
763
  value: total,
733
764
  abbreviate: false
734
765
  }) }),
735
- align: "right"
766
+ align: "right",
767
+ // Only collapse to content width in percent-column mode, matching
768
+ // the per-series value cells above (which are gated the same way).
769
+ // In non-percent multi-series mode the Total stays a plain
770
+ // right-aligned cell so regular stacking is untouched (ticket
771
+ // scope) and the Total matches the series value cells stacked
772
+ // above it.
773
+ ...usePercentColumn ? {
774
+ pl: 0,
775
+ style: {
776
+ width: "1%",
777
+ whiteSpace: "nowrap"
778
+ }
779
+ } : {}
736
780
  }
737
781
  ]
738
782
  }
739
783
  ] : [];
740
- return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkLWDHU4QKjs.ChartTooltip, { children: [
784
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk34OKRQETjs.ChartTooltip, { children: [
741
785
  annotation ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
742
786
  AnnotationHeader,
743
787
  {
@@ -746,7 +790,7 @@ function DefaultChartTooltip({
746
790
  description: annotation.description
747
791
  }
748
792
  ) : null,
749
- hideTitle ? null : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.ChartTooltipTitle, { children: formatTitle({
793
+ hideTitle ? null : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.ChartTooltipTitle, { children: formatTitle({
750
794
  position,
751
795
  timezone,
752
796
  timeFormat,
@@ -754,12 +798,12 @@ function DefaultChartTooltip({
754
798
  textLocale
755
799
  }) }),
756
800
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
757
- _chunkLWDHU4QKjs.ChartTooltipTable,
801
+ _chunk34OKRQETjs.ChartTooltipTable,
758
802
  {
759
803
  rows: [...rows, ...totalRow]
760
804
  }
761
805
  ),
762
- hasOnClick && tooltipClickLabel ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.ChartTooltipFooter, { children: typeof tooltipClickLabel === "string" ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreactbox.Box, { display: "flex", alignItems: "center", gap: 300, children: [
806
+ hasOnClick && tooltipClickLabel ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.ChartTooltipFooter, { children: typeof tooltipClickLabel === "string" ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _seedsreactbox.Box, { display: "flex", alignItems: "center", gap: 300, children: [
763
807
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacticon.Icon, { name: "hand-pointer-clicking-outline", color: "icon.base" }),
764
808
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _seedsreacttext.Text, { color: "text.subtext", fontSize: 200, children: tooltipClickLabel })
765
809
  ] }) : tooltipClickLabel }) : null
@@ -777,9 +821,18 @@ var SeriesLegend = _react.memo.call(void 0, function SeriesLegend2({
777
821
  content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChartLabelWithIcon, { icon: item.icon, children: item.name }),
778
822
  color: item.color
779
823
  }));
780
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.ChartLegend, { legendLabels: labels });
824
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.ChartLegend, { legendLabels: labels });
781
825
  });
782
826
 
827
+ // src/charts/shared/stacking.ts
828
+ function isChartPercentStacked(options) {
829
+ const plotOptions = options.plotOptions;
830
+ if (!plotOptions) return false;
831
+ return Object.values(plotOptions).some(
832
+ (slot) => slot != null && typeof slot === "object" && slot.stacking === "percent"
833
+ );
834
+ }
835
+
783
836
  // src/charts/shared/chartExport.ts
784
837
  function defaultFilename(type, now) {
785
838
  const iso = now.toISOString();
@@ -1067,7 +1120,7 @@ function ChartRenderer({
1067
1120
  const chartWithAnnotations = chart;
1068
1121
  const hasRenderedAnnotations = !!_optionalChain([chartWithAnnotations, 'optionalAccess', _56 => _56.annotations, 'optionalAccess', _57 => _57.length]) && !!_optionalChain([annotationLookup, 'optionalAccess', _58 => _58.size]) && plotHeight > 0;
1069
1122
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
1070
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLWDHU4QKjs.GlobalChartStyleOverrides, {}),
1123
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk34OKRQETjs.GlobalChartStyleOverrides, {}),
1071
1124
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1072
1125
  _highchartsreactofficial.HighchartsReact,
1073
1126
  {
@@ -1086,7 +1139,7 @@ function ChartRenderer({
1086
1139
  }
1087
1140
  ) : null,
1088
1141
  chart ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1089
- _chunkLWDHU4QKjs.ChartTooltipPortal,
1142
+ _chunk34OKRQETjs.ChartTooltipPortal,
1090
1143
  {
1091
1144
  chart,
1092
1145
  renderContent: (context) => {
@@ -1117,23 +1170,33 @@ function ChartRenderer({
1117
1170
  ] : (
1118
1171
  // Drop v1's `readonly` wrapper (v2 is mutable); icon is forwarded
1119
1172
  // from `series[]` by index below since the helper doesn't carry it.
1120
- _chunkLWDHU4QKjs.transformTimeSeriesTooltipData.call(void 0, {
1121
- context,
1122
- data: series.map((s, i) => ({
1123
- name: s.name,
1124
- points: [],
1125
- styles: { color: _nullishCoalesce(_nullishCoalesce(s.color, () => ( colors[i])), () => ( "")) }
1126
- }))
1127
- }).map((row, i) => ({
1128
- color: row.color,
1129
- name: row.name,
1130
- value: row.value,
1131
- icon: _optionalChain([series, 'access', _65 => _65[i], 'optionalAccess', _66 => _66.icon])
1132
- }))
1173
+ (() => {
1174
+ const showPercent = isChartPercentStacked(options) && series.length > 1;
1175
+ const pctCtx = context;
1176
+ const pointIndex = pctCtx.point.index;
1177
+ const chartSeries = pctCtx.series.chart.series;
1178
+ return _chunk34OKRQETjs.transformTimeSeriesTooltipData.call(void 0, {
1179
+ context,
1180
+ data: series.map((s, i) => ({
1181
+ name: s.name,
1182
+ points: [],
1183
+ styles: { color: _nullishCoalesce(_nullishCoalesce(s.color, () => ( colors[i])), () => ( "")) }
1184
+ }))
1185
+ }).map((row, i) => {
1186
+ const percentage = _optionalChain([chartSeries, 'access', _65 => _65[i], 'optionalAccess', _66 => _66.points, 'optionalAccess', _67 => _67[pointIndex], 'optionalAccess', _68 => _68.percentage]);
1187
+ return {
1188
+ color: row.color,
1189
+ name: row.name,
1190
+ value: row.value,
1191
+ icon: _optionalChain([series, 'access', _69 => _69[i], 'optionalAccess', _70 => _70.icon]),
1192
+ ...showPercent && typeof percentage === "number" && !isNaN(percentage) ? { percent: percentage } : {}
1193
+ };
1194
+ });
1195
+ })()
1133
1196
  );
1134
1197
  const position = isPie || isPackedBubble ? _nullishCoalesce(ctx.key, () => ( ctx.point.name)) : context.x;
1135
- const tickPositions = _optionalChain([ctx, 'access', _67 => _67.points, 'optionalAccess', _68 => _68[0], 'optionalAccess', _69 => _69.series, 'optionalAccess', _70 => _70.xAxis, 'optionalAccess', _71 => _71.tickPositions]);
1136
- const resolved = _optionalChain([annotationLookup, 'optionalAccess', _72 => _72.get, 'call', _73 => _73(context.point.x)]);
1198
+ const tickPositions = _optionalChain([ctx, 'access', _71 => _71.points, 'optionalAccess', _72 => _72[0], 'optionalAccess', _73 => _73.series, 'optionalAccess', _74 => _74.xAxis, 'optionalAccess', _75 => _75.tickPositions]);
1199
+ const resolved = _optionalChain([annotationLookup, 'optionalAccess', _76 => _76.get, 'call', _77 => _77(context.point.x)]);
1137
1200
  const annotation = resolved ? {
1138
1201
  icon: resolved.icon,
1139
1202
  color: resolved.color,
@@ -1180,4 +1243,4 @@ function ChartRenderer({
1180
1243
 
1181
1244
 
1182
1245
  exports.datetimeFormatter = datetimeFormatter; exports.valueFormatter = valueFormatter; exports.buildBaseChartOptions = buildBaseChartOptions; exports.deriveMaxFontSize = deriveMaxFontSize; exports.deriveMaxWordCount = deriveMaxWordCount; exports.deriveWordPadding = deriveWordPadding; exports.buildWordCloudOptions = buildWordCloudOptions; exports.useSeedsChartSetup = useSeedsChartSetup; exports.ChartRenderer = ChartRenderer;
1183
- //# sourceMappingURL=chunk-NU2RCATK.js.map
1246
+ //# sourceMappingURL=chunk-5TWNMPQU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/_work/seeds/seeds/seeds-react/seeds-react-data-viz/dist/chunk-5TWNMPQU.js","../src/charts/shared/baseChartOptions.ts","../src/charts/wordcloud/adapter.ts","../src/charts/shared/chartBase.tsx","../src/charts/shared/ChartAnnotationMarkerPortal.tsx","../src/charts/shared/DefaultChartTooltip.tsx","../src/charts/shared/ChartLabelWithIcon.tsx","../src/charts/shared/formatters/datetimeFormatter.ts","../src/charts/shared/formatters/valueFormatter.ts","../src/charts/shared/SeriesLegend.tsx","../src/charts/shared/stacking.ts","../src/charts/shared/chartExport.ts"],"names":["useEffect","useRef","useState","memo","jsxs","jsx"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACoBO,SAAS,qBAAA,CAAsB;AAAA,EACpC,IAAA;AAAA,EACA,WAAA;AAAA,EACA,iBAAA;AAAA,EACA,KAAA;AAAA,EACA,iBAAA,EAAmB,KAAA;AAAA,EACnB;AACF,CAAA,EASE;AACA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO;AAAA,MACL,IAAA;AAAA,MACA,UAAA,EAAY,IAAA;AAAA,MACZ,SAAA,EAAW,KAAA;AAAA,MACX,GAAI,iBAAA,EAAmB,EAAE,SAAA,EAAW,GAAG,EAAA,EAAI,CAAC;AAAA,IAC9C,CAAA;AAAA,IACA,aAAA,EAAe,EAAE,WAAA,EAAa,iBAAA,EAAmB,MAAM,CAAA;AAAA,IACvD,KAAA,EAAO,EAAE,IAAA,EAAM,GAAG,CAAA;AAAA,IAClB,OAAA,EAAS,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,IAC1B,MAAA,EAAQ,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,IACzB,OAAA,EAAS;AAAA,MACP,OAAA,EAAS,IAAA;AAAA,MACT,OAAA,EAAS,IAAA;AAAA,MACT,OAAA,EAAS,CAAA;AAAA,MACT,KAAA,EAAO,MAAA;AAAA,MACP,MAAA,EAAQ,IAAA;AAAA,MACR,SAAA,EAAW;AAAA,IACb,CAAA;AAAA;AAAA;AAAA,IAGA,GAAI,SAAA,EAAW,EAAE,IAAA,EAAM,EAAE,SAAS,EAAE,EAAA,EAAI,CAAC;AAAA,EAC3C,CAAA;AACF;AD3BA;AACA;AEjCA,IAAM,sBAAA,EAA0C,CAAC,GAAA,EAAK,CAAC,CAAA;AAChD,IAAM,yBAAA,EAA2B,EAAA;AASxC,IAAM,iCAAA,EAAmC,EAAA;AAEzC,IAAM,iCAAA,EAAmC,EAAA;AAGzC,IAAM,uCAAA,EAAyC,EAAA;AAQxC,SAAS,iBAAA,CAAkB,KAAA,EAAuB;AACvD,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,gCAAA;AACnB,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,sCAAsC,CAAA;AACzE,EAAA,OAAO,IAAA,CAAK,GAAA;AAAA,IACV,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,gCAAgC,CAAA;AAAA,IAClD;AAAA,EACF,CAAA;AACF;AAMA,IAAM,8BAAA,EAAgC,GAAA;AAQtC,IAAM,8BAAA,EAAgC,EAAA;AACtC,IAAM,oCAAA,EAAsC,CAAA;AAerC,SAAS,kBAAA,CAAmB,KAAA,EAAuB;AACxD,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,6BAAA;AACnB,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,mCAAmC,CAAA;AACtE,EAAA,OAAO,IAAA,CAAK,GAAA;AAAA,IACV,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,6BAA6B,CAAA;AAAA,IAC/C;AAAA,EACF,CAAA;AACF;AAIA,IAAM,gCAAA,EAAkC,CAAA;AAGxC,IAAM,gCAAA,EAAkC,CAAA;AACxC,IAAM,sCAAA,EAAwC,GAAA;AAavC,SAAS,iBAAA,CAAkB,KAAA,EAAuB;AACvD,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,+BAAA;AACnB,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,qCAAqC,CAAA;AACxE,EAAA,OAAO,IAAA,CAAK,GAAA;AAAA,IACV,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,+BAA+B,CAAA;AAAA,IACjD;AAAA,EACF,CAAA;AACF;AAaO,IAAM,iCAAA,EAAoD,CAC/D,OAAA,EACA,EAAE,MAAM,CAAA,EAAA,GACL;AACH,EAAA,MAAM,SAAA,EAAW,KAAA,CAAM,MAAA,EAAQ,KAAA,CAAM,MAAA,EAAQ,KAAA,CAAM,OAAA,EAAS,KAAA,CAAM,MAAA;AAClE,EAAA,GAAA,CAAI,QAAA,EAAU,GAAA,EAAO,OAAO,KAAA;AAC5B,EAAA,MAAM,EAAA,EAAI,QAAA,EAAU,GAAA;AACpB,EAAA,MAAM,OAAA,EAAS;AAAA,IACb,CAAA,EAAG,KAAA,CAAM,OAAA,EAAS,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AAAA,IAChC,CAAA,EAAG,KAAA,CAAM,OAAA,EAAS,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAC;AAAA,EAClC,CAAA;AACA,EAAA,GAAA,CAAI,CAAA,CAAE,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,CAAC,CAAC,EAAA,EAAI,QAAA,CAAA,EAAW;AAClE,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,MAAA;AACT,CAAA;AAaO,IAAM,kCAAA,EAAgE,CAC3E,KAAA,EAAA,GACG;AACH,EAAA,MAAM,YAAA,mBAAc,KAAA,CAAM,MAAA,CAAO,OAAA,CAAQ,WAAA,UAAe,GAAA;AACxD,EAAA,KAAA,CAAM,WAAA,EAAa;AAAA,IACjB,KAAA,EAAO,KAAA,CAAM,UAAA,CAAW,MAAA,EAAQ,YAAA,EAAc,CAAA;AAAA,IAC9C,MAAA,EAAQ,KAAA,CAAM,UAAA,CAAW,OAAA,EAAS,YAAA,EAAc;AAAA,EAClD,CAAA;AAIA,EAAA,OAAO,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,QAAA,EAAU,MAAM,CAAA;AACvC,CAAA;AAsBA,IAAM,sBAAA,EACJ,0HAAA;AAEK,SAAS,eAAA,CAAgB,IAAA,EAAuB;AACrD,EAAA,OAAO,qBAAA,CAAsB,IAAA,CAAK,IAAI,CAAA;AACxC;AAGO,SAAS,sBAAA,CACd,OAAA,EACA,MAAA,EACQ;AACR,EAAA,GAAA,CAAI,CAAC,OAAA,GAAU,MAAA,CAAO,OAAA,IAAW,EAAA,GAAK,QAAA,GAAW,IAAA,EAAM,OAAO,CAAA;AAC9D,EAAA,MAAM,MAAA,EAAQ,MAAA,CAAO,SAAA,CAAU,CAAC,KAAA,EAAA,GAAU,KAAA,CAAM,GAAA,IAAO,OAAO,CAAA;AAC9D,EAAA,OAAO,MAAA,IAAU,CAAA,EAAA,EAAK,EAAA,EAAI,KAAA;AAC5B;AAGO,SAAS,eAAA,CACd,MAAA,EACA,GAAA,EACA,GAAA,EACQ;AACR,EAAA,OAAO,IAAA,IAAQ,IAAA,EAAM,EAAA,EAAA,CAAK,OAAA,EAAS,GAAA,EAAA,EAAA,CAAQ,IAAA,EAAM,GAAA,CAAA;AACnD;AAaO,SAAS,cAAA,CAAe,MAAA,EAAwB;AACrD,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,CAAC,CAAC,CAAA;AACrC;AAmBO,SAAS,mBAAA,CAAoB;AAAA,EAClC,MAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAAsC;AACpC,EAAA,GAAA,CAAI,SAAA,EAAW,OAAO,CAAA;AACtB,EAAA,MAAM,CAAC,QAAA,EAAU,QAAQ,EAAA,EAAI,YAAA;AAC7B,EAAA,GAAA,CAAI,WAAA,EAAa,OAAO,QAAA;AACxB,EAAA,MAAM,EAAA,EAAI,eAAA,CAAgB,MAAA,EAAQ,GAAA,EAAK,GAAG,CAAA;AAC1C,EAAA,OAAO,SAAA,EAAW,EAAA,EAAA,CAAK,SAAA,EAAW,QAAA,CAAA;AACpC;AAwBO,SAAS,yBAAA,CACd,IAAA,EACA,YAAA,EAC2B;AAC3B,EAAA,MAAM,QAAA,kBAAU,IAAI,GAAA,CAAmD,CAAA;AACvE,EAAA,IAAA,CAAA,MAAW,MAAA,GAAS,IAAA,EAAM;AACxB,IAAA,MAAM,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AACxC,IAAA,GAAA,CAAI,MAAA,EAAQ,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AAAA,IAAA,KACxB,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,OAAA,EAAS,CAAC,KAAK,CAAC,CAAA;AAAA,EACzC;AACA,EAAA,IAAA,CAAA,MAAW,OAAA,GAAU,OAAA,CAAQ,MAAA,CAAO,CAAA,EAAG;AACrC,IAAA,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,MAAM,CAAA;AAAA,EAC3C;AAEA,EAAA,MAAM,aAAA,EAAe,CAAC,GAAG,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA;AACzC,EAAA,MAAM,OAAA,EAAoC,CAAC,CAAA;AAC3C,EAAA,IAAA,CAAA,IAAS,MAAA,EAAQ,CAAA,EAAG,MAAA,CAAO,OAAA,EAAS,YAAA,EAAc,KAAA,EAAA,EAAS;AACzD,IAAA,IAAI,eAAA,EAAiB,KAAA;AACrB,IAAA,IAAA,CAAA,MAAW,OAAA,GAAU,YAAA,EAAc;AACjC,MAAA,GAAA,CAAI,MAAA,CAAO,OAAA,GAAU,YAAA,EAAc,KAAA;AACnC,MAAA,GAAA,CAAI,MAAA,EAAQ,MAAA,CAAO,MAAA,EAAQ;AACzB,QAAA,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,KAAK,CAAE,CAAA;AAC1B,QAAA,eAAA,EAAiB,IAAA;AAAA,MACnB;AAAA,IACF;AACA,IAAA,GAAA,CAAI,CAAC,cAAA,EAAgB,KAAA;AAAA,EACvB;AACA,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,MAAM,CAAA;AAClD;AAkCO,SAAS,8BAAA,CACd,IAAA,EACA,MAAA,EACA,YAAA,EACA,aAAA,EAAuB,6BAAA,EACvB,YAAA,EAAsB,wBAAA,EACtB,YAAA,EAAsB,gCAAA,EACO;AAC7B,EAAA,MAAM,UAAA,EAAY,OAAA,CAAQ,OAAA,GAAU,MAAA,CAAO,OAAA,EAAS,CAAC,CAAA;AACrD,EAAA,MAAM,OAAA,EAAS,UAAA,EACX,yBAAA,CAA0B,IAAA,EAAM,YAAY,EAAA,EAC5C,CAAC,GAAG,IAAI,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,MAAM,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,YAAY,CAAA;AAEvE,EAAA,MAAM,kBAAA,EAAoB,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAA,GAAU,cAAA,CAAe,KAAA,CAAM,MAAM,CAAC,CAAA;AAC5E,EAAA,MAAM,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,iBAAiB,CAAA;AACzC,EAAA,MAAM,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,iBAAiB,CAAA;AAEzC,EAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAO,CAAA,EAAA,GAAM;AAC9B,IAAA,MAAM,WAAA,EAAa,sBAAA,CAAuB,KAAA,CAAM,OAAA,EAAS,MAAM,CAAA;AAC/D,IAAA,MAAM,WAAA,EAAa,iBAAA,CAAkB,CAAC,CAAA;AAItC,IAAA,MAAM,EAAA,EAAI,eAAA,CAAgB,UAAA,EAAY,GAAA,EAAK,GAAG,CAAA;AAK9C,IAAA,MAAM,OAAA,EAAS,YAAA,EAAc,EAAA,EAAA,CAAK,YAAA,EAAc,WAAA,CAAA;AAChD,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,KAAA,CAAM,IAAA;AAAA,MACZ,MAAA;AAAA,MACA,UAAA;AAAA,MACA,OAAA,EAAS,mBAAA,CAAoB;AAAA,QAC3B,MAAA,EAAQ,UAAA;AAAA,QACR,GAAA;AAAA,QACA,GAAA;AAAA,QACA,SAAA;AAAA,QACA,WAAA,EAAa,eAAA,CAAgB,KAAA,CAAM,IAAI,CAAA;AAAA,QACvC;AAAA,MACF,CAAC,CAAA;AAAA,MACD,SAAA,EAAW,CAAA,4BAAA,EAA+B,UAAU,CAAA,6BAAA,EAAgC,CAAC,CAAA,CAAA;AAC3B,MAAA;AAC5D,IAAA;AACD,EAAA;AACH;AAYsB;AACP,EAAA;AACO,IAAA;AACmC,IAAA;AACxB,IAAA;AACL,IAAA;AAC1B,EAAA;AACoC,EAAA;AACD,EAAA;AACgC,IAAA;AAClB,IAAA;AACjD,EAAA;AACmE,EAAA;AACrE;AAWqB;AACwB,EAAA;AAKF,EAAA;AAEmB,EAAA;AAC7C,EAAA;AACP,IAAA;AACA,IAAA;AACN,IAAA;AACsC,IAAA;AACtC,IAAA;AACA,IAAA;AACF,EAAA;AAEmC,EAAA;AAC3B,IAAA;AACa,IAAA;AACpB,EAAA;AAEM,EAAA;AACF,IAAA;AACI,IAAA;AACG,MAAA;AACF,MAAA;AACkB,MAAA;AAC1B,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ2C,IAAA;AAC9B,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUgB,MAAA;AAChB,MAAA;AACE,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQW,QAAA;AAAA;AAAA;AAAA;AAItB,QAAA;AACA,QAAA;AAAA;AAAA;AAG4C,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASpC,QAAA;AAAA;AAAA;AAGsB,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQX,QAAA;AAEyC,QAAA;AACrD,QAAA;AACG,UAAA;AAEwC,YAAA;AAChB,8BAAA;AAEqB,cAAA;AACI,cAAA;AAEnD,YAAA;AAEW,YAAA;AACW,cAAA;AACR,cAAA;AACO,cAAA;AACL,gBAAA;AACW,gBAAA;AACxB,cAAA;AAEH,YAAA;AAEM,YAAA;AACmB,cAAA;AAEzB,YAAA;AACN,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAIM,IAAA;AACR,EAAA;AACF;AF/O4F;AACA;AGjTrE;AACS;AACI;AACF;AACF;AACO;AACN;AACN;AACK;AAChC;AACE;AACAA;AACA;AACAC;AACAC;AAEK;AACkB;AHkTmE;AACA;AInU1C;AACrB;AACT;AACC;AAiBjB;AAV2B;AAC7B,EAAA;AACA,EAAA;AAMC;AAEuD,EAAA;AAElD,IAAA;AAAC,MAAA;AAAA,MAAA;AACS,QAAA;AACG,QAAA;AACI,QAAA;AACT,QAAA;AACC,QAAA;AACJ,QAAA;AACU,QAAA;AAEkC,QAAA;AAAe,MAAA;AAE9D,IAAA;AACJ,oBAAA;AAAC,MAAA;AAAA,MAAA;AACsB,QAAA;AACf,QAAA;AACkB,QAAA;AAAA,MAAA;AAC1B,IAAA;AACF,EAAA;AAEJ;AAKoB;AAEuB;AACJ,EAAA;AACnC,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAUC,EAAA;AACqE,IAAA;AACjC,IAAA;AACsB,IAAA;AAE3C,IAAA;AACiB,MAAA;AAEwB,MAAA;AACH,MAAA;AAChC,QAAA;AACpB,MAAA;AAI6B,MAAA;AACiB,QAAA;AAC9C,MAAA;AAEyE,MAAA;AACnC,QAAA;AACjB,QAAA;AAMc,QAAA;AACb,UAAA;AACpB,QAAA;AACoB,QAAA;AAC0B,UAAA;AACN,UAAA;AACxC,QAAA;AAC8C,QAAA;AAET,QAAA;AACb,QAAA;AACzB,MAAA;AAE8B,MAAA;AACnB,MAAA;AAIgB,MAAA;AAGd,QAAA;AAC+B,UAAA;AACnB,YAAA;AACpB,UAAA;AAC0D,UAAA;AACb,YAAA;AAC7C,UAAA;AACgD,UAAA;AACjD,QAAA;AACJ,MAAA;AAC4D,IAAA;AAItB,IAAA;AACV,MAAA;AAC+B,QAAA;AAElD,QAAA;AACE,0BAAA;AAAC,YAAA;AAAA,YAAA;AACC,cAAA;AACY,cAAA;AAAA,YAAA;AACd,UAAA;AACA,UAAA;AAEF,QAAA;AACN,MAAA;AAEJ,IAAA;AAEJ,EAAA;AACF;AJwR4F;AACA;AKzaxE;AACoB;AACnB;AL2auE;AACA;AMhbvE;AACmB;AAWlC;AAH4BC;AAC6C,EAAA;AAGvE,IAAA;AAAyC,sBAAA;AACxC,MAAA;AAGH,IAAA;AAEJ,EAAA;AACF;ANwa4F;AACA;AO/Y7E;AACC;AAQK;AACD;AAQ6C;AACZ,EAAA;AAC3C,IAAA;AACC,IAAA;AACF,IAAA;AACK,IAAA;AACX,EAAA;AACyC,EAAA;AACnC,EAAA;AAC8D,IAAA;AACI,IAAA;AACF,IAAA;AACvE,EAAA;AACF;AAgB0B;AACM,EAAA;AACrB,IAAA;AACT,EAAA;AAE0B,EAAA;AACqB,EAAA;AACjB,IAAA;AACI,IAAA;AAGc,IAAA;AAC5C,MAAA;AACF,IAAA;AACiC,IAAA;AACnC,EAAA;AAC2B,EAAA;AAEa,EAAA;AAIhB,EAAA;AACI,EAAA;AAItB,EAAA;AAEe,EAAA;AACZ,IAAA;AACT,EAAA;AACsB,EAAA;AACb,IAAA;AACT,EAAA;AAC2B,EAAA;AAClB,IAAA;AACT,EAAA;AAC0B,EAAA;AACjB,IAAA;AACT,EAAA;AACO,EAAA;AACT;AAYkC;AAChC,EAAA;AACiB,EAAA;AAAA;AAEN,EAAA;AACE,EAAA;AACA,EAAA;AAC2B;AAEkB,EAAA;AACxB,IAAA;AAClC,EAAA;AAEmD,EAAA;AAEN,EAAA;AACf,EAAA;AACmB,EAAA;AAEjB,EAAA;AAC6B,EAAA;AACO,EAAA;AAEtB,EAAA;AAGM,EAAA;AACC,EAAA;AAEhC,EAAA;AACd,IAAA;AAGyC,MAAA;AAEe,MAAA;AACJ,QAAA;AACvD,MAAA;AACA,MAAA;AACG,IAAA;AACA,IAAA;AACiC,MAAA;AAC2B,MAAA;AACxB,QAAA;AACvC,MAAA;AACA,MAAA;AACG,IAAA;AACiC,MAAA;AACyB,MAAA;AACrB,QAAA;AACxC,MAAA;AACA,MAAA;AACG,IAAA;AACkC,MAAA;AACrC,MAAA;AACF,IAAA;AACsB,MAAA;AACpB,MAAA;AACJ,EAAA;AAEoD,EAAA;AAC/C,IAAA;AACO,IAAA;AACO,EAAA;AAE4B,EAAA;AACS,IAAA;AACjD,MAAA;AACO,MAAA;AACO,IAAA;AACS,IAAA;AAC9B,EAAA;AAEiB,EAAA;AACnB;AAqBuC;AACrC,EAAA;AACiB,EAAA;AAAA;AAEN,EAAA;AACE,EAAA;AACA,EAAA;AACoB;AAEyB,EAAA;AACrC,IAAA;AACrB,EAAA;AAEmD,EAAA;AACnB,EAAA;AAE5B,EAAA;AACiB,EAAA;AACd,IAAA;AAKG,MAAA;AACW,QAAA;AACF,QAAA;AACF,QAAA;AACC,QAAA;AACA,QAAA;AACE,QAAA;AACA,QAAA;AAEV,MAAA;AACW,QAAA;AACF,QAAA;AACF,QAAA;AACC,QAAA;AACA,QAAA;AACR,MAAA;AACN,MAAA;AACG,IAAA;AACwC,MAAA;AAC3C,MAAA;AACG,IAAA;AACyB,MAAA;AAC5B,MAAA;AACG,IAAA;AACA,IAAA;AACL,IAAA;AAGY,MAAA;AACC,QAAA;AACH,QAAA;AACC,QAAA;AACF,QAAA;AACP,MAAA;AACA,MAAA;AACJ,EAAA;AAE2C,EAAA;AACtC,IAAA;AACO,IAAA;AACO,EAAA;AACrB;APiT4F;AACA;AQpmB7D;AACD;AAkCC;AAC7B,EAAA;AACe,EAAA;AACJ,EAAA;AACE,EAAA;AACE,EAAA;AACf,EAAA;AACA,EAAA;AACoC;AACJ,EAAA;AAMT,EAAA;AAC6C,IAAA;AACpE,EAAA;AAEiC,EAAA;AACT,IAAA;AACX,MAAA;AACD,MAAA;AACM,MAAA;AACf,IAAA;AACH,EAAA;AAEgC,EAAA;AACiC,IAAA;AAC1C,IAAA;AACX,MAAA;AACA,MAAA;AACA,MAAA;AACT,IAAA;AACH,EAAA;AAGqB,EAAA;AACnB,IAAA;AACA,IAAA;AACQ,IAAA;AACA,IAAA;AACA,IAAA;AACT,EAAA;AACH;AR4jB4F;AACA;AKxkBxF;AAvCiB;AACnB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACa,EAAA;AAOJ;AACgC,EAAA;AACX,EAAA;AACrB,IAAA;AACP,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AACH;AAM0B;AACxB,EAAA;AACA,EAAA;AACA,EAAA;AAKC;AAC2C,EAAA;AACA,EAAA;AAGvC,EAAA;AAEGC,IAAAA;AAC0D,MAAA;AAGnD,MAAA;AAKP,IAAA;AAGC,IAAA;AAKP,EAAA;AAEJ;AAQoC;AAClC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACe,EAAA;AACf,EAAA;AACY,EAAA;AACe;AAOkC,EAAA;AAEhC,EAAA;AAIrB,IAAA;AAEW,IAAA;AAGX,MAAA;AAKN,IAAA;AAKkB,IAAA;AAwBI,IAAA;AACb,MAAA;AACE,QAAA;AACL,UAAA;AACA,UAAA;AAGa,YAAA;AAAkB,cAAA;AAAE,cAAA;AACzB,YAAA;AACC,YAAA;AACH,YAAA;AACyD,YAAA;AAC/D,UAAA;AACA,UAAA;AACiC,YAAA;AACxB,YAAA;AACH,YAAA;AACyD,YAAA;AAC/D,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAQO,IAAA;AACE,MAAA;AACL,QAAA;AACA,QAAA;AAGW,UAAA;AACI,4BAAA;AAAkB,cAAA;AAAE,cAAA;AAAC,YAAA;AACgB,4BAAA;AAGxB,UAAA;AAEnB,UAAA;AACT,QAAA;AACF,MAAA;AACF,IAAA;AACD,EAAA;AAMsD,EAAA;AAGW,EAAA;AAK5D,EAAA;AACE,IAAA;AACiB,MAAA;AACR,MAAA;AACL,QAAA;AAAA;AAAA;AAAA;AAIkC,UAAA;AAES,UAAA;AAI3C,QAAA;AACA,QAAA;AAAA;AAIgB,UAAA;AAGD,YAAA;AACI,YAAA;AACK,YAAA;AAEpB,UAAA;AAEK,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQH,UAAA;AACM,YAAA;AACG,YAAA;AACE,cAAA;AACK,cAAA;AACd,YAAA;AAED,UAAA;AACP,QAAA;AACF,MAAA;AACF,IAAA;AAED,EAAA;AAIF,EAAA;AACCC,IAAAA;AAAC,MAAA;AAAA,MAAA;AACkB,QAAA;AACC,QAAA;AACM,QAAA;AAAA,MAAA;AAExB,IAAA;AAGa,IAAA;AACX,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAEJ,IAAA;AAEFA,oBAAAA;AAAC,MAAA;AAAA,MAAA;AAC4B,QAAA;AAAA,MAAA;AAC7B,IAAA;AAGK,IAAA;AAEoD,sBAAA;AAE9C,sBAAA;AAOP,IAAA;AACN,EAAA;AAEJ;ALkf4F;AACA;AS5yBvE;AACa;AA0B5B;AALoE;AACxE,EAAA;AACC;AACmC,EAAA;AAEW,IAAA;AAEjC,IAAA;AACZ,EAAA;AACwC,EAAA;AAC3C;ATyxB2F;AACA;AUjzBjB;AAC7C,EAAA;AACH,EAAA;AACS,EAAA;AAIgB,IAAA;AAClD,EAAA;AACF;AVgzB4F;AACA;AWlyBf;AAC/C,EAAA;AAC6C,EAAA;AACR,EAAA;AACnE;AAEwC;AAIb,EAAA;AAC3B;AXgyB4F;AACA;AGqPxF;AAphC8B;AACF;AAMF;AACO;AACN;AAEN;AAEK;AAoDN;AAOtB;AAEA;AA8YkE;AACtC,EAAA;AACH,EAAA;AACS,EAAA;AAImB,IAAA;AACrD,EAAA;AACF;AA0CE;AACA,EAAA;AACkB,EAAA;AAClB,EAAA;AAC0B,EAAA;AACD,EAAA;AACzB,EAAA;AAC2D;AACpC,EAAA;AACM,EAAA;AAKR,EAAA;AAKb,IAAA;AAEc,IAAA;AACtB,EAAA;AAKgB,EAAA;AAKX,IAAA;AAIO,MAAA;AACoD,QAAA;AAC5D,MAAA;AACF,IAAA;AACC,EAAA;AACM,IAAA;AACP,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAEwD,EAAA;AACnB,IAAA;AACT,IAAA;AACmB,IAAA;AACpB,MAAA;AACyB,MAAA;AACnD,IAAA;AACO,IAAA;AACiB,EAAA;AAEmC,EAAA;AAC7B,IAAA;AACQ,IAAA;AACQ,IAAA;AACpB,MAAA;AACK,MAAA;AAC/B,IAAA;AACO,IAAA;AACyB,EAAA;AAEqB,EAAA;AACzD;AA2IiC;AAC/B,EAAA;AACA,EAAA;AACa,EAAA;AACkC;AAEZ,EAAA;AAEf,EAAA;AACuB,IAAA;AACI,IAAA;AACrC,IAAA;AACC,MAAA;AACoB,QAAA;AACP,QAAA;AACc,QAAA;AACW,QAAA;AAC7C,MAAA;AACF,IAAA;AACF,EAAA;AACO,EAAA;AACE,IAAA;AACO,MAAA;AACuC,MAAA;AACrD,IAAA;AAAA;AAEkB,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMV,IAAA;AACG,MAAA;AACK,MAAA;AACA,MAAA;AACD,MAAA;AACF,MAAA;AACgB,QAAA;AACP,QAAA;AACc,QAAA;AACW,QAAA;AAC7C,MAAA;AACF,IAAA;AACO,IAAA;AACF,MAAA;AACQ,MAAA;AACuC,QAAA;AACzC,QAAA;AACT,MAAA;AACF,IAAA;AACO,IAAA;AACF,MAAA;AAAA;AAAA;AAAA;AAIQ,MAAA;AAC2D,QAAA;AACtE,MAAA;AACF,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOI,IAAA;AACS,MAAA;AACC,QAAA;AACC,QAAA;AACoB,UAAA;AACP,UAAA;AACc,UAAA;AACS,UAAA;AAC3C,QAAA;AACF,MAAA;AAED,IAAA;AACP,EAAA;AACF;AAaqB;AAEsC,EAAA;AAKrC,EAAA;AAKZ,IAAA;AACoC,MAAA;AAClB,MAAA;AACxB,IAAA;AACF,EAAA;AAEO,EAAA;AAC8D,IAAA;AACI,IAAA;AAChB,IAAA;AAIO,MAAA;AAC1C,MAAA;AAC2B,MAAA;AAC/C,IAAA;AACuD,IAAA;AAKL,MAAA;AACrB,MAAA;AACc,MAAA;AACrC,MAAA;AACgB,QAAA;AAClB,MAAA;AACqB,QAAA;AACvB,MAAA;AACF,IAAA;AACF,EAAA;AACF;AAE8B;AAC5B,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACa,EAAA;AACb,EAAA;AACA,EAAA;AACqB;AAC2C,EAAA;AAClB,EAAA;AAKD,EAAA;AAGW,EAAA;AACjC,EAAA;AAKqC,EAAA;AACN,EAAA;AAQgB,EAAA;AAGjE,IAAA;AACa,IAAA;AACQ,MAAA;AAGgB,MAAA;AACxC,IAAA;AACG,EAAA;AAI4B,EAAA;AACZ,EAAA;AAML,EAAA;AACF,IAAA;AACD,oBAAA;AACT,MAAA;AAAwB,QAAA;AACwB,QAAA;AAChD,MAAA;AACF,IAAA;AACQ,EAAA;AAKM,EAAA;AACF,IAAA;AACkB,IAAA;AAC4B,IAAA;AAC1B,MAAA;AAC/B,IAAA;AACmB,IAAA;AACZ,EAAA;AAK8B,EAAA;AAQN,IAAA;AACiB,IAAA;AAMA,IAAA;AAC1C,IAAA;AACF,MAAA;AAEC,MAAA;AACe,QAAA;AACA,UAAA;AACG,UAAA;AACY,YAAA;AACZ,YAAA;AAC4B,cAAA;AACO,cAAA;AAClB,gBAAA;AAIkB,gBAAA;AAClC,kBAAA;AACa,oBAAA;AACuB,oBAAA;AACtB,oBAAA;AACuB,oBAAA;AAC5C,kBAAA;AACsC,kBAAA;AACvC,gBAAA;AACH,cAAA;AACF,YAAA;AACF,UAAA;AACF,QAAA;AAED,MAAA;AACU,MAAA;AACuB,QAAA;AAEhC,QAAA;AACS,UAAA;AAC8C,YAAA;AAC7B,cAAA;AAGa,cAAA;AACnB,gBAAA;AACuB,gBAAA;AACpC,cAAA;AACH,YAAA;AACF,UAAA;AAED,QAAA;AACP,MAAA;AAGK,MAAA;AAAA;AAAA;AAGwC,MAAA;AAAA;AAAA;AAGlC,MAAA;AACe,QAAA;AACqB,QAAA;AAC/C,MAAA;AACF,IAAA;AACU,EAAA;AAKoB,EAAA;AAEH,EAAA;AAGzB,EAAA;AAKA,EAAA;AAA2B,oBAAA;AAC3BA,oBAAAA;AAAC,MAAA;AAAA,MAAA;AACa,QAAA;AAEH,QAAA;AACC,QAAA;AAAA,MAAA;AACZ,IAAA;AAEEA,IAAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AACuD,QAAA;AACjB,QAAA;AACtC,QAAA;AAAA,MAAA;AAEA,IAAA;AAEFA,IAAAA;AAAC,MAAA;AAAA,MAAA;AACC,QAAA;AAC4B,QAAA;AAKd,UAAA;AAoB0B,UAAA;AACR,UAAA;AACM,UAAA;AAAA;AAAA;AAGhC,YAAA;AACE,cAAA;AAIsB,gBAAA;AACO,gBAAA;AACV,gBAAA;AACQ,gBAAA;AACH,gBAAA;AACxB,cAAA;AACF,YAAA;AAEA,UAAA;AACE,YAAA;AACmB,cAAA;AACD,cAAA;AACC,cAAA;AACE,cAAA;AAAA;AAEY,cAAA;AACjC,YAAA;AACF,UAAA;AAAA;AAAA;AAGO,YAAA;AAS+C,cAAA;AACrC,cAAA;AAUiB,cAAA;AACQ,cAAA;AACF,cAAA;AACpC,gBAAA;AAC4B,gBAAA;AAClB,kBAAA;AACC,kBAAA;AACmC,kBAAA;AAC5C,gBAAA;AACe,cAAA;AAGuB,gBAAA;AACjC,gBAAA;AACM,kBAAA;AACD,kBAAA;AACC,kBAAA;AACM,kBAAA;AAIb,kBAAA;AAEN,gBAAA;AACD,cAAA;AACA,YAAA;AAAA,UAAA;AAQQ,UAAA;AAIuC,UAAA;AACU,UAAA;AAE5D,UAAA;AACiB,YAAA;AACC,YAAA;AACA,YAAA;AACM,YAAA;AAExB,UAAA;AACiB,UAAA;AACnB,YAAA;AACA,YAAA;AAAA;AAEoB,YAAA;AACpB,YAAA;AACA,YAAA;AACA,YAAA;AACG,YAAA;AACH,YAAA;AACA,YAAA;AACA,YAAA;AACD,UAAA;AACH,QAAA;AAAA,MAAA;AAEA,IAAA;AAGA,IAAA;AAAC,MAAA;AAAA,MAAA;AAC8B,QAAA;AACnB,UAAA;AACA,UAAA;AACuB,UAAA;AAC/B,QAAA;AAAA,MAAA;AAEN,IAAA;AAEJ,EAAA;AAEJ;AH3B4F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/_work/seeds/seeds/seeds-react/seeds-react-data-viz/dist/chunk-5TWNMPQU.js","sourcesContent":[null,"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","import { buildBaseChartOptions } from \"../shared/baseChartOptions\";\nimport { WORD_CLOUD_CHART_HEIGHT } from \"../../constants/chartOptions\";\nimport type {\n SeedsChartOptions,\n WordcloudPlacementStrategy,\n WordcloudSpiral,\n} from \"../shared/chartBase\";\nimport type {\n SeedsChartWordCloudClickEvent,\n WordCloudClickData,\n WordCloudDataPoint,\n WordCloudGroup,\n WordCloudProps,\n} from \"./types\";\n\nconst DEFAULT_OPACITY_RANGE: [number, number] = [0.6, 1];\nexport const WORD_CLOUD_MIN_FONT_SIZE = 13;\n// Load-bearing, not cosmetic: Highcharts' `deriveFontSize` falls back to\n// `maxFontSize: 1` when the option isn't a number\n// (`isNumber(maxFontSize) ? maxFontSize : 1`, wordcloud.src.js ~1142), and\n// \"if minFontSize is larger than maxFontSize the result equals minFontSize\" —\n// so an omitted `maxFontSize` silently collapses every word to the\n// `minFontSize` floor, rendering a uniform, non-size-varying cloud. Also the\n// ceiling of `deriveMaxFontSize`'s clamp below, and its fallback when width\n// is unknown (jsdom, SSR, or before `useMeasure`'s first tick).\nconst WORD_CLOUD_MAXIMUM_MAX_FONT_SIZE = 50;\n// Floor of `deriveMaxFontSize`'s clamp.\nconst WORD_CLOUD_MINIMUM_MAX_FONT_SIZE = 24;\n// Chosen by trial and error, tuned so roughly 90-100 words render without\n// excess whitespace. Looks arbitrary — don't re-derive it.\nconst WORD_CLOUD_MAX_FONT_SIZE_WIDTH_DIVISOR = 15;\n\n/**\n * Responsive default for `maxFontSize`, derived from the chart's own\n * container width so wider charts get a higher font ceiling. A falsy width\n * (0 — jsdom, SSR, or a real browser before `ResizeObserver`'s first tick)\n * yields the ceiling rather than collapsing toward the floor.\n */\nexport function deriveMaxFontSize(width: number): number {\n if (!width) return WORD_CLOUD_MAXIMUM_MAX_FONT_SIZE;\n const derived = Math.floor(width / WORD_CLOUD_MAX_FONT_SIZE_WIDTH_DIVISOR);\n return Math.min(\n Math.max(derived, WORD_CLOUD_MINIMUM_MAX_FONT_SIZE),\n WORD_CLOUD_MAXIMUM_MAX_FONT_SIZE\n );\n}\n\n// Upper bound on rendered words — larger inputs are capped to the heaviest\n// `WORD_CLOUD_MAXIMUM_WORD_COUNT` by weight (see\n// `transformWordCloudDataToPoints`). Also the ceiling of `deriveMaxWordCount`'s\n// clamp below.\nconst WORD_CLOUD_MAXIMUM_WORD_COUNT = 100;\n// Floor of `deriveMaxWordCount`'s clamp. A narrow container still needs to fit\n// whatever words survive the cap at (at least) `minFontSize`-tall boxes, so\n// capping to fewer words shrinks the field Highcharts must lay out — that's\n// the lever that keeps the post-layout scale-to-fit step (`getScale`,\n// wordcloud.src.js ~685) from shrinking every word, including ones already at\n// the `minFontSize` floor, below what the option promises. Chosen by trial and\n// error — don't re-derive it.\nconst WORD_CLOUD_MINIMUM_WORD_COUNT = 40;\nconst WORD_CLOUD_WORD_COUNT_WIDTH_DIVISOR = 9;\n\n/**\n * Responsive cap on rendered word count, derived from the chart's own\n * container width so a narrow container renders fewer words. Highcharts lays\n * every surviving word out at (at least) `minFontSize`, then uniformly scales\n * the whole series to fit the plot box (`getScale`) — a fixed word count on a\n * narrow container needs a layout field roughly as large as on a wide one,\n * which drives that scale factor below 1 and shrinks the `minFontSize` floor\n * along with everything else. Fewer words keeps the required field closer to\n * the available plot box, so the floor actually holds on screen. A falsy\n * width (0 — jsdom, SSR, or before `ResizeObserver`'s first tick) yields the\n * ceiling rather than collapsing toward the floor, matching\n * `deriveMaxFontSize`.\n */\nexport function deriveMaxWordCount(width: number): number {\n if (!width) return WORD_CLOUD_MAXIMUM_WORD_COUNT;\n const derived = Math.floor(width / WORD_CLOUD_WORD_COUNT_WIDTH_DIVISOR);\n return Math.min(\n Math.max(derived, WORD_CLOUD_MINIMUM_WORD_COUNT),\n WORD_CLOUD_MAXIMUM_WORD_COUNT\n );\n}\n\n// Ceiling of `deriveWordPadding`'s clamp — the flat default this family\n// shipped with before padding became responsive.\nconst WORD_CLOUD_MAXIMUM_WORD_PADDING = 4;\n// Floor of `deriveWordPadding`'s clamp. Chosen by trial and error — don't\n// re-derive it.\nconst WORD_CLOUD_MINIMUM_WORD_PADDING = 1;\nconst WORD_CLOUD_WORD_PADDING_WIDTH_DIVISOR = 150;\n\n/**\n * Responsive default for `wordPadding`, derived from the chart's own\n * container width. `wordCloudPaddingPlacementStrategy` inflates every word's\n * measured box by this amount on every side before Highcharts ever sees it —\n * a fixed padding costs the same absolute pixels regardless of container\n * size, so on a narrow container it eats proportionally more of the already\n * smaller field, compounding the same post-layout scale-to-fit shrinkage\n * `deriveMaxWordCount` addresses. A falsy width (0 — jsdom, SSR, or before\n * `ResizeObserver`'s first tick) yields the ceiling, matching\n * `deriveMaxFontSize`/`deriveMaxWordCount`.\n */\nexport function deriveWordPadding(width: number): number {\n if (!width) return WORD_CLOUD_MAXIMUM_WORD_PADDING;\n const derived = Math.floor(width / WORD_CLOUD_WORD_PADDING_WIDTH_DIVISOR);\n return Math.min(\n Math.max(derived, WORD_CLOUD_MINIMUM_WORD_PADDING),\n WORD_CLOUD_MAXIMUM_WORD_PADDING\n );\n}\n\n/**\n * Custom spiral registered on `WordcloudSeries.prototype.spirals` (in\n * `chartBase.tsx`, the only file allowed to touch the live Highcharts\n * object) as `seedsAspectArchimedean`. Highcharts' own built-in `archimedean`\n * traces a true circle with no aspect term, so a round cloud can't grow into\n * a wide container; this version stretches the spiral by the field's aspect\n * ratio instead, producing a wider silhouette in a wider container.\n *\n * A pure function of `(attempt, field)` — exported and unit tested directly,\n * since jsdom cannot render this chart to verify its shape visually.\n */\nexport const wordCloudAspectArchimedeanSpiral: WordcloudSpiral = (\n attempt,\n { field }\n) => {\n const maxDelta = field.width * field.width + field.height * field.height;\n if (attempt > 10000) return false;\n const t = attempt * 0.8;\n const result = {\n x: field.ratioX * t * Math.cos(t),\n y: field.ratioY * t * Math.sin(t),\n };\n if (!(Math.min(Math.abs(result.x), Math.abs(result.y)) < maxDelta)) {\n return false;\n }\n return result;\n};\n\n/**\n * Custom placementStrategy registered on\n * `WordcloudSeries.prototype.placementStrategy` (in `chartBase.tsx`) as\n * `seedsWordPadding`, approximating word spacing — Highcharts' wordcloud\n * module has no native padding option. Inflates each word's measured box by\n * `wordPadding` on every side before Highcharts reads it into collision\n * testing; since the whole series is scaled to fit the plot box afterward,\n * padding is paid for in smaller rendered text, not a bigger cloud — see\n * `deriveWordPadding`'s doc comment for why this default shrinks on a narrow\n * container instead of staying fixed.\n */\nexport const wordCloudPaddingPlacementStrategy: WordcloudPlacementStrategy = (\n point\n) => {\n const wordPadding = point.series.options.wordPadding ?? 0;\n point.dimensions = {\n width: point.dimensions.width + wordPadding * 2,\n height: point.dimensions.height + wordPadding * 2,\n };\n // `rotation` must be explicitly `false`, not omitted: computing `-rotation`\n // as an angle makes `-undefined` a `NaN`, which poisons every point's\n // collision polygon and silently drops the whole cloud.\n return { x: 0, y: 0, rotation: false };\n};\n\n// Whole-string match only — \"hello 😀\" stays a dimmable keyword; only an\n// emoji-only term (optionally several, with modifiers/ZWJ/variation\n// selectors/regional-indicator flag pairs) is exempt from weight-driven\n// dimming. Stands in for an authoritative term-type field this chart doesn't\n// receive — if the heuristic misses in practice, add an explicit\n// `WordCloudDataPoint.type?: \"emoticon\"` field instead.\n// Each modifier is its own top-level alternative rather than combined into a\n// single character class (combining/joining code points inside `[...]` trip\n// ESLint's no-misleading-character-class), and functionally this whole-string\n// test only needs \"is this code point one of the allowed set,\" which\n// top-level alternation expresses just as well.\n// Deliberately omits `\\p{Emoji_Presentation}`: every code point it covers is\n// already covered by `\\p{Extended_Pictographic}`, `\\p{Regional_Indicator}`,\n// or the skin-tone modifiers below (verified against every Unicode code\n// point), so including it too makes those alternatives overlap. An\n// overlapping alternative inside a `+` repetition is a ReDoS — the engine\n// explores every way to partition a run of code points across the ambiguous\n// branches before it can fail on the trailing `$`. With the redundant\n// alternative removed, the branches are pairwise disjoint, so each character\n// matches exactly one of them and the match stays linear.\nconst EMOJI_ONLY_TERM_REGEX =\n /^(?:\\p{Extended_Pictographic}|\\p{Regional_Indicator}|\\u{1F3FB}|\\u{1F3FC}|\\u{1F3FD}|\\u{1F3FE}|\\u{1F3FF}|\\u200D|\\uFE0F)+$/u;\n\nexport function isEmojiOnlyTerm(name: string): boolean {\n return EMOJI_ONLY_TERM_REGEX.test(name);\n}\n\n/** Index of `groupId` in `groups` — palette slot 0 for an unknown/absent group or when `groups` is omitted. */\nexport function resolveGroupColorIndex(\n groupId: string | undefined,\n groups: ReadonlyArray<WordCloudGroup> | undefined\n): number {\n if (!groups || groups.length === 0 || groupId == null) return 0;\n const index = groups.findIndex((group) => group.id === groupId);\n return index === -1 ? 0 : index;\n}\n\n/** `t` in `[0, 1]`; guarded to `1` when every weight is equal so a flat distribution renders at the top of a range instead of collapsing to `NaN`/the bottom. */\nexport function normalizeWeight(\n weight: number,\n min: number,\n max: number\n): number {\n return max === min ? 1 : (weight - min) / (max - min);\n}\n\n/**\n * The curve applied to each raw weight before it's mapped onto the font-size\n * range (`transformWordCloudDataToPoints`). Log-shaped, matching the legacy\n * D3 `scaleLog()` this cloud replaces — a wide raw-weight spread (a Zipf-shaped\n * distribution: a handful of dominant words, a long tail, the common case for\n * word-count data) would otherwise render far too extreme. The opacity ramp\n * reads this same compressed value, so size and opacity derive from one curve\n * and can't drift apart. Clamped to a floor of `1` (not `0`) so a\n * zero/negative weight can't send `Math.log` to `-Infinity`/`NaN` and corrupt\n * every point's normalization.\n */\nexport function compressWeight(weight: number): number {\n return Math.log(Math.max(weight, 1));\n}\n\ninterface ResolvePointOpacityParams {\n weight: number;\n min: number;\n max: number;\n hasGroups: boolean;\n isEmojiOnly: boolean;\n opacityRange: [number, number];\n}\n\n/**\n * Layer 1 (weight ramp). Hardcoded to full opacity (`1`, independent of\n * `opacityRange`) in competitive mode — not an omission: when words are\n * grouped, size alone encodes weight, and the ramp only re-activates once a\n * group is highlighted externally (`activeGroupId`). Emoji-only terms pin to\n * the top of the *range* (which does respect a custom `opacityRange`)\n * regardless of weight.\n */\nexport function resolvePointOpacity({\n weight,\n min,\n max,\n hasGroups,\n isEmojiOnly,\n opacityRange,\n}: ResolvePointOpacityParams): number {\n if (hasGroups) return 1;\n const [rangeMin, rangeMax] = opacityRange;\n if (isEmojiOnly) return rangeMax;\n const t = normalizeWeight(weight, min, max);\n return rangeMin + t * (rangeMax - rangeMin);\n}\n\nexport interface WordCloudSeriesPoint {\n name: string;\n weight: number;\n colorIndex: number;\n opacity: number;\n className: string;\n // Carries the pre-compression weight and the source groupId back out\n // through a click event — see SeedsChartWordCloudDataPoint.custom's doc\n // comment in types.ts for why the plain `weight` field can't serve that\n // purpose here.\n custom: { groupId?: string; rawWeight: number };\n}\n\n/**\n * Partitions `data` by `groupId` and round-robins across groups (heaviest\n * first within each) so a low-volume group still gets representation once\n * `maxWordCount` would otherwise be exhausted entirely by heavier groups —\n * avoids an empty-legend-entry state when one group's terms are individually\n * lighter than another's. Points with no matching `groupId` form their own\n * bucket. Re-sorts the final selection by descending weight so the output\n * matches the ungrouped path's \"heaviest first\" order.\n */\nexport function allocateWordsAcrossGroups(\n data: ReadonlyArray<WordCloudDataPoint>,\n maxWordCount: number\n): Array<WordCloudDataPoint> {\n const buckets = new Map<string | undefined, Array<WordCloudDataPoint>>();\n for (const point of data) {\n const bucket = buckets.get(point.groupId);\n if (bucket) bucket.push(point);\n else buckets.set(point.groupId, [point]);\n }\n for (const bucket of buckets.values()) {\n bucket.sort((a, b) => b.weight - a.weight);\n }\n\n const bucketArrays = [...buckets.values()];\n const result: Array<WordCloudDataPoint> = [];\n for (let round = 0; result.length < maxWordCount; round++) {\n let addedThisRound = false;\n for (const bucket of bucketArrays) {\n if (result.length >= maxWordCount) break;\n if (round < bucket.length) {\n result.push(bucket[round]!);\n addedThisRound = true;\n }\n }\n if (!addedThisRound) break;\n }\n return result.sort((a, b) => b.weight - a.weight);\n}\n\n/**\n * Caps to the heaviest `maxWordCount` words (`deriveMaxWordCount`'s ceiling,\n * `WORD_CLOUD_MAXIMUM_WORD_COUNT`, when omitted), then computes each point's\n * colorIndex/opacity from that rendered subset — so \"heaviest/lightest\" in\n * the acceptance criteria refers to what's actually on screen, not the\n * pre-cap input. Grouped input (`groups` non-empty) caps via\n * `allocateWordsAcrossGroups` instead of a flat slice, so a group doesn't\n * lose all its words to heavier groups; ungrouped input is untouched.\n *\n * The output `weight` is a font-size *target* in `[minFontSize, maxFontSize]`,\n * not the raw input: each word's compressed weight (`compressWeight`) is mapped\n * over the rendered subset's `[min, max]` domain onto that range — the legacy\n * D3 `scaleLog().domain([min,max]).range([minFont,maxFont])`. Highcharts sizes a\n * word via `floor(max(minFont, weight / maxWeight × maxFont))`, so feeding the\n * target directly (the heaviest word's target *is* maxFont, which becomes the\n * series `maxWeight`) reproduces the mapping exactly. Feeding the bare\n * compressed value instead anchored the ramp at weight 0, not the lightest\n * word, so any dataset whose lightest count sits well above 1 — every real\n * sampled word set — crammed the whole cloud into the top of the font range.\n * Sorting/capping still happens on the *raw* weight first: `compressWeight` is\n * monotonic for positive inputs, so \"heaviest\" means the same words either way,\n * and capping before mapping keeps the min/max bounds (and the opacity ramp)\n * scoped to the rendered subset. The caller must pass the same\n * `minFontSize`/`maxFontSize` it hands Highcharts' plotOptions, or the\n * reproduction identity breaks.\n *\n * `className` also carries a per-point `--index-${i}` marker (in addition to\n * the existing `--group-${colorIndex}` one) — `WordCloud.tsx`'s\n * `resolveActiveWordIndices` and `wordcloud.css`'s index-keyed dimming rules\n * use it to raise/dim points by `activeNames` without either the active-name\n * set or any name-matching logic ever reaching this adapter.\n */\nexport function transformWordCloudDataToPoints(\n data: ReadonlyArray<WordCloudDataPoint>,\n groups: ReadonlyArray<WordCloudGroup> | undefined,\n opacityRange: [number, number],\n maxWordCount: number = WORD_CLOUD_MAXIMUM_WORD_COUNT,\n minFontSize: number = WORD_CLOUD_MIN_FONT_SIZE,\n maxFontSize: number = WORD_CLOUD_MAXIMUM_MAX_FONT_SIZE\n): Array<WordCloudSeriesPoint> {\n const hasGroups = Boolean(groups && groups.length > 0);\n const capped = hasGroups\n ? allocateWordsAcrossGroups(data, maxWordCount)\n : [...data].sort((a, b) => b.weight - a.weight).slice(0, maxWordCount);\n\n const compressedWeights = capped.map((point) => compressWeight(point.weight));\n const min = Math.min(...compressedWeights);\n const max = Math.max(...compressedWeights);\n\n return capped.map((point, i) => {\n const colorIndex = resolveGroupColorIndex(point.groupId, groups);\n const compressed = compressedWeights[i]!;\n // Word's position within the rendered subset's compressed [min,max]\n // domain — the same normalized value the opacity ramp reads, so size and\n // opacity can't diverge.\n const t = normalizeWeight(compressed, min, max);\n // Font-size target fed to Highcharts as `weight`: legacy D3\n // scaleLog().domain([min,max]).range([minFont,maxFont]). See this\n // function's doc for why the target, not the bare compressed value,\n // reaches the engine.\n const weight = minFontSize + t * (maxFontSize - minFontSize);\n return {\n name: point.name,\n weight,\n colorIndex,\n opacity: resolvePointOpacity({\n weight: compressed,\n min,\n max,\n hasGroups,\n isEmojiOnly: isEmojiOnlyTerm(point.name),\n opacityRange,\n }),\n className: `seeds-wordcloud-word--group-${colorIndex} seeds-wordcloud-word--index-${i}`,\n custom: { groupId: point.groupId, rawWeight: point.weight },\n };\n });\n}\n\n/**\n * Keyboard activation has no `stopPropagation` and its `type` isn't `\"click\"`;\n * derive `pageX`/`pageY` from the target's bounding rect for keyboard, from\n * the raw event for a mouse click. Mirrors `transformBubbleClickData`\n * (`bubble/adapter.ts`). `weight`/`groupId` read from `point.custom` — the\n * original input values, not the log-compressed sizing weight or the\n * resolved `colorIndex` that actually live on the Highcharts point.\n */\nexport function transformWordCloudClickData(\n event: SeedsChartWordCloudClickEvent\n): WordCloudClickData {\n const base = {\n name: event.point.name,\n weight: event.point.custom?.rawWeight ?? event.point.weight,\n groupId: event.point.custom?.groupId,\n pointIndex: event.point.index,\n };\n const isClickEvent = event.type === \"click\";\n if (!isClickEvent && event.target) {\n const rect = (event.target as HTMLElement).getBoundingClientRect();\n return { ...base, pageX: rect.x, pageY: rect.y };\n }\n return { ...base, pageX: event.pageX ?? 0, pageY: event.pageY ?? 0 };\n}\n\nexport function buildWordCloudOptions(\n props: WordCloudProps,\n containerWidth?: number,\n // Whether `activeNames` is *supplied at all* (not undefined) — not its\n // current null/array value, which changes on every hover and would defeat\n // `WordCloud.tsx`'s memo narrowing if it drove this. Only this derived\n // boolean crosses into the adapter; `activeNames` itself never does — see\n // `WordCloud.tsx`'s `hasActiveNames`/`resolveActiveWordIndices`.\n hasActiveNames = false\n): SeedsChartOptions {\n const opacityRange = props.opacityRange ?? DEFAULT_OPACITY_RANGE;\n // Resolved once so the [minFont, maxFont] range the transform maps onto is\n // the exact same pair handed to Highcharts' plotOptions below — the transform\n // feeds each point a font-size target as its weight, and Highcharts only\n // reproduces that target when its own maxFontSize matches.\n const minFontSize = props.minFontSize ?? WORD_CLOUD_MIN_FONT_SIZE;\n const maxFontSize =\n props.maxFontSize ?? deriveMaxFontSize(containerWidth ?? 0);\n const points = transformWordCloudDataToPoints(\n props.data,\n props.groups,\n opacityRange,\n deriveMaxWordCount(containerWidth ?? 0),\n minFontSize,\n maxFontSize\n );\n\n const base = buildBaseChartOptions({\n type: \"wordcloud\",\n description: props.description,\n });\n\n return {\n ...base,\n chart: {\n ...base.chart,\n type: \"wordcloud\",\n height: props.height ?? WORD_CLOUD_CHART_HEIGHT,\n },\n // `ChartRenderer`'s default-tooltip routing only recognizes \"pie\" and\n // \"packedbubble\" as single-point (non-cartesian) series types\n // (chartBase.tsx); an unrecognized type like \"wordcloud\" falls through to\n // the time-series branch, which reads `context.x` as a timestamp and\n // renders a bogus epoch-date title. Disabled entirely rather than adding\n // a wordcloud branch to shared code — the word's size/opacity already\n // communicates weight.\n tooltip: { ...base.tooltip, enabled: false },\n plotOptions: {\n // `series.animation: false` matches the sibling-adapter convention, but\n // the wordcloud series overrides it back to `true`: Highcharts 11.4.8's\n // WordcloudSeries.drawPoints() only builds `animatableAttribs` inside an\n // `if (animation)` branch (wordcloud.src.js ~line 1243). With animation\n // off, any point that was previously placed but fails to re-place on a\n // later redraw hits `DPU.draw()`'s point-removal path with\n // `animatableAttribs: undefined`, and `Object.keys(undefined)` throws.\n // A component's routine post-mount `chart.update()` triggers exactly\n // this, so it surfaces on a plain mount, not just on resize.\n series: { animation: false },\n wordcloud: {\n animation: true,\n // Hover dim (layer 3). Off by default for column-derived series\n // (wordcloud's base class) — pie is the only series type that\n // defaults this to `true`. Without it, Highcharts never adds\n // `.highcharts-point-inactive` to the other words on hover. Turned\n // off entirely once the consumer has opted into `activeNames`-driven\n // emphasis — otherwise Highcharts' own hover pass fights the\n // consumer's `data-active-word-indices` dimming (wordcloud.css).\n inactiveOtherPoints: !hasActiveNames,\n // Resolved above and shared with the transform's font-size mapping.\n // Must stay a number — see `WORD_CLOUD_MAXIMUM_MAX_FONT_SIZE`'s doc\n // comment for why `undefined` breaks sizing.\n minFontSize,\n maxFontSize,\n // Pinned horizontal — Highcharts' default tilts words vertical\n // (`{from: 0, to: 90}`).\n rotation: { from: 0, to: 0, orientations: 1 },\n // Highcharts' built-in rectangular spiral. It factors the field's\n // aspect ratio in natively (it's built on squareSpiral over the field\n // dimensions), so it fills a wide container and packs denser than the\n // circular `archimedean`. Chosen over the custom `seedsAspectArchimedean`\n // after a visual comparison once the font-size hierarchy fix made word\n // sizes vary enough for the circular spiral's gaps to show. The custom\n // spiral stays registered in chartBase.tsx for now; drop it if\n // rectangular holds up in-app.\n spiral: \"rectangular\",\n // Highcharts' wordcloud module defaults `style.fontWeight` to `\"900\"`\n // (heavy bold) — overridden to normal weight.\n style: { fontWeight: \"normal\" },\n // `seedsWordPadding` (a pure function above, registered onto\n // Highcharts in chartBase.tsx) approximates word spacing, which\n // Highcharts' wordcloud module has no native option for; it reads\n // `wordPadding` off this same resolved options object. An explicit\n // prop wins; otherwise derived from the measured container width\n // (`deriveWordPadding`) — see its doc comment for why a fixed\n // padding disproportionately shrinks text on a narrow container.\n placementStrategy: \"seedsWordPadding\",\n wordPadding:\n props.wordPadding ?? deriveWordPadding(containerWidth ?? 0),\n point: {\n events: {\n click: props.onClick\n ? (event: SeedsChartWordCloudClickEvent) => {\n event.stopPropagation?.();\n // Keyboard events have no `target` when not a genuine click.\n if (event.type !== \"click\" && !event.target) return;\n props.onClick!(transformWordCloudClickData(event));\n }\n : undefined,\n mouseOver: props.onWordHover\n ? (event) => {\n const point = event.target;\n if (!point) return;\n props.onWordHover!({\n name: point.name,\n groupId: point.custom?.groupId,\n });\n }\n : undefined,\n mouseOut: props.onWordHover\n ? () => {\n props.onWordHover!(null);\n }\n : undefined,\n },\n },\n },\n },\n series:\n points.length > 0\n ? [{ type: \"wordcloud\", name: \"Word Cloud\", data: points }]\n : [],\n };\n}\n","// 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 highchartsWordcloud from \"highcharts/modules/wordcloud\";\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 { isChartPercentStacked } from \"./stacking\";\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\";\nimport {\n wordCloudAspectArchimedeanSpiral,\n wordCloudPaddingPlacementStrategy,\n} from \"../wordcloud/adapter\";\nimport type {\n SeedsChartWordCloudPlotOptions,\n SeedsChartWordCloudSeriesOptions,\n} from \"../wordcloud/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 — only bubble depends on it.\nhighchartsMore(Highcharts);\n// `wordcloud` adds the wordcloud series type — only the wordcloud family depends on it.\nhighchartsWordcloud(Highcharts);\n\n/**\n * Runtime shape of the two string-keyed registries `WordcloudSeries.prototype`\n * exposes for extension (`placementStrategy`, `spirals`) — not modeled in\n * Highcharts' own `.d.ts`. `point.dimensions` is mutable here deliberately:\n * `drawPoints()` evaluates `placementStrategy(point, …)` before it reads\n * `point.dimensions` into `getPolygon(…)`, in the same comma-separated `const`\n * declarator list, so a strategy that rewrites the dimensions in place changes\n * the collision geometry Highcharts uses immediately after.\n */\ninterface WordcloudSeriesPrototype {\n placementStrategy: Record<string, WordcloudPlacementStrategy>;\n spirals: Record<string, WordcloudSpiral>;\n}\n\n/**\n * Every Highcharts Point carries a back-reference to its owning series;\n * reading a family-specific option (e.g. `wordPadding`) off the point's\n * resolved series options — rather than threading it through the strategy's\n * own `options` param, which Highcharts controls the shape of — is how a\n * registry-keyed strategy reaches its own config value.\n */\nexport type WordcloudPlacementStrategy = (\n point: {\n dimensions: { width: number; height: number };\n index: number;\n series: { options: { wordPadding?: number } };\n },\n options: {\n field: { width: number; height: number; ratioX: number; ratioY: number };\n rotation: { orientations: number; from: number; to: number };\n }\n) => { x: number; y: number; rotation?: number | false };\n\nexport type WordcloudSpiral = (\n attempt: number,\n params: { field: WordcloudSpiralField }\n) => { x: number; y: number } | false;\n\n/** The subset of Highcharts' playing-field object a spiral function reads. */\nexport interface WordcloudSpiralField {\n width: number;\n height: number;\n ratioX: number;\n ratioY: number;\n}\n\nconst wordcloudSeriesPrototype = (\n Highcharts as unknown as {\n seriesTypes: { wordcloud: { prototype: WordcloudSeriesPrototype } };\n }\n).seriesTypes.wordcloud.prototype;\n\n// Both extensions are pure functions owned by the wordcloud family\n// (`wordcloud/adapter.ts`) — this file only wires them into Highcharts'\n// engine-level registries, since it's the sole file allowed to touch the\n// live `Highcharts` object.\nwordcloudSeriesPrototype.spirals.seedsAspectArchimedean =\n wordCloudAspectArchimedeanSpiral;\nwordcloudSeriesPrototype.placementStrategy.seedsWordPadding =\n wordCloudPaddingPlacementStrategy;\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; `wordcloud` is the wordcloud family. */\nexport type SeedsChartType =\n | \"column\"\n | \"bar\"\n | \"line\"\n | \"spline\"\n | \"area\"\n | \"areaspline\"\n | \"pie\"\n | \"packedbubble\"\n | \"wordcloud\";\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 wordcloud?: SeedsChartWordCloudPlotOptions;\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 | SeedsChartWordCloudSeriesOptions\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 /** Seeds icon or partner-logo name shown before the row name; `undefined` when the row has none. */\n icon?: TypeIconName;\n /**\n * Percentage of the whole — populated by pie/donut tooltips (slice percent)\n * and by percent-stacked multi-series bar/line-area tooltips (per-series\n * percent-of-stack at the hovered x); `undefined` for all other rows.\n */\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 // Icon indexed by the hovered slice — same source the legend reads.\n icon: series[ctx.point.index]?.icon,\n },\n ]\n : // Drop v1's `readonly` wrapper (v2 is mutable); icon is forwarded\n // from `series[]` by index below since the helper doesn't carry it.\n (() => {\n // Percent-stacked multi-series charts get a per-series\n // percent-of-stack column. Highcharts sets `.percentage` on\n // each point when stacking:\"percent\"; read it the same way the\n // helper reads `.y` — off chart.series[i].points[pointIndex] —\n // so the percent lines up with its series' value. Single-series\n // is skipped (percent is trivially 100% at every x). The legacy\n // shared helper is left untouched (also used by v1 components).\n const showPercent =\n isChartPercentStacked(options) && series.length > 1;\n const pctCtx = context as unknown as {\n point: { index: number };\n series: {\n chart: {\n series: Array<{\n points?: Array<{ percentage?: number }>;\n }>;\n };\n };\n };\n const pointIndex = pctCtx.point.index;\n const chartSeries = pctCtx.series.chart.series;\n return 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, i) => {\n // Guard NaN: Highcharts emits NaN% when a stack totals 0.\n const percentage =\n chartSeries[i]?.points?.[pointIndex]?.percentage;\n return {\n color: row.color,\n name: row.name,\n value: row.value,\n icon: series[i]?.icon,\n ...(showPercent &&\n typeof percentage === \"number\" &&\n !isNaN(percentage)\n ? { percent: percentage }\n : {}),\n };\n });\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 { ChartLabelWithIcon } from \"./ChartLabelWithIcon\";\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 // More than one row carrying a valid percent means a percent-stacked\n // bar/line-area tooltip (N series rows). Give percent its own column so the\n // percents align vertically down the table (AC-5). Donut/pie emit exactly one\n // percent row — keep their grouped, right-aligned percent+value cell\n // unchanged (svbarker review, PR #2267).\n const usePercentColumn =\n data.filter((d) => d.percent != null && !isNaN(d.percent)).length > 1;\n\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 <ChartLabelWithIcon icon={d.icon}>\n <Text aria-label={`${d.name}: `}>{d.name}</Text>\n </ChartLabelWithIcon>\n </ChartLegendLabel>\n ),\n };\n\n // Bind locally so TS narrows `percent` to `number` inside the guarded JSX\n // (a separate boolean const would not narrow the property access, forcing\n // an `as number` cast). `!isNaN` still filters Highcharts' NaN% (total = 0).\n const percent = d.percent;\n\n // Percent-stacked multi-series: split percent into its own right-aligned\n // column so the percents line up down the table, with the value in the last\n // column. A row without a valid percent leaves the percent cell empty so\n // its value stays column-aligned. A fixed 12px (space token 350) gap\n // between the percent and value columns comes from the percent cell's\n // right padding; the value cell's left padding is zeroed so the gap is\n // exactly 12px, matching the grouped donut/pie layout below.\n //\n // The shared v1 Table is `width: 100%` with the default `table-layout:\n // auto`, and the tooltip forces a min width wider than the content, so the\n // browser spreads that surplus across every column — including the value\n // column, whose box then grows past its text and pushes the visible\n // percent→value gap beyond 12px. An explicit cell `width` is only a\n // *preferred* width in auto layout, so making the name column greedy\n // (`width: 100%`) doesn't stop the surplus from still leaking into the\n // percent/value columns. Invert the constraint instead: pin the percent and\n // value cells to `width: 1%` (a width below their min-content) plus\n // `white-space: nowrap`, which clamps each to exactly its text width and\n // routes 100% of the surplus to the only unconstrained column — the name\n // column. The last column then collapses to its content and the 12px\n // padding above is the exact, stable percent→value gap regardless of\n // tooltip width (izzywyatt review, PR #2884).\n if (usePercentColumn) {\n return {\n cells: [\n nameCell,\n {\n content:\n percent != null && !isNaN(percent) ? (\n <Text>{Math.round(percent)}%</Text>\n ) : null,\n align: \"right\" as const,\n pr: 350,\n style: { width: \"1%\" as const, whiteSpace: \"nowrap\" as const },\n },\n {\n content: <Text>{formattedValue}</Text>,\n align: \"right\" as const,\n pl: 0,\n style: { width: \"1%\" as const, whiteSpace: \"nowrap\" as const },\n },\n ],\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 percent != null && !isNaN(percent) ? (\n <Box display=\"inline-flex\" alignItems=\"center\" gap={350}>\n <Text>{Math.round(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 // In column mode the Total has no percent, so span the label\n // across the name + percent columns; the value then lands in\n // the value column (col 3) without an empty <td>.\n colSpan: usePercentColumn ? 2 : 1,\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 // Only collapse to content width in percent-column mode, matching\n // the per-series value cells above (which are gated the same way).\n // In non-percent multi-series mode the Total stays a plain\n // right-aligned cell so regular stacking is untouched (ticket\n // scope) and the Total matches the series value cells stacked\n // above it.\n ...(usePercentColumn\n ? {\n pl: 0,\n style: {\n width: \"1%\" as const,\n whiteSpace: \"nowrap\" as const,\n },\n }\n : {}),\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","import { memo } from \"react\";\nimport { Icon, type TypeIconName } from \"@sproutsocial/seeds-react-icon\";\n\nexport type TypeChartLabelWithIconProps = Readonly<{\n children: React.ReactNode;\n icon?: TypeIconName;\n}>;\n\n// Shared icon+label layout used by the v2 `SeriesLegend` and `DefaultChartTooltip`.\nexport const ChartLabelWithIcon = memo<TypeChartLabelWithIconProps>(\n function ChartLabelWithIcon({ children, icon }: TypeChartLabelWithIconProps) {\n return icon ? (\n <div className=\"seeds-chart-label-with-icon\">\n <Icon name={icon} fixedWidth aria-hidden />\n {children}\n </div>\n ) : (\n children\n );\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 { type TypeIconName } from \"@sproutsocial/seeds-react-icon\";\nimport { ChartLegend } from \"../../components/ChartLegend\";\nimport { ChartLabelWithIcon } from \"./ChartLabelWithIcon\";\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 <ChartLabelWithIcon icon={item.icon}>{item.name}</ChartLabelWithIcon>\n ),\n color: item.color,\n }));\n return <ChartLegend legendLabels={labels} />;\n});\n","import type { SeedsChartOptions } from \"./chartBase\";\n\n/**\n * Narrower than `isChartStacked`: true only when a plotOptions slot carries\n * `stacking: \"percent\"` (not \"normal\", grouped, or unstacked). This is the\n * signal for rendering a per-series percent column in the tooltip — Highcharts\n * populates each point's `.percentage` only under percent stacking. Kept\n * key-agnostic (bar/column/area/areaspline) to match `isChartStacked`.\n */\nexport function isChartPercentStacked(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 (slot as { stacking?: unknown }).stacking === \"percent\"\n );\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"]}
@@ -1,11 +1,11 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
2
 
3
- var _chunkNU2RCATKjs = require('./chunk-NU2RCATK.js');
3
+ var _chunk5TWNMPQUjs = require('./chunk-5TWNMPQU.js');
4
4
 
5
5
  // src/charts/shared/axisOptions.ts
6
6
  function makeDatetimeAxisLabelFormatter(timezone, timeFormat) {
7
7
  return function() {
8
- const { primary, secondary } = _chunkNU2RCATKjs.datetimeFormatter.call(void 0, {
8
+ const { primary, secondary } = _chunk5TWNMPQUjs.datetimeFormatter.call(void 0, {
9
9
  value: this.value,
10
10
  tickPositions: _nullishCoalesce(this.axis.tickPositions, () => ( [])),
11
11
  timezone,
@@ -53,4 +53,4 @@ function resolveTooltipOptions(input) {
53
53
 
54
54
 
55
55
  exports.buildDimensionalAxis = buildDimensionalAxis; exports.resolveTooltipOptions = resolveTooltipOptions;
56
- //# sourceMappingURL=chunk-BRIU5SPJ.js.map
56
+ //# sourceMappingURL=chunk-CV45MD47.js.map