@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 +17 -0
- package/dist/index.cjs +271 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +319 -95
- package/dist/index.js.map +1 -1
- package/dist/styles.css +55 -0
- package/package.json +2 -2
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
242
|
+
var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
|
|
220
243
|
|
|
221
244
|
// src/reducer.ts
|
|
222
|
-
var
|
|
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) => ({
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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/
|
|
1212
|
+
// src/components/dashboard/DashboardTile.tsx
|
|
1101
1213
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1102
|
-
|
|
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,
|
|
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,
|
|
1107
|
-
message.text ? /* @__PURE__ */ (0,
|
|
1108
|
-
/* @__PURE__ */ (0,
|
|
1109
|
-
/* @__PURE__ */ (0,
|
|
1110
|
-
/* @__PURE__ */ (0,
|
|
1111
|
-
/* @__PURE__ */ (0,
|
|
1112
|
-
/* @__PURE__ */ (0,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
1140
|
-
|
|
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,
|
|
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,
|
|
1326
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
1162
1327
|
} }) : null,
|
|
1163
|
-
state.status ? /* @__PURE__ */ (0,
|
|
1164
|
-
state.error ? /* @__PURE__ */ (0,
|
|
1165
|
-
/* @__PURE__ */ (0,
|
|
1166
|
-
/* @__PURE__ */ (0,
|
|
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
|
-
|
|
1169
|
-
/* @__PURE__ */ (0,
|
|
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
|
|
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,
|
|
1193
|
-
/* @__PURE__ */ (0,
|
|
1194
|
-
streaming ? /* @__PURE__ */ (0,
|
|
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
|
|
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,
|
|
1224
|
-
/* @__PURE__ */ (0,
|
|
1225
|
-
/* @__PURE__ */ (0,
|
|
1226
|
-
/* @__PURE__ */ (0,
|
|
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
|
|
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
|
|
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,
|
|
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,
|