@sensiblestats/widget-react 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Render the greeting dashboard carried on `turn_complete`: tile grid, CSS, and tap handling that turns a tile into a follow-up chat message. Older servers that never send `dashboard` render nothing new. `widget-embed` also now generates and persists a first-party anonymous visitor id (localStorage, falls back to in-memory) so greeting dashboards can be tiered for anonymous visitors.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @sensiblestats/widget-sdk@0.12.0
13
+
14
+ ## 0.13.1
15
+
16
+ ### Patch Changes
17
+
18
+ - Odds movement chart: drop the direction clause from the chart's `aria-label` when the price is flat. It used to read "…currently 1.44, ." — a screen reader announced the dangling punctuation on exactly the no-movement case.
19
+
3
20
  ## 0.13.0
4
21
 
5
22
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/StatsWidget.tsx
39
39
  var import_react10 = require("react");
40
- var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
40
+ var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
41
41
 
42
42
  // src/i18n.ts
43
43
  var STRINGS = {
@@ -76,7 +76,8 @@ var STRINGS = {
76
76
  chartTimeLabel: (ms) => new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
77
77
  chartAria: (opening, current, direction, hours) => {
78
78
  const span = hours % 24 === 0 ? `${hours / 24} day${hours / 24 === 1 ? "" : "s"}` : `${hours} hour${hours === 1 ? "" : "s"}`;
79
- return `Price movement over the last ${span}: opened at ${opening}, currently ${current}, ${direction}.`;
79
+ const drift = direction ? `, ${direction}` : "";
80
+ return `Price movement over the last ${span}: opened at ${opening}, currently ${current}${drift}.`;
80
81
  },
81
82
  inSlip: "In slip",
82
83
  slipAdded: (n) => n === void 0 ? "Added to slip" : `Added to slip (${n} selection${n === 1 ? "" : "s"})`,
@@ -122,7 +123,8 @@ var STRINGS = {
122
123
  chartTimeLabel: (ms) => new Intl.DateTimeFormat("tr-TR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)),
123
124
  chartAria: (opening, current, direction, hours) => {
124
125
  const span = hours % 24 === 0 ? `${hours / 24} g\xFCn` : `${hours} saat`;
125
- return `Son ${span} i\xE7indeki oran hareketi: ${opening} ile a\xE7\u0131ld\u0131, \u015Fu anda ${current}, ${direction}.`;
126
+ const drift = direction ? `, ${direction}` : "";
127
+ return `Son ${span} i\xE7indeki oran hareketi: ${opening} ile a\xE7\u0131ld\u0131, \u015Fu anda ${current}${drift}.`;
126
128
  },
127
129
  inSlip: "Kuponda",
128
130
  slipAdded: (n) => n === void 0 ? "Kupona eklendi" : `Kupona eklendi (${n} se\xE7im)`,
@@ -203,6 +205,27 @@ function toUserContext(cfg) {
203
205
  return cfg.userContext && Object.keys(cfg.userContext).length > 0 ? cfg.userContext : void 0;
204
206
  }
205
207
 
208
+ // src/dashboardTap.ts
209
+ var import_widget_sdk = require("@sensiblestats/widget-sdk");
210
+ function isSignalEntityType(value) {
211
+ return Object.prototype.hasOwnProperty.call(import_widget_sdk.ENTITY_TYPE_CODES, value);
212
+ }
213
+ function createDashboardTapSignal(sendSignal) {
214
+ return (tile, index, layout) => {
215
+ if (!isSignalEntityType(tile.signal.entityType)) return;
216
+ try {
217
+ void sendSignal({
218
+ signalType: "MarketView",
219
+ entityType: tile.signal.entityType,
220
+ entityId: tile.signal.entityId,
221
+ dashboardLayout: layout,
222
+ tileIndex: index
223
+ }).catch(() => void 0);
224
+ } catch {
225
+ }
226
+ };
227
+ }
228
+
206
229
  // src/theme.ts
207
230
  function themeAttrs(ui) {
208
231
  const style = {
@@ -216,10 +239,10 @@ function themeAttrs(ui) {
216
239
 
217
240
  // src/useChatStream.ts
218
241
  var import_react = require("react");
219
- var import_widget_sdk2 = require("@sensiblestats/widget-sdk");
242
+ var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
220
243
 
221
244
  // src/reducer.ts
222
- var import_widget_sdk = require("@sensiblestats/widget-sdk");
245
+ var import_widget_sdk2 = require("@sensiblestats/widget-sdk");
223
246
 
224
247
  // src/chart.ts
225
248
  var CHART_VIEW_W = 320;
@@ -505,7 +528,11 @@ function applyEvent(state, event) {
505
528
  actionsLoading: false,
506
529
  popupText,
507
530
  conversationId: event.data.conversationId ?? state.conversationId,
508
- messages: mapActive(state, (t) => ({ ...t, done: true }))
531
+ messages: mapActive(state, (t) => ({
532
+ ...t,
533
+ done: true,
534
+ greetingDashboard: event.data.dashboard ?? void 0
535
+ }))
509
536
  };
510
537
  }
511
538
  case "error": {
@@ -557,7 +584,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
557
584
  try {
558
585
  for await (const event of handle) dispatch({ kind: "EVENT", event });
559
586
  } catch (err) {
560
- if (err instanceof import_widget_sdk2.WidgetSdkError) onError(err);
587
+ if (err instanceof import_widget_sdk3.WidgetSdkError) onError(err);
561
588
  else if (err?.name === "AbortError") {
562
589
  } else throw err;
563
590
  } finally {
@@ -628,6 +655,89 @@ function SendIcon() {
628
655
  function CloseIcon() {
629
656
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", d: "M18 6L6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2" }) });
630
657
  }
658
+ function FootballIcon() {
659
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
660
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "9", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
661
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M12 7l3.5 2.5-1.3 4.1H9.8L8.5 9.5z" })
662
+ ] });
663
+ }
664
+ function GoalNetIcon() {
665
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "5", width: "18", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1", d: "M3 9h18M3 13h18M8 5v12M16 5v12" })
668
+ ] });
669
+ }
670
+ function BttsIcon() {
671
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "12", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "12", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" })
674
+ ] });
675
+ }
676
+ function CardIcon() {
677
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "3", width: "12", height: "18", rx: "1.5", fill: "currentColor" }) });
678
+ }
679
+ function CornerFlagIcon() {
680
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", d: "M6 21V4" }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M6 4l11 3.5L6 11z" })
683
+ ] });
684
+ }
685
+ function PlayerIcon() {
686
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "7", r: "3.2", fill: "currentColor" }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M5 20c0-4 3-6.5 7-6.5s7 2.5 7 6.5z" })
689
+ ] });
690
+ }
691
+ function TargetIcon() {
692
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "8.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.4", fill: "currentColor" })
696
+ ] });
697
+ }
698
+ function TrophyIcon() {
699
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M7 3h10v4a5 5 0 0 1-5 5 5 5 0 0 1-5-5zM9 15h6v3H9zM7 19h10v2H7z" }),
701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.4", d: "M7 5H4v2a3 3 0 0 0 3 3M17 5h3v2a3 3 0 0 1-3 3" })
702
+ ] });
703
+ }
704
+ function ChartLineIcon() {
705
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", d: "M4 17l5-6 4 3 7-9" }) });
706
+ }
707
+ function ChartBarIcon() {
708
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M5 12h3v8H5zM10.5 7h3v13h-3zM16 15h3v5h-3z" }) });
709
+ }
710
+ function ChatIcon() {
711
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", d: "M4 5h16v11H9l-4 4z" }) });
712
+ }
713
+ function FireIcon() {
714
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M12 2c1 3-2 4-2 7a3 3 0 0 0 6 0c1 1 2 2.5 2 4.5A6 6 0 0 1 6 14c0-4 3-5 3-9 1 1 1.5 2 1.5 3C11.5 6 11 4 12 2z" }) });
715
+ }
716
+ function SparkleIcon() {
717
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M12 2l1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6z" }) });
718
+ }
719
+ function ChevronIcon() {
720
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
721
+ }
722
+ var DASHBOARD_ICONS = {
723
+ football: FootballIcon,
724
+ "goal-net": GoalNetIcon,
725
+ btts: BttsIcon,
726
+ card: CardIcon,
727
+ "corner-flag": CornerFlagIcon,
728
+ player: PlayerIcon,
729
+ target: TargetIcon,
730
+ trophy: TrophyIcon,
731
+ "chart-line": ChartLineIcon,
732
+ "chart-bar": ChartBarIcon,
733
+ chat: ChatIcon,
734
+ fire: FireIcon,
735
+ sparkle: SparkleIcon
736
+ };
737
+ function DashboardIcon({ name }) {
738
+ const Glyph = DASHBOARD_ICONS[name] ?? TargetIcon;
739
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, {});
740
+ }
631
741
 
632
742
  // src/components/GreetingBubble.tsx
633
743
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -786,7 +896,7 @@ var import_react5 = require("react");
786
896
 
787
897
  // src/components/AddToSlipButton.tsx
788
898
  var import_react4 = require("react");
789
- var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
899
+ var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
790
900
 
791
901
  // src/slip.ts
792
902
  var ADD_TO_SLIP_TIMEOUT_MS = 1e4;
@@ -849,7 +959,7 @@ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
849
959
  setPending(true);
850
960
  try {
851
961
  const raw = await withTimeout(Promise.resolve(handler(selection)));
852
- if (raw != null && !(0, import_widget_sdk3.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
962
+ if (raw != null && !(0, import_widget_sdk4.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
853
963
  const result = raw ?? void 0;
854
964
  setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
855
965
  setSettled(isTerminal(result));
@@ -1027,7 +1137,9 @@ function directionWord(vm, ui) {
1027
1137
  return "";
1028
1138
  }
1029
1139
  function OddsMovementCard({ vm, ui }) {
1030
- const aria = ui?.chartAria ? ui.chartAria(vm.opening, vm.current, directionWord(vm, ui), vm.windowHours) : `Price movement over the last ${vm.windowHours} hours: opened at ${vm.opening}, currently ${vm.current}, ${directionWord(vm, ui)}.`;
1140
+ const word = directionWord(vm, ui);
1141
+ const drift = word ? `, ${word}` : "";
1142
+ const aria = ui?.chartAria ? ui.chartAria(vm.opening, vm.current, word, vm.windowHours) : `Price movement over the last ${vm.windowHours} hours: opened at ${vm.opening}, currently ${vm.current}${drift}.`;
1031
1143
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-omcard", children: [
1032
1144
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-om-head", children: [
1033
1145
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-om-market", children: vm.market }),
@@ -1097,27 +1209,75 @@ function ActionButtons({ actions, loading, disabled, onAct }) {
1097
1209
  ] });
1098
1210
  }
1099
1211
 
1100
- // src/components/ChatMessage.tsx
1212
+ // src/components/dashboard/DashboardTile.tsx
1101
1213
  var import_jsx_runtime14 = require("react/jsx-runtime");
1102
- function ChatMessage({ message, disabled, onAct, ui, slip }) {
1214
+ var MAX_CRESTS = 3;
1215
+ function DashboardTile({ tile, onTap }) {
1216
+ const spanClass = tile.span === "full" ? "ss-w-dash-tile--full" : "ss-w-dash-tile--half";
1217
+ const crests = (tile.imageUrls ?? []).slice(0, MAX_CRESTS);
1218
+ const hasMeta = Boolean(tile.badgeText) || crests.length > 0;
1219
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", className: `ss-w-dash-tile ${spanClass}`, onClick: onTap, children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-icon", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DashboardIcon, { name: tile.icon }) }),
1221
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "ss-w-dash-tile-body", children: [
1222
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-title", children: tile.title }),
1223
+ tile.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-subtitle", children: tile.subtitle }) : null,
1224
+ hasMeta ? (
1225
+ // Badge + crests sit on their own row, below the title, instead of sharing the
1226
+ // icon/chevron row -- at the half-span tile's ~180px width a nowrap badge alone
1227
+ // (~70px) left the title column at ~22px and forced character-by-character wrapping.
1228
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "ss-w-dash-tile-meta", children: [
1229
+ tile.badgeText ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-badge", children: tile.badgeText }) : null,
1230
+ crests.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-crests", children: crests.map((url, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-crest", style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" }, `${url}-${i}`)) }) : null
1231
+ ] })
1232
+ ) : null
1233
+ ] }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-chevron", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronIcon, {}) })
1235
+ ] });
1236
+ }
1237
+
1238
+ // src/components/dashboard/GreetingDashboard.tsx
1239
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1240
+ var LAYOUT_CLASSES = {
1241
+ market: "ss-w-dash--market",
1242
+ goal: "ss-w-dash--goal",
1243
+ discovery: "ss-w-dash--discovery"
1244
+ };
1245
+ function layoutClass(layout) {
1246
+ return Object.prototype.hasOwnProperty.call(LAYOUT_CLASSES, layout) ? LAYOUT_CLASSES[layout] : LAYOUT_CLASSES.goal;
1247
+ }
1248
+ function GreetingDashboard({ dashboard, onTap }) {
1249
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `ss-w-dash ${layoutClass(dashboard.layout)}`, children: [
1250
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-dash-head", children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-title", children: dashboard.title }),
1252
+ dashboard.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-subtitle", children: dashboard.subtitle }) : null
1253
+ ] }),
1254
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DashboardTile, { tile: t, onTap: () => onTap(t, i) }, `${t.title}-${i}`)) }),
1255
+ dashboard.footerTip ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-foot", children: dashboard.footerTip }) : null
1256
+ ] });
1257
+ }
1258
+
1259
+ // src/components/ChatMessage.tsx
1260
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1261
+ function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1103
1262
  if (message.role === "user") {
1104
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1105
1264
  }
1106
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1107
- message.text ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Markdown, { text: message.text }) : null,
1108
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1109
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1110
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1111
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1112
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1265
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1266
+ message.text ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Markdown, { text: message.text }) : null,
1267
+ message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1268
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1113
1273
  ] });
1114
1274
  }
1115
1275
 
1116
1276
  // src/components/IntentChips.tsx
1117
- var import_jsx_runtime15 = require("react/jsx-runtime");
1277
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1118
1278
  function IntentChips({ chips, onPick }) {
1119
1279
  if (chips.length === 0) return null;
1120
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1280
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1121
1281
  }
1122
1282
 
1123
1283
  // src/scroll.ts
@@ -1127,8 +1287,8 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1127
1287
  }
1128
1288
 
1129
1289
  // src/components/MessageList.tsx
1130
- var import_jsx_runtime16 = require("react/jsx-runtime");
1131
- function MessageList({ controller, ui, slip }) {
1290
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1291
+ function MessageList({ controller, ui, slip, onDashboardTap }) {
1132
1292
  const { state, send } = controller;
1133
1293
  const listRef = (0, import_react7.useRef)(null);
1134
1294
  const endRef = (0, import_react7.useRef)(null);
@@ -1136,8 +1296,9 @@ function MessageList({ controller, ui, slip }) {
1136
1296
  (0, import_react7.useEffect)(() => {
1137
1297
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1138
1298
  }, [state.messages, state.status]);
1139
- const empty = state.messages.length === 0;
1140
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1299
+ const hasUserMessage = state.messages.some((m) => m.role === "user");
1300
+ const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1301
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1141
1302
  "div",
1142
1303
  {
1143
1304
  className: "ss-w-list",
@@ -1147,26 +1308,30 @@ function MessageList({ controller, ui, slip }) {
1147
1308
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1148
1309
  },
1149
1310
  children: [
1150
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1311
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1151
1312
  ChatMessage,
1152
1313
  {
1153
1314
  message: m,
1154
1315
  disabled: state.isStreaming,
1155
1316
  onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
1317
+ onTap: (tile, index) => {
1318
+ send(tile.actionValue, { displayText: tile.title });
1319
+ if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1320
+ },
1156
1321
  ui,
1157
1322
  slip
1158
1323
  },
1159
1324
  m.id
1160
1325
  )),
1161
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1326
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1162
1327
  } }) : null,
1163
- state.status ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1164
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1165
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1166
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1328
+ state.status ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1329
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1331
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1167
1332
  ] }) : null,
1168
- empty ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1169
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: endRef })
1333
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1334
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: endRef })
1170
1335
  ]
1171
1336
  }
1172
1337
  );
@@ -1174,7 +1339,7 @@ function MessageList({ controller, ui, slip }) {
1174
1339
 
1175
1340
  // src/components/ChatInput.tsx
1176
1341
  var import_react8 = require("react");
1177
- var import_jsx_runtime17 = require("react/jsx-runtime");
1342
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1178
1343
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1179
1344
  const [value, setValue] = (0, import_react8.useState)("");
1180
1345
  const submit = () => {
@@ -1189,14 +1354,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1189
1354
  submit();
1190
1355
  }
1191
1356
  };
1192
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-input", children: [
1193
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1194
- streaming ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SendIcon, {}) })
1357
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ss-w-input", children: [
1358
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1359
+ streaming ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SendIcon, {}) })
1195
1360
  ] });
1196
1361
  }
1197
1362
 
1198
1363
  // src/components/ChatPanel.tsx
1199
- var import_jsx_runtime18 = require("react/jsx-runtime");
1364
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1200
1365
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1201
1366
  function useIsModal() {
1202
1367
  const [modal, setModal] = (0, import_react9.useState)(false);
@@ -1210,7 +1375,7 @@ function useIsModal() {
1210
1375
  }, []);
1211
1376
  return modal;
1212
1377
  }
1213
- function ChatPanel({ controller, ui, slip }) {
1378
+ function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1214
1379
  const { state, close, stop, send } = controller;
1215
1380
  const isModal = useIsModal();
1216
1381
  (0, import_react9.useEffect)(() => {
@@ -1220,10 +1385,10 @@ function ChatPanel({ controller, ui, slip }) {
1220
1385
  window.addEventListener("keydown", onKey);
1221
1386
  return () => window.removeEventListener("keydown", onKey);
1222
1387
  }, [close]);
1223
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1224
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1225
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageList, { controller, ui, slip }),
1226
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1389
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1390
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1227
1392
  ] });
1228
1393
  }
1229
1394
 
@@ -1480,17 +1645,72 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1480
1645
  @media (prefers-reduced-motion: reduce) {
1481
1646
  .ss-w-abtn-shimmer { animation: none; }
1482
1647
  }
1648
+
1649
+ /* greeting dashboard */
1650
+ .ss-w-dash { display: flex; flex-direction: column; gap: 12px; padding: 4px 0; }
1651
+ .ss-w-dash-head { display: flex; flex-direction: column; }
1652
+ .ss-w-dash-title { font-size: 18px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1653
+ .ss-w-dash-subtitle { font-size: 13px; color: var(--ss-w-faint); margin-top: 4px; }
1654
+ .ss-w-dash-foot { font-size: 11px; color: var(--ss-w-faint); margin-top: 2px; }
1655
+
1656
+ .ss-w-dash-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
1657
+ .ss-w-dash-tile--half { grid-column: span 1; }
1658
+ .ss-w-dash-tile--full { grid-column: 1 / -1; }
1659
+
1660
+ .ss-w-dash-tile {
1661
+ display: flex; align-items: center; gap: 10px;
1662
+ padding: 12px; border-radius: 14px; border: 1px solid var(--ss-w-line);
1663
+ background: var(--ss-w-panel); text-align: left; cursor: pointer; width: 100%;
1664
+ color: inherit; font: inherit;
1665
+ min-width: 0; /* the panel is min(400px, 100vw - 40px): without this,
1666
+ a long Turkish title blows the column out */
1667
+ }
1668
+ .ss-w-dash-tile-icon { flex: none; display: grid; place-items: center; width: 20px; height: 20px; color: var(--ss-w-accent); font-size: 18px; }
1669
+ .ss-w-dash-tile-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; }
1670
+ .ss-w-dash-tile-title { min-width: 0; font-weight: 600; font-size: 14px; overflow-wrap: anywhere; }
1671
+ .ss-w-dash-tile-subtitle { min-width: 0; font-size: 11.5px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
1672
+ .ss-w-dash-tile-chevron { flex: none; margin-left: auto; width: 14px; height: 14px; color: var(--ss-w-faint); }
1673
+
1674
+ /* Badge + crests share their own row, below the title -- NOT the icon/chevron row. A
1675
+ nowrap badge (~70px) sitting beside icon+chevron in a ~180px half-span tile left the
1676
+ title column at ~22px wide, wrapping every title character-by-character. Here the row
1677
+ gets the body's full width, so a badge this size is comfortably inside it. */
1678
+ .ss-w-dash-tile-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
1679
+ .ss-w-dash-tile-badge { flex: none; font-size: 10.5px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 13%, var(--ss-w-panel)); border-radius: 999px; padding: 3px 9px; white-space: nowrap; }
1680
+
1681
+ .ss-w-dash-tile-crests { flex: none; display: flex; align-items: center; }
1682
+ .ss-w-dash-tile-crest {
1683
+ width: 18px; height: 18px; border-radius: 50%;
1684
+ background-size: cover; background-position: center; background-color: var(--ss-w-bubble);
1685
+ margin-left: -6px; border: 2px solid var(--ss-w-panel); /* overlapping stack, capped at 3 in DashboardTile */
1686
+ }
1687
+ .ss-w-dash-tile-crest:first-child { margin-left: 0; }
1688
+
1689
+ /* Market: default 2-column grid, tiles get the same accent rail as the other card
1690
+ families (.ss-w-binsight, .ss-w-mocard, .ss-w-omcard) so a market tile reads as
1691
+ part of that visual language rather than a plain unstyled button. */
1692
+ .ss-w-dash--market .ss-w-dash-tile { border-left: 3px solid var(--ss-w-accent); }
1693
+
1694
+ /* Goal: one column of pill rows. */
1695
+ .ss-w-dash--goal .ss-w-dash-grid { grid-template-columns: 1fr; }
1696
+ .ss-w-dash--goal .ss-w-dash-tile { border-radius: 999px; padding: 10px 14px; }
1697
+
1698
+ /* Discovery: tinted bento cards. */
1699
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(1) { background: #eef8f1; }
1700
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: #eef3fb; }
1701
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: #fdf6ec; }
1702
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: #f5eefb; }
1483
1703
  `;
1484
1704
 
1485
1705
  // src/StatsWidget.tsx
1486
- var import_jsx_runtime19 = require("react/jsx-runtime");
1706
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1487
1707
  function buildClient(props) {
1488
1708
  try {
1489
1709
  if ("client" in props && props.client) {
1490
1710
  return { client: props.client, conversation: props.client.conversation() };
1491
1711
  }
1492
1712
  const cfg = props.config;
1493
- const client = new import_widget_sdk4.StatsWidgetClient(toClientOptions(cfg));
1713
+ const client = new import_widget_sdk5.StatsWidgetClient(toClientOptions(cfg));
1494
1714
  return { client, conversation: client.conversation(toUserContext(cfg)) };
1495
1715
  } catch (err) {
1496
1716
  console.error("[StatsWidget] failed to initialize:", err instanceof Error ? err.message : err);
@@ -1525,6 +1745,10 @@ function StatsWidget(props) {
1525
1745
  }),
1526
1746
  [props.config?.onAddToSlip, controller.state.conversationId, built]
1527
1747
  );
1748
+ const onDashboardTap = (0, import_react10.useMemo)(
1749
+ () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
1750
+ [built]
1751
+ );
1528
1752
  (0, import_react10.useEffect)(() => {
1529
1753
  if (!built || !ui.injectStyles) return;
1530
1754
  const node = rootRef.current?.getRootNode();
@@ -1532,7 +1756,7 @@ function StatsWidget(props) {
1532
1756
  }, [built, ui.injectStyles]);
1533
1757
  if (!built) return null;
1534
1758
  const attrs = themeAttrs(ui);
1535
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1759
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1536
1760
  ChatFab,
1537
1761
  {
1538
1762
  label: ui.launcherLabel,