@eshal-bot/chat-widget 0.2.26 → 0.2.28

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.
@@ -87838,11 +87838,81 @@
87838
87838
  return null;
87839
87839
  }
87840
87840
 
87841
+ // ── Figures side by side ───────────────────────────────────────────────────
87842
+ // A chart that compares figures (pre vs post GRP, the 1+/3+/5+ reach curves)
87843
+ // arrives with `props.series`, [{ key, dataKey, name }] built by A2uiBlock's
87844
+ // chartSeries() from the spec's `ys` and `names`: one bar or one line per
87845
+ // figure, named in a legend, its colour fixed by its place in the order. The
87846
+ // colours: the brand blue (docs/design-system), then amber, pink and cyan from
87847
+ // the frontend chart palette's families, an order checked for colour-blind
87848
+ // separation and 3:1 contrast on the widget's light and dark surfaces (on dark,
87849
+ // blue-500 stands in for the brand blue-700, which falls under 3:1 there).
87850
+ // Mirrors SERIES_PALETTE in the frontend's chartShared.tsx.
87851
+ const SERIES_COLORS = ['#1d4ed8', '#d97706', '#db2777', '#0891b2'];
87852
+ const SERIES_COLORS_DARK = ['#3b82f6', '#d97706', '#db2777', '#0891b2'];
87853
+ function seriesColor(i, isDark = false) {
87854
+ const colors = isDark ? SERIES_COLORS_DARK : SERIES_COLORS;
87855
+ return colors[i % colors.length];
87856
+ }
87857
+
87858
+ // The figures drawn side by side, or [] for a one-figure chart.
87859
+ function sideBySide(props) {
87860
+ return Array.isArray(props.series) && props.series.length > 1 ? props.series : [];
87861
+ }
87862
+
87863
+ // Each figure's money formatting (moneyFormatters by its own key, so spend
87864
+ // reads as money beside GRP), and the one axis's: money only when every
87865
+ // figure on it is.
87866
+ function seriesMoney(series, currency) {
87867
+ const each = series.map(s => moneyFormatters(currency, s.key));
87868
+ return {
87869
+ each,
87870
+ axis: each.length > 0 && each.every(Boolean) ? each[0] : null
87871
+ };
87872
+ }
87873
+
87874
+ // The tooltip value of a figure side by side, formatted as that figure. Its
87875
+ // line names the figure, so it is written in ink (seriesTooltipItem): amber
87876
+ // text on white falls under 4.5:1.
87877
+ function seriesTooltip(series, each, unit) {
87878
+ return (v, name, item) => {
87879
+ const money = each[series.findIndex(s => s.dataKey === (item && item.dataKey))];
87880
+ if (money) return money.full(v);
87881
+ return unit ? `${Number(v).toLocaleString()} ${unit}` : Number(v).toLocaleString();
87882
+ };
87883
+ }
87884
+ function seriesTooltipItem(series, inkColor) {
87885
+ return series.length > 0 ? {
87886
+ color: inkColor
87887
+ } : undefined;
87888
+ }
87889
+
87890
+ // The legend of figures side by side: above the plot, clear of slanted
87891
+ // category labels; its words in ink, the swatch carrying the colour.
87892
+ function seriesLegend(inkColor) {
87893
+ return /*#__PURE__*/jsxRuntimeExports.jsx(Legend, {
87894
+ verticalAlign: "top",
87895
+ iconSize: 10,
87896
+ wrapperStyle: {
87897
+ fontSize: 11,
87898
+ paddingBottom: 6
87899
+ },
87900
+ formatter: value => /*#__PURE__*/jsxRuntimeExports.jsx("span", {
87901
+ style: {
87902
+ color: inkColor
87903
+ },
87904
+ children: value
87905
+ })
87906
+ });
87907
+ }
87908
+
87841
87909
  /**
87842
87910
  * Recharts bar chart - mirrors agent-sdk's eshalRenderers.BarChart.
87843
87911
  *
87844
87912
  * Expects `props.data` to be an array of `{ label, value }` objects
87845
- * (already normalized by A2uiBlock's numericData helper).
87913
+ * (already normalized by A2uiBlock's numericData helper), or, for figures
87914
+ * side by side, `{ label, s0, s1, ... }` points with `props.series`: grouped
87915
+ * bars, one per figure, named in a legend.
87846
87916
  */
87847
87917
  function BarChartRenderer({
87848
87918
  props,
@@ -87855,6 +87925,9 @@
87855
87925
  // Human series name for the tooltip ("Average Salary"), so it no longer
87856
87926
  // reads the generic dataKey "value".
87857
87927
  const seriesName = props.valueLabel || undefined;
87928
+ const series = sideBySide(props);
87929
+ const figures = seriesMoney(series, props.currency);
87930
+ const axisMoney = series.length > 0 ? figures.axis : money;
87858
87931
  const borderColor = isDark ? '#374151' : '#e2e8f0';
87859
87932
  const bgColor = isDark ? '#1f2937' : '#ffffff';
87860
87933
  const labelColor = isDark ? '#9ca3af' : '#64748b';
@@ -87964,9 +88037,10 @@
87964
88037
  fontSize: 11,
87965
88038
  fill: labelColor
87966
88039
  } : undefined,
87967
- tickFormatter: v => money ? money.compact(v) : props.unit ? `${formatCompactNumber(v)} ${props.unit}` : formatCompactNumber(v)
88040
+ tickFormatter: v => axisMoney ? axisMoney.compact(v) : props.unit ? `${formatCompactNumber(v)} ${props.unit}` : formatCompactNumber(v)
87968
88041
  }), /*#__PURE__*/jsxRuntimeExports.jsx(Tooltip, {
87969
- formatter: v => money ? money.full(v) : props.unit ? `${Number(v).toLocaleString()} ${props.unit}` : Number(v).toLocaleString(),
88042
+ formatter: series.length > 0 ? seriesTooltip(series, figures.each, props.unit) : v => money ? money.full(v) : props.unit ? `${Number(v).toLocaleString()} ${props.unit}` : Number(v).toLocaleString(),
88043
+ itemStyle: seriesTooltipItem(series, titleColor),
87970
88044
  contentStyle: {
87971
88045
  background: tooltipBg,
87972
88046
  border: `1px solid ${borderColor}`,
@@ -87974,7 +88048,12 @@
87974
88048
  fontSize: 12,
87975
88049
  color: titleColor
87976
88050
  }
87977
- }), /*#__PURE__*/jsxRuntimeExports.jsx(Bar, {
88051
+ }), series.length > 0 && seriesLegend(labelColor), series.length > 0 ? series.map((s, i) => /*#__PURE__*/jsxRuntimeExports.jsx(Bar, {
88052
+ dataKey: s.dataKey,
88053
+ name: s.name,
88054
+ fill: seriesColor(i, isDark),
88055
+ radius: [2, 2, 0, 0]
88056
+ }, s.dataKey)) : /*#__PURE__*/jsxRuntimeExports.jsx(Bar, {
87978
88057
  dataKey: "value",
87979
88058
  name: seriesName,
87980
88059
  fill: "#3b82f6",
@@ -88243,7 +88322,9 @@
88243
88322
 
88244
88323
  /**
88245
88324
  * Recharts line / area chart - replaces hand-rolled LineSvg.
88246
- * `props.data` must be an array of `{ label, value }` objects.
88325
+ * `props.data` must be an array of `{ label, value }` objects, or, for
88326
+ * figures side by side, `{ label, s0, s1, ... }` points with `props.series`:
88327
+ * one line each, named in a legend.
88247
88328
  * Set `area={true}` to fill under the line.
88248
88329
  */
88249
88330
  function LineChartRenderer({
@@ -88254,6 +88335,12 @@
88254
88335
  const data = Array.isArray(props.data) ? props.data : [];
88255
88336
  const money = moneyFormatters(props.currency, props.yKey);
88256
88337
  const seriesName = props.valueLabel || undefined;
88338
+ const series = sideBySide(props);
88339
+ const figures = seriesMoney(series, props.currency);
88340
+ const axisMoney = series.length > 0 ? figures.axis : money;
88341
+ // Several lines over a long run of periods (26 weeks of reach): every few
88342
+ // ticks and no dots, so the lines stay legible at the widget's width.
88343
+ const crowded = series.length > 0 && data.length > 12;
88257
88344
  const borderColor = isDark ? '#374151' : '#e2e8f0';
88258
88345
  const bgColor = isDark ? '#1f2937' : '#ffffff';
88259
88346
  const labelColor = isDark ? '#9ca3af' : '#64748b';
@@ -88333,7 +88420,7 @@
88333
88420
  },
88334
88421
  angle: 0,
88335
88422
  textAnchor: "middle",
88336
- interval: 0,
88423
+ interval: crowded ? 'preserveStartEnd' : 0,
88337
88424
  label: props.xAxisLabel ? {
88338
88425
  value: props.xAxisLabel,
88339
88426
  position: 'insideBottom',
@@ -88347,9 +88434,10 @@
88347
88434
  fontSize: 11,
88348
88435
  fill: labelColor
88349
88436
  },
88350
- tickFormatter: v => money ? money.compact(v) : props.unit ? `${formatCompactNumber(v)} ${props.unit}` : formatCompactNumber(v)
88437
+ tickFormatter: v => axisMoney ? axisMoney.compact(v) : props.unit ? `${formatCompactNumber(v)} ${props.unit}` : formatCompactNumber(v)
88351
88438
  }), /*#__PURE__*/jsxRuntimeExports.jsx(Tooltip, {
88352
- formatter: v => money ? money.full(v) : props.unit ? `${Number(v).toLocaleString()} ${props.unit}` : Number(v).toLocaleString(),
88439
+ formatter: series.length > 0 ? seriesTooltip(series, figures.each, props.unit) : v => money ? money.full(v) : props.unit ? `${Number(v).toLocaleString()} ${props.unit}` : Number(v).toLocaleString(),
88440
+ itemStyle: seriesTooltipItem(series, titleColor),
88353
88441
  contentStyle: {
88354
88442
  background: tooltipBg,
88355
88443
  border: `1px solid ${borderColor}`,
@@ -88357,7 +88445,33 @@
88357
88445
  fontSize: 12,
88358
88446
  color: titleColor
88359
88447
  }
88360
- }), area ? /*#__PURE__*/jsxRuntimeExports.jsx(Area, {
88448
+ }), series.length > 0 && seriesLegend(labelColor), series.length > 0 ? series.map((s, i) => {
88449
+ const color = seriesColor(i, isDark);
88450
+ const dot = crowded ? false : {
88451
+ r: 3,
88452
+ fill: color
88453
+ };
88454
+ return area ? /*#__PURE__*/jsxRuntimeExports.jsx(Area, {
88455
+ type: "monotone",
88456
+ dataKey: s.dataKey,
88457
+ name: s.name,
88458
+ stroke: color,
88459
+ fill: color,
88460
+ fillOpacity: 0.15,
88461
+ strokeWidth: 2,
88462
+ dot: dot
88463
+ }, s.dataKey) : /*#__PURE__*/jsxRuntimeExports.jsx(Line, {
88464
+ type: "monotone",
88465
+ dataKey: s.dataKey,
88466
+ name: s.name,
88467
+ stroke: color,
88468
+ strokeWidth: 2,
88469
+ dot: dot,
88470
+ activeDot: {
88471
+ r: 5
88472
+ }
88473
+ }, s.dataKey);
88474
+ }) : area ? /*#__PURE__*/jsxRuntimeExports.jsx(Area, {
88361
88475
  type: "monotone",
88362
88476
  dataKey: "value",
88363
88477
  name: seriesName,
@@ -88739,6 +88853,40 @@
88739
88853
  }).filter(d => Number.isFinite(d.value));
88740
88854
  }
88741
88855
 
88856
+ // The figures a chart compares side by side: the spec's `ys` (the runtime's
88857
+ // agents/data_charts.py writes them for pre vs post GRP, the 1+/3+/5+ reach
88858
+ // curves), each named by `names` or else its prettified key. Two or more that
88859
+ // the rows carry, else none, and a one-figure spec renders as before. Each
88860
+ // figure is read under its own data key (s0, s1, ...), so a figure whose name
88861
+ // matches the category's key ("label") cannot overwrite it.
88862
+ function chartSeries(spec) {
88863
+ const ys = Array.isArray(spec.ys) ? spec.ys.filter(k => typeof k === 'string' && k) : [];
88864
+ const sample = Array.isArray(spec.data) ? spec.data.find(row => row && typeof row === 'object') : null;
88865
+ const keys = sample ? [...new Set(ys)].filter(k => Object.prototype.hasOwnProperty.call(sample, k)) : [];
88866
+ if (keys.length < 2) return [];
88867
+ const names = spec.names && typeof spec.names === 'object' ? spec.names : {};
88868
+ return keys.map((key, i) => ({
88869
+ key,
88870
+ dataKey: `s${i}`,
88871
+ name: typeof names[key] === 'string' && names[key].trim() ? names[key].trim() : prettifyLabel(key)
88872
+ }));
88873
+ }
88874
+
88875
+ // numericData() for figures side by side: the label and each figure under its
88876
+ // series' data key, null where a row has none; a row with none of them goes.
88877
+ function seriesData(data, xKey, series) {
88878
+ return (data || []).filter(row => row && typeof row === 'object').map(row => {
88879
+ const point = {
88880
+ label: formatAxisTick(row[xKey], xKey)
88881
+ };
88882
+ for (const s of series) {
88883
+ const value = row[s.key] == null ? NaN : Number(row[s.key]);
88884
+ point[s.dataKey] = Number.isFinite(value) ? value : null;
88885
+ }
88886
+ return point;
88887
+ }).filter(point => series.some(s => point[s.dataKey] !== null));
88888
+ }
88889
+
88742
88890
  // ── Chart (SVG: line / bar / pie / donut + everything-else fallback) ─────
88743
88891
 
88744
88892
  function Chart({
@@ -88746,12 +88894,16 @@
88746
88894
  type,
88747
88895
  isDark
88748
88896
  }) {
88897
+ const series = chartSeries(spec);
88749
88898
  const {
88750
88899
  x,
88751
88900
  y
88752
- } = inferKeys(spec.data, spec.x, spec.y);
88753
- const rows = numericData(spec.data, x, y);
88901
+ } = inferKeys(spec.data, spec.x, series.length > 0 ? series[0].key : spec.y);
88754
88902
  const t = String(type || '').trim().toLowerCase();
88903
+ // Figures side by side draw as grouped bars or one line each; a pie holds
88904
+ // one figure, so a spec of several typed pie draws its first.
88905
+ const sideBySide = series.length > 0 && t !== 'pie' && t !== 'donut';
88906
+ const rows = sideBySide ? seriesData(spec.data, x, series) : numericData(spec.data, x, y);
88755
88907
  const chartProps = {
88756
88908
  title: spec.title,
88757
88909
  data: rows,
@@ -88766,7 +88918,9 @@
88766
88918
  valueLabel: prettifyLabel(y),
88767
88919
  // The currency the chart states ("USD", "INR", "₹"): the only source of
88768
88920
  // a currency symbol (widgetCatalog moneyFormatters).
88769
- currency: spec.currency
88921
+ currency: spec.currency,
88922
+ // Figures side by side (chartSeries): one bar or line each, in a legend.
88923
+ series: sideBySide ? series : undefined
88770
88924
  };
88771
88925
  if (t === 'pie') return /*#__PURE__*/jsxRuntimeExports.jsx(PieChartRenderer, {
88772
88926
  props: chartProps,