@sensiblestats/widget-react 0.2.1 → 0.4.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 +150 -66
- 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 +150 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +24 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Render `betting_insight` events as insight cards (market, selection + line, odds with an optional live badge, supporting stats, and the responsible-gambling disclaimer). **Bug fix:** the reducer previously had no case for `betting_insight` and no fallthrough, so any unmodelled event returned `undefined` and corrupted state — the next event then crashed the widget (`Cannot read properties of undefined (reading 'length')`). Unknown event types are now ignored safely and can never break the UI.
|
|
8
|
+
|
|
9
|
+
## 0.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Entity cards are no longer clickable — they render as static info cards
|
|
14
|
+
(planned to return later with proper question composition). Fix iOS Safari
|
|
15
|
+
zooming on input focus (textarea font-size ≥ 16px on touch). Fix the message
|
|
16
|
+
list yanking the reader to the bottom when late content (suggested actions)
|
|
17
|
+
arrives after they scrolled up — auto-scroll now follows only while pinned to
|
|
18
|
+
the bottom. Also ships the i18n/localized labels and logo refactor from master.
|
|
19
|
+
|
|
3
20
|
## 0.2.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -132,16 +132,19 @@ function str(v) {
|
|
|
132
132
|
function num(v) {
|
|
133
133
|
return typeof v === "number" && Number.isFinite(v) ? String(v) : void 0;
|
|
134
134
|
}
|
|
135
|
-
function
|
|
136
|
-
if (!Array.isArray(
|
|
135
|
+
function readStatCells(raw) {
|
|
136
|
+
if (!Array.isArray(raw)) return [];
|
|
137
137
|
const cells = [];
|
|
138
|
-
for (const entry of
|
|
138
|
+
for (const entry of raw) {
|
|
139
139
|
const label = str(entry.label);
|
|
140
140
|
const value = num(entry.value) ?? str(entry.value);
|
|
141
141
|
if (label !== void 0 && value !== void 0) cells.push({ label, value });
|
|
142
142
|
}
|
|
143
143
|
return cells;
|
|
144
144
|
}
|
|
145
|
+
function readStats(card) {
|
|
146
|
+
return readStatCells(card.stats);
|
|
147
|
+
}
|
|
145
148
|
function toCardVM(card) {
|
|
146
149
|
const entityType = str(card.entityType)?.toLowerCase();
|
|
147
150
|
const kind = entityType === "player" ? "player" : entityType === "team" ? "team" : "generic";
|
|
@@ -151,8 +154,7 @@ function toCardVM(card) {
|
|
|
151
154
|
name: card.canonicalName,
|
|
152
155
|
image: str(card.imagePath) ?? str(card.imageUrl) ?? str(card.image_url) ?? str(card.logoUrl) ?? str(card.logo),
|
|
153
156
|
sub: str(card.subtitle) ?? str(card.teamName) ?? str(card.leagueName) ?? str(card.position),
|
|
154
|
-
stats: readStats(card)
|
|
155
|
-
query: card.canonicalName
|
|
157
|
+
stats: readStats(card)
|
|
156
158
|
};
|
|
157
159
|
}
|
|
158
160
|
function toActionVM(action) {
|
|
@@ -168,6 +170,24 @@ function toChipVM(intent) {
|
|
|
168
170
|
function chipFromText(text) {
|
|
169
171
|
return { text };
|
|
170
172
|
}
|
|
173
|
+
function toInsightVM(insight) {
|
|
174
|
+
const market = str(insight.marketLabel) ?? str(insight.marketDeveloperName) ?? "";
|
|
175
|
+
const selection = [str(insight.selectionLabel), str(insight.line)].filter(Boolean).join(" ") || void 0;
|
|
176
|
+
const value = insight.odds?.value;
|
|
177
|
+
const odds = typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : void 0;
|
|
178
|
+
return {
|
|
179
|
+
id: str(insight.insightId) ?? `${str(insight.ruleId) ?? "insight"}:${num(insight.matchId) ?? ""}`,
|
|
180
|
+
market,
|
|
181
|
+
selection,
|
|
182
|
+
odds,
|
|
183
|
+
// The wire payload carries bookmakerId (not a name); render a name only if one is present.
|
|
184
|
+
bookmaker: str(insight.odds?.bookmakerName),
|
|
185
|
+
isLive: insight.odds?.isLive === true,
|
|
186
|
+
text: str(insight.text) ?? "",
|
|
187
|
+
stats: readStatCells(insight.stats),
|
|
188
|
+
disclaimer: str(insight.disclaimer)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
171
191
|
|
|
172
192
|
// src/reducer.ts
|
|
173
193
|
var POPUP_MAX = 160;
|
|
@@ -194,6 +214,8 @@ function applyEvent(state, event) {
|
|
|
194
214
|
return { ...state, messages: mapActive(state, (t) => ({ ...t, cards: [...t.cards, toCardVM(event.data.card)] })) };
|
|
195
215
|
case "action_button":
|
|
196
216
|
return { ...state, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, actions: [...t.actions, toActionVM(event.data.action)] })) };
|
|
217
|
+
case "betting_insight":
|
|
218
|
+
return { ...state, messages: mapActive(state, (t) => ({ ...t, insights: [...t.insights, toInsightVM(event.data.insight)] })) };
|
|
197
219
|
case "intent_chip": {
|
|
198
220
|
const chip = toChipVM(event.data.intent);
|
|
199
221
|
return chip ? { ...state, starters: [...state.starters, chip] } : state;
|
|
@@ -211,6 +233,7 @@ function applyEvent(state, event) {
|
|
|
211
233
|
return { ...state, error: suppress ? state.error : event.data, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
212
234
|
}
|
|
213
235
|
}
|
|
236
|
+
return state;
|
|
214
237
|
}
|
|
215
238
|
function reducer(state, action) {
|
|
216
239
|
switch (action.kind) {
|
|
@@ -220,11 +243,11 @@ function reducer(state, action) {
|
|
|
220
243
|
return { ...state, isOpen: false, isStreaming: false, status: null, actionsLoading: false };
|
|
221
244
|
case "USER_SENT": {
|
|
222
245
|
const user = { role: "user", id: action.userId, text: action.displayText ?? action.input.message };
|
|
223
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false };
|
|
246
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false };
|
|
224
247
|
return { ...state, messages: [...state.messages, user, assistant], isStreaming: true, status: null, actionsLoading: false, starters: [], error: null, lastUserInput: action.input, lastDisplayText: action.displayText ?? null };
|
|
225
248
|
}
|
|
226
249
|
case "GREETING_SENT": {
|
|
227
|
-
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], done: false, isGreeting: true };
|
|
250
|
+
const assistant = { role: "assistant", id: action.assistantId, text: "", cards: [], actions: [], insights: [], done: false, isGreeting: true };
|
|
228
251
|
return { ...state, messages: [...state.messages, assistant], isStreaming: true, status: null, error: null };
|
|
229
252
|
}
|
|
230
253
|
case "DISMISS_POPUP":
|
|
@@ -325,9 +348,6 @@ function SendIcon() {
|
|
|
325
348
|
function CloseIcon() {
|
|
326
349
|
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" }) });
|
|
327
350
|
}
|
|
328
|
-
function ChevronIcon() {
|
|
329
|
-
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", d: "M9 6l6 6-6 6" }) });
|
|
330
|
-
}
|
|
331
351
|
|
|
332
352
|
// src/components/GreetingBubble.tsx
|
|
333
353
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
@@ -386,13 +406,13 @@ var MAX_VISIBLE = 3;
|
|
|
386
406
|
function initials(name) {
|
|
387
407
|
return name.split(/\s+/).slice(0, 2).map((w) => w[0] ?? "").join("").toUpperCase();
|
|
388
408
|
}
|
|
389
|
-
function EntityCardList({ cards, disabled,
|
|
409
|
+
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
390
410
|
const [expanded, setExpanded] = (0, import_react2.useState)(false);
|
|
391
411
|
if (cards.length === 0) return null;
|
|
392
412
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
393
413
|
const overflow = cards.length - MAX_VISIBLE;
|
|
394
414
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-w-cards", children: [
|
|
395
|
-
visible.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("
|
|
415
|
+
visible.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-w-ecard", "data-kind": c.kind, children: [
|
|
396
416
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
397
417
|
"span",
|
|
398
418
|
{
|
|
@@ -409,82 +429,125 @@ function EntityCardList({ cards, disabled, onSelect, showLessLabel = "Show less"
|
|
|
409
429
|
c.stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-w-stat", children: [
|
|
410
430
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("b", { children: s.value }),
|
|
411
431
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: s.label })
|
|
412
|
-
] }, s.label))
|
|
413
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-w-chev", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronIcon, {}) })
|
|
432
|
+
] }, s.label))
|
|
414
433
|
] }, c.id)),
|
|
415
434
|
overflow > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "ss-w-cards-more", disabled, onClick: () => setExpanded((v) => !v), children: expanded ? showLessLabel : showMoreLabel(overflow) }) : null
|
|
416
435
|
] });
|
|
417
436
|
}
|
|
418
437
|
|
|
419
|
-
// src/components/
|
|
438
|
+
// src/components/BettingInsightList.tsx
|
|
420
439
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
440
|
+
function BettingInsightList({ insights }) {
|
|
441
|
+
if (!insights || insights.length === 0) return null;
|
|
442
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-binsight", children: [
|
|
443
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ss-w-bi-head", children: [
|
|
444
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ss-w-bi-mkt", children: [
|
|
445
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-market", children: it.market }),
|
|
446
|
+
it.selection ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-sel", children: it.selection }) : null
|
|
447
|
+
] }),
|
|
448
|
+
it.odds ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ss-w-bi-odds", children: [
|
|
449
|
+
it.isLive ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-live", children: "Live" }) : null,
|
|
450
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: it.odds }),
|
|
451
|
+
it.bookmaker ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ss-w-bi-book", children: it.bookmaker }) : null
|
|
452
|
+
] }) : null
|
|
453
|
+
] }),
|
|
454
|
+
it.text ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ss-w-bi-text", children: it.text }) : null,
|
|
455
|
+
it.stats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-bi-stats", children: it.stats.map((s) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "ss-w-bi-stat", children: [
|
|
456
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: s.value }),
|
|
457
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: s.label })
|
|
458
|
+
] }, `${s.label}:${s.value}`)) }) : null,
|
|
459
|
+
it.disclaimer ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ss-w-bi-disc", children: it.disclaimer }) : null
|
|
460
|
+
] }, it.id)) });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/components/ActionButtons.tsx
|
|
464
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
421
465
|
function ActionButtons({ actions, loading, disabled, onAct }) {
|
|
422
466
|
if (actions.length === 0 && !loading) return null;
|
|
423
|
-
return /* @__PURE__ */ (0,
|
|
424
|
-
actions.map((a, i) => /* @__PURE__ */ (0,
|
|
425
|
-
actions.length === 0 && loading ? /* @__PURE__ */ (0,
|
|
426
|
-
/* @__PURE__ */ (0,
|
|
427
|
-
/* @__PURE__ */ (0,
|
|
467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ss-w-actions", children: [
|
|
468
|
+
actions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "ss-w-abtn", disabled, onClick: () => onAct(a), children: a.label }, `${a.label}-${i}`)),
|
|
469
|
+
actions.length === 0 && loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
470
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ss-w-abtn-shimmer" }),
|
|
471
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ss-w-abtn-shimmer" })
|
|
428
472
|
] }) : null
|
|
429
473
|
] });
|
|
430
474
|
}
|
|
431
475
|
|
|
432
476
|
// src/components/ChatMessage.tsx
|
|
433
|
-
var
|
|
434
|
-
function ChatMessage({ message, disabled, onAct,
|
|
477
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
478
|
+
function ChatMessage({ message, disabled, onAct, ui }) {
|
|
435
479
|
if (message.role === "user") {
|
|
436
|
-
return /* @__PURE__ */ (0,
|
|
480
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
|
|
437
481
|
}
|
|
438
|
-
return /* @__PURE__ */ (0,
|
|
439
|
-
message.text ? /* @__PURE__ */ (0,
|
|
440
|
-
/* @__PURE__ */ (0,
|
|
441
|
-
/* @__PURE__ */ (0,
|
|
482
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
483
|
+
message.text ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Markdown, { text: message.text }) : null,
|
|
484
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(BettingInsightList, { insights: message.insights }),
|
|
486
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
442
487
|
] });
|
|
443
488
|
}
|
|
444
489
|
|
|
445
490
|
// src/components/IntentChips.tsx
|
|
446
|
-
var
|
|
491
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
447
492
|
function IntentChips({ chips, onPick }) {
|
|
448
493
|
if (chips.length === 0) return null;
|
|
449
|
-
return /* @__PURE__ */ (0,
|
|
494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/scroll.ts
|
|
498
|
+
var PIN_THRESHOLD = 48;
|
|
499
|
+
function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
500
|
+
return el.scrollHeight - el.scrollTop - el.clientHeight <= threshold;
|
|
450
501
|
}
|
|
451
502
|
|
|
452
503
|
// src/components/MessageList.tsx
|
|
453
|
-
var
|
|
504
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
454
505
|
function MessageList({ controller, ui }) {
|
|
455
506
|
const { state, send } = controller;
|
|
507
|
+
const listRef = (0, import_react3.useRef)(null);
|
|
456
508
|
const endRef = (0, import_react3.useRef)(null);
|
|
509
|
+
const pinnedRef = (0, import_react3.useRef)(true);
|
|
457
510
|
(0, import_react3.useEffect)(() => {
|
|
458
|
-
endRef.current?.scrollIntoView({ block: "end" });
|
|
511
|
+
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
459
512
|
}, [state.messages, state.status]);
|
|
460
513
|
const empty = state.messages.length === 0;
|
|
461
|
-
return /* @__PURE__ */ (0,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
ui
|
|
514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
515
|
+
"div",
|
|
516
|
+
{
|
|
517
|
+
className: "ss-w-list",
|
|
518
|
+
"aria-live": "polite",
|
|
519
|
+
ref: listRef,
|
|
520
|
+
onScroll: () => {
|
|
521
|
+
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
470
522
|
},
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
523
|
+
children: [
|
|
524
|
+
state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
525
|
+
ChatMessage,
|
|
526
|
+
{
|
|
527
|
+
message: m,
|
|
528
|
+
disabled: state.isStreaming,
|
|
529
|
+
onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
|
|
530
|
+
ui
|
|
531
|
+
},
|
|
532
|
+
m.id
|
|
533
|
+
)),
|
|
534
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
535
|
+
} }) : null,
|
|
536
|
+
state.status ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
|
|
537
|
+
state.error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
|
|
538
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
539
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
540
|
+
] }) : null,
|
|
541
|
+
empty ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
542
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref: endRef })
|
|
543
|
+
]
|
|
544
|
+
}
|
|
545
|
+
);
|
|
483
546
|
}
|
|
484
547
|
|
|
485
548
|
// src/components/ChatInput.tsx
|
|
486
549
|
var import_react4 = require("react");
|
|
487
|
-
var
|
|
550
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
488
551
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
489
552
|
const [value, setValue] = (0, import_react4.useState)("");
|
|
490
553
|
const submit = () => {
|
|
@@ -499,14 +562,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
499
562
|
submit();
|
|
500
563
|
}
|
|
501
564
|
};
|
|
502
|
-
return /* @__PURE__ */ (0,
|
|
503
|
-
/* @__PURE__ */ (0,
|
|
504
|
-
streaming ? /* @__PURE__ */ (0,
|
|
565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-input", children: [
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
567
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SendIcon, {}) })
|
|
505
568
|
] });
|
|
506
569
|
}
|
|
507
570
|
|
|
508
571
|
// src/components/ChatPanel.tsx
|
|
509
|
-
var
|
|
572
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
510
573
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
511
574
|
function useIsModal() {
|
|
512
575
|
const [modal, setModal] = (0, import_react5.useState)(false);
|
|
@@ -530,10 +593,10 @@ function ChatPanel({ controller, ui }) {
|
|
|
530
593
|
window.addEventListener("keydown", onKey);
|
|
531
594
|
return () => window.removeEventListener("keydown", onKey);
|
|
532
595
|
}, [close]);
|
|
533
|
-
return /* @__PURE__ */ (0,
|
|
534
|
-
/* @__PURE__ */ (0,
|
|
535
|
-
/* @__PURE__ */ (0,
|
|
536
|
-
/* @__PURE__ */ (0,
|
|
596
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
598
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageList, { controller, ui }),
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
537
600
|
] });
|
|
538
601
|
}
|
|
539
602
|
|
|
@@ -617,7 +680,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
617
680
|
|
|
618
681
|
/* entity cards */
|
|
619
682
|
.ss-w-cards { display: flex; flex-direction: column; gap: 7px; }
|
|
620
|
-
.ss-w-ecard { display: flex; align-items: center; gap: 11px; width: 100%; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 11px; text-align: left;
|
|
683
|
+
.ss-w-ecard { display: flex; align-items: center; gap: 11px; width: 100%; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 11px; text-align: left; }
|
|
621
684
|
.ss-w-thumb { width: 40px; height: 40px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700; color: #fff; background-color: var(--ss-w-nav); background-size: cover; background-position: center; }
|
|
622
685
|
.ss-w-thumb.ss-w-team { border-radius: 10px; }
|
|
623
686
|
.ss-w-ecard[data-kind="player"] .ss-w-thumb { background-color: var(--ss-w-accent); }
|
|
@@ -628,10 +691,26 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
628
691
|
.ss-w-stat { margin-left: auto; text-align: center; }
|
|
629
692
|
.ss-w-stat b { display: block; font-variant-numeric: tabular-nums; }
|
|
630
693
|
.ss-w-stat span { font-size: 9px; text-transform: uppercase; color: var(--ss-w-faint); }
|
|
631
|
-
.ss-w-chev { color: var(--ss-w-faint); font-size: 16px; display: grid; place-items: center; }
|
|
632
694
|
.ss-w-cards-more { align-self: flex-start; font-size: 11.5px; font-weight: 560; color: var(--ss-w-accent); background: none; border: none; cursor: pointer; padding: 3px 2px; }
|
|
633
695
|
.ss-w-cards-more:hover:not(:disabled) { text-decoration: underline; }
|
|
634
|
-
.ss-w-
|
|
696
|
+
.ss-w-cards-more:disabled { opacity: .5; cursor: default; }
|
|
697
|
+
|
|
698
|
+
/* betting insights */
|
|
699
|
+
.ss-w-binsights { display: flex; flex-direction: column; gap: 7px; }
|
|
700
|
+
.ss-w-binsight { background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; border-left: 3px solid var(--ss-w-accent); padding: 9px 11px; display: flex; flex-direction: column; gap: 6px; }
|
|
701
|
+
.ss-w-bi-head { display: flex; align-items: flex-start; gap: 10px; }
|
|
702
|
+
.ss-w-bi-mkt { display: flex; flex-direction: column; min-width: 0; }
|
|
703
|
+
.ss-w-bi-market { font-size: 8.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ss-w-faint); line-height: 1.3; }
|
|
704
|
+
.ss-w-bi-sel { font-weight: 660; }
|
|
705
|
+
.ss-w-bi-odds { margin-left: auto; display: flex; align-items: center; gap: 6px; flex: none; }
|
|
706
|
+
.ss-w-bi-odds b { font-size: 15px; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); }
|
|
707
|
+
.ss-w-bi-book { font-size: 10px; color: var(--ss-w-faint); }
|
|
708
|
+
.ss-w-bi-live { font-size: 8.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: #c0392b; border-radius: 4px; padding: 1px 5px; }
|
|
709
|
+
.ss-w-bi-text { font-size: 12px; color: var(--ss-w-ink); }
|
|
710
|
+
.ss-w-bi-stats { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
711
|
+
.ss-w-bi-stat b { display: block; font-variant-numeric: tabular-nums; font-weight: 660; }
|
|
712
|
+
.ss-w-bi-stat span { font-size: 9px; text-transform: uppercase; color: var(--ss-w-faint); }
|
|
713
|
+
.ss-w-bi-disc { font-size: 10px; font-style: italic; color: var(--ss-w-faint); }
|
|
635
714
|
|
|
636
715
|
/* action buttons */
|
|
637
716
|
.ss-w-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
|
|
@@ -648,6 +727,11 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
648
727
|
.ss-w-input { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--ss-w-line); }
|
|
649
728
|
.ss-w-textarea { flex: 1; resize: none; border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 8px 10px; font: inherit; color: inherit; background: var(--ss-w-panel); max-height: 96px; }
|
|
650
729
|
.ss-w-textarea:disabled { opacity: .55; cursor: not-allowed; }
|
|
730
|
+
/* iOS Safari zooms the page when a focused field's font-size is < 16px. Bump it
|
|
731
|
+
on touch devices only, so the desktop 13px design is unchanged. */
|
|
732
|
+
@media (pointer: coarse) {
|
|
733
|
+
.ss-w-textarea { font-size: 16px; }
|
|
734
|
+
}
|
|
651
735
|
.ss-w-send { width: 36px; height: 36px; flex: none; border: none; border-radius: 50%; background: var(--ss-w-accent); color: #fff; cursor: pointer; display: grid; place-items: center; font-size: 16px; }
|
|
652
736
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
653
737
|
|
|
@@ -657,7 +741,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
657
741
|
`;
|
|
658
742
|
|
|
659
743
|
// src/StatsWidget.tsx
|
|
660
|
-
var
|
|
744
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
661
745
|
function buildClient(props) {
|
|
662
746
|
try {
|
|
663
747
|
if ("client" in props && props.client) {
|
|
@@ -693,7 +777,7 @@ function StatsWidget(props) {
|
|
|
693
777
|
}, [built, ui.injectStyles]);
|
|
694
778
|
if (!built) return null;
|
|
695
779
|
const attrs = themeAttrs(ui);
|
|
696
|
-
return /* @__PURE__ */ (0,
|
|
780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.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_runtime14.jsx)(ChatPanel, { controller, ui }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
697
781
|
ChatFab,
|
|
698
782
|
{
|
|
699
783
|
label: ui.launcherLabel,
|