@sensiblestats/widget-react 0.38.0 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/index.cjs +147 -69
- 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 +363 -285
- package/dist/index.js.map +1 -1
- package/dist/styles.css +7 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.39.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 428171d: Fix dead scroll space below the chat: the top-anchor spacer was reserved even when a turn was first shown already-complete (panel opened after the greeting finished, or a restored/cross-tab-synced conversation), leaving a full viewport of blank scrollable space. It is now reserved only while a turn is actively streaming. Also adds `overscroll-behavior: contain` to the message list and drops the `+` icon from the "New conversation" menu item.
|
|
8
|
+
|
|
9
|
+
## 0.39.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a5e97e2: Move the "New conversation" header action into a kebab (⋮) overflow menu, keeping Close as a one-tap control. The menu opens on click, closes on Escape, outside-click, or selection, and manages focus for keyboard users.
|
|
14
|
+
|
|
3
15
|
## 0.38.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/StatsWidget.tsx
|
|
39
|
-
var
|
|
39
|
+
var import_react24 = require("react");
|
|
40
40
|
var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/kickoff.ts
|
|
@@ -58,6 +58,7 @@ var STRINGS = {
|
|
|
58
58
|
placeholder: "Ask about a team, player or match\u2026",
|
|
59
59
|
closeChat: "Close chat",
|
|
60
60
|
newConversation: "New conversation",
|
|
61
|
+
moreOptions: "More options",
|
|
61
62
|
chatDialogLabel: "SensibleStats chat",
|
|
62
63
|
stopResponse: "Stop response",
|
|
63
64
|
sendMessage: "Send message",
|
|
@@ -131,6 +132,7 @@ var STRINGS = {
|
|
|
131
132
|
placeholder: "Bir tak\u0131m, oyuncu veya ma\xE7 hakk\u0131nda sor\u2026",
|
|
132
133
|
closeChat: "Sohbeti kapat",
|
|
133
134
|
newConversation: "Yeni sohbet",
|
|
135
|
+
moreOptions: "Di\u011Fer se\xE7enekler",
|
|
134
136
|
chatDialogLabel: "SensibleStats sohbet",
|
|
135
137
|
stopResponse: "Yan\u0131t\u0131 durdur",
|
|
136
138
|
sendMessage: "Mesaj g\xF6nder",
|
|
@@ -227,6 +229,7 @@ function normalizeUi(cfg) {
|
|
|
227
229
|
injectStyles: cfg.injectStyles ?? true,
|
|
228
230
|
closeChat: strings.closeChat,
|
|
229
231
|
newConversation: strings.newConversation,
|
|
232
|
+
moreOptions: strings.moreOptions,
|
|
230
233
|
chatDialogLabel: strings.chatDialogLabel,
|
|
231
234
|
stopResponse: strings.stopResponse,
|
|
232
235
|
sendMessage: strings.sendMessage,
|
|
@@ -1428,8 +1431,12 @@ function CloseIcon() {
|
|
|
1428
1431
|
function ChevronIcon() {
|
|
1429
1432
|
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" }) });
|
|
1430
1433
|
}
|
|
1431
|
-
function
|
|
1432
|
-
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.
|
|
1434
|
+
function KebabIcon() {
|
|
1435
|
+
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.jsxs)("g", { fill: "currentColor", children: [
|
|
1436
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "5", r: "1.6" }),
|
|
1437
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.6" }),
|
|
1438
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "19", r: "1.6" })
|
|
1439
|
+
] }) });
|
|
1433
1440
|
}
|
|
1434
1441
|
var DASHBOARD_EMOJI = {
|
|
1435
1442
|
football: "\u26BD",
|
|
@@ -1495,21 +1502,86 @@ function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup,
|
|
|
1495
1502
|
}
|
|
1496
1503
|
|
|
1497
1504
|
// src/components/ChatPanel.tsx
|
|
1498
|
-
var
|
|
1505
|
+
var import_react23 = require("react");
|
|
1499
1506
|
|
|
1500
1507
|
// src/components/ChatHeader.tsx
|
|
1508
|
+
var import_react6 = require("react");
|
|
1501
1509
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1502
|
-
function ChatHeader({
|
|
1510
|
+
function ChatHeader({
|
|
1511
|
+
onClose,
|
|
1512
|
+
closeLabel = "Close chat",
|
|
1513
|
+
logoUrl,
|
|
1514
|
+
headerTitle,
|
|
1515
|
+
onNewConversation,
|
|
1516
|
+
canReset,
|
|
1517
|
+
newConversationLabel = "New conversation",
|
|
1518
|
+
moreOptionsLabel = "More options"
|
|
1519
|
+
}) {
|
|
1520
|
+
const [menuOpen, setMenuOpen] = (0, import_react6.useState)(false);
|
|
1521
|
+
const wrapRef = (0, import_react6.useRef)(null);
|
|
1522
|
+
const kebabRef = (0, import_react6.useRef)(null);
|
|
1523
|
+
const firstItemRef = (0, import_react6.useRef)(null);
|
|
1524
|
+
const hasMenu = !!(canReset && onNewConversation);
|
|
1525
|
+
(0, import_react6.useEffect)(() => {
|
|
1526
|
+
if (menuOpen) firstItemRef.current?.focus();
|
|
1527
|
+
}, [menuOpen]);
|
|
1528
|
+
(0, import_react6.useEffect)(() => {
|
|
1529
|
+
if (!menuOpen) return;
|
|
1530
|
+
const onPointer = (e) => {
|
|
1531
|
+
if (!wrapRef.current?.contains(e.target)) setMenuOpen(false);
|
|
1532
|
+
};
|
|
1533
|
+
const onKey = (e) => {
|
|
1534
|
+
if (e.key === "Escape") {
|
|
1535
|
+
setMenuOpen(false);
|
|
1536
|
+
kebabRef.current?.focus();
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
document.addEventListener("mousedown", onPointer);
|
|
1540
|
+
document.addEventListener("keydown", onKey);
|
|
1541
|
+
return () => {
|
|
1542
|
+
document.removeEventListener("mousedown", onPointer);
|
|
1543
|
+
document.removeEventListener("keydown", onKey);
|
|
1544
|
+
};
|
|
1545
|
+
}, [menuOpen]);
|
|
1546
|
+
const runNewConversation = () => {
|
|
1547
|
+
setMenuOpen(false);
|
|
1548
|
+
onNewConversation?.();
|
|
1549
|
+
};
|
|
1503
1550
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "ss-w-header", children: [
|
|
1504
1551
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BrandMark, { logoUrl, className: "ss-w-header-mark" }),
|
|
1505
1552
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-w-header-title", children: headerTitle ?? "SensibleStats" }),
|
|
1506
|
-
|
|
1553
|
+
hasMenu ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-w-menu-wrap", ref: wrapRef, children: [
|
|
1554
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1555
|
+
"button",
|
|
1556
|
+
{
|
|
1557
|
+
type: "button",
|
|
1558
|
+
className: "ss-w-kebab",
|
|
1559
|
+
ref: kebabRef,
|
|
1560
|
+
"aria-haspopup": "menu",
|
|
1561
|
+
"aria-expanded": menuOpen,
|
|
1562
|
+
"aria-label": moreOptionsLabel,
|
|
1563
|
+
onClick: () => setMenuOpen((open) => !open),
|
|
1564
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KebabIcon, {})
|
|
1565
|
+
}
|
|
1566
|
+
),
|
|
1567
|
+
menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-w-menu", role: "menu", "aria-label": moreOptionsLabel, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1568
|
+
"button",
|
|
1569
|
+
{
|
|
1570
|
+
type: "button",
|
|
1571
|
+
className: "ss-w-menu-item",
|
|
1572
|
+
role: "menuitem",
|
|
1573
|
+
ref: firstItemRef,
|
|
1574
|
+
onClick: runNewConversation,
|
|
1575
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: newConversationLabel })
|
|
1576
|
+
}
|
|
1577
|
+
) }) : null
|
|
1578
|
+
] }) : null,
|
|
1507
1579
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "ss-w-close", "aria-label": closeLabel, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {}) })
|
|
1508
1580
|
] });
|
|
1509
1581
|
}
|
|
1510
1582
|
|
|
1511
1583
|
// src/components/MessageList.tsx
|
|
1512
|
-
var
|
|
1584
|
+
var import_react21 = require("react");
|
|
1513
1585
|
|
|
1514
1586
|
// src/components/Markdown.tsx
|
|
1515
1587
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -1527,7 +1599,7 @@ function Markdown({ text }) {
|
|
|
1527
1599
|
}
|
|
1528
1600
|
|
|
1529
1601
|
// src/components/EntityCardList.tsx
|
|
1530
|
-
var
|
|
1602
|
+
var import_react7 = require("react");
|
|
1531
1603
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1532
1604
|
var MAX_VISIBLE = 3;
|
|
1533
1605
|
var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
|
|
@@ -1611,7 +1683,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
|
|
|
1611
1683
|
)) });
|
|
1612
1684
|
}
|
|
1613
1685
|
function EntityCardItem({ c }) {
|
|
1614
|
-
const [active, setActive] = (0,
|
|
1686
|
+
const [active, setActive] = (0, import_react7.useState)(0);
|
|
1615
1687
|
const hasBody = c.scopes.length > 0;
|
|
1616
1688
|
if (!hasBody) {
|
|
1617
1689
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-ecard ss-w-ecard-bare", "data-kind": c.kind, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CardHeader, { c, showScopeCrest: false }) });
|
|
@@ -1625,7 +1697,7 @@ function EntityCardItem({ c }) {
|
|
|
1625
1697
|
] });
|
|
1626
1698
|
}
|
|
1627
1699
|
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
1628
|
-
const [expanded, setExpanded] = (0,
|
|
1700
|
+
const [expanded, setExpanded] = (0, import_react7.useState)(false);
|
|
1629
1701
|
if (cards.length === 0) return null;
|
|
1630
1702
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
1631
1703
|
const overflow = cards.length - MAX_VISIBLE;
|
|
@@ -1636,18 +1708,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
1636
1708
|
}
|
|
1637
1709
|
|
|
1638
1710
|
// src/components/BettingInsightList.tsx
|
|
1639
|
-
var
|
|
1711
|
+
var import_react12 = require("react");
|
|
1640
1712
|
|
|
1641
1713
|
// src/components/AddToSlipButton.tsx
|
|
1642
|
-
var
|
|
1714
|
+
var import_react9 = require("react");
|
|
1643
1715
|
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
1644
1716
|
|
|
1645
1717
|
// src/components/Toast.tsx
|
|
1646
|
-
var
|
|
1718
|
+
var import_react8 = require("react");
|
|
1647
1719
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1648
1720
|
var DISMISS_MS = 4e3;
|
|
1649
1721
|
function Toast({ toast, onDismiss }) {
|
|
1650
|
-
(0,
|
|
1722
|
+
(0, import_react8.useEffect)(() => {
|
|
1651
1723
|
if (!toast) return;
|
|
1652
1724
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
1653
1725
|
return () => clearTimeout(timer);
|
|
@@ -1665,10 +1737,10 @@ function withTimeout(value) {
|
|
|
1665
1737
|
]);
|
|
1666
1738
|
}
|
|
1667
1739
|
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1668
|
-
const [pending, setPending] = (0,
|
|
1669
|
-
const [settled, setSettled] = (0,
|
|
1670
|
-
const [toast, setToast] = (0,
|
|
1671
|
-
const dismiss = (0,
|
|
1740
|
+
const [pending, setPending] = (0, import_react9.useState)(false);
|
|
1741
|
+
const [settled, setSettled] = (0, import_react9.useState)(false);
|
|
1742
|
+
const [toast, setToast] = (0, import_react9.useState)(null);
|
|
1743
|
+
const dismiss = (0, import_react9.useCallback)(() => setToast(null), []);
|
|
1672
1744
|
const handler = slip.onAddToSlip;
|
|
1673
1745
|
if (!ui || !handler || !selection) return null;
|
|
1674
1746
|
const onClick = async () => {
|
|
@@ -1732,14 +1804,14 @@ function OddsMovementBadge({
|
|
|
1732
1804
|
}
|
|
1733
1805
|
|
|
1734
1806
|
// src/components/Crest.tsx
|
|
1735
|
-
var
|
|
1807
|
+
var import_react10 = require("react");
|
|
1736
1808
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1737
1809
|
function initials2(name) {
|
|
1738
1810
|
return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
|
|
1739
1811
|
}
|
|
1740
1812
|
function Crest2({ url, name, kind }) {
|
|
1741
1813
|
const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
|
|
1742
|
-
const [failed, setFailed] = (0,
|
|
1814
|
+
const [failed, setFailed] = (0, import_react10.useState)(false);
|
|
1743
1815
|
if (url && !failed) {
|
|
1744
1816
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1745
1817
|
}
|
|
@@ -1747,7 +1819,7 @@ function Crest2({ url, name, kind }) {
|
|
|
1747
1819
|
}
|
|
1748
1820
|
|
|
1749
1821
|
// src/components/PinToggle.tsx
|
|
1750
|
-
var
|
|
1822
|
+
var import_react11 = require("react");
|
|
1751
1823
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1752
1824
|
var PIN_GLYPHS = {
|
|
1753
1825
|
pin: { viewBox: "0 0 24 24", strokeWidth: 1.4, transform: "scale(1.5)", d: "M9.5 1.5 14.5 6.5 13 8l-.5-.5-3 3 .3 2.2-1.4 1.4L5.6 11.3l-3.3 3.3-1-1 3.3-3.3L1.9 7.5l1.4-1.4 2.2.3 3-3L8 2.9z" },
|
|
@@ -1755,8 +1827,8 @@ var PIN_GLYPHS = {
|
|
|
1755
1827
|
bookmark: { viewBox: "0 0 24 24", strokeWidth: 2, d: "M7 3h10a1.5 1.5 0 0 1 1.5 1.5V21l-6.5-3.6L5.5 21V4.5A1.5 1.5 0 0 1 7 3z" }
|
|
1756
1828
|
};
|
|
1757
1829
|
function PinToggle({ pin, monitor, ui }) {
|
|
1758
|
-
const [toast, setToast] = (0,
|
|
1759
|
-
const dismiss = (0,
|
|
1830
|
+
const [toast, setToast] = (0, import_react11.useState)(null);
|
|
1831
|
+
const dismiss = (0, import_react11.useCallback)(() => setToast(null), []);
|
|
1760
1832
|
if (!monitor || !monitor.enabled) return null;
|
|
1761
1833
|
const key = pinKey(pin);
|
|
1762
1834
|
const pinned = monitor.isPinned(key);
|
|
@@ -1808,7 +1880,7 @@ function StatRows({ stats }) {
|
|
|
1808
1880
|
] }, `${s.label}:${s.value}`)) });
|
|
1809
1881
|
}
|
|
1810
1882
|
function ScopedStats({ scopes }) {
|
|
1811
|
-
const [active, setActive] = (0,
|
|
1883
|
+
const [active, setActive] = (0, import_react12.useState)(0);
|
|
1812
1884
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
1813
1885
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
1814
1886
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
@@ -1819,7 +1891,7 @@ function hasStatsContent(it) {
|
|
|
1819
1891
|
return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
|
|
1820
1892
|
}
|
|
1821
1893
|
function ReasonStrip({ it, ui }) {
|
|
1822
|
-
const [open, setOpen] = (0,
|
|
1894
|
+
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
1823
1895
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1824
1896
|
const hasStats = hasStatsContent(it);
|
|
1825
1897
|
const hasText = !!it.text;
|
|
@@ -1929,7 +2001,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
1929
2001
|
}
|
|
1930
2002
|
|
|
1931
2003
|
// src/components/MarketOddsCardList.tsx
|
|
1932
|
-
var
|
|
2004
|
+
var import_react13 = require("react");
|
|
1933
2005
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1934
2006
|
function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
1935
2007
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -1946,7 +2018,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1946
2018
|
] });
|
|
1947
2019
|
}
|
|
1948
2020
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
|
|
1949
|
-
const [open, setOpen] = (0,
|
|
2021
|
+
const [open, setOpen] = (0, import_react13.useState)(false);
|
|
1950
2022
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1951
2023
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
1952
2024
|
const showPin = !!monitor && vm.pinEnabled;
|
|
@@ -2241,7 +2313,7 @@ function FixtureCard({ card }) {
|
|
|
2241
2313
|
}
|
|
2242
2314
|
|
|
2243
2315
|
// src/components/dashboard/CountdownCard.tsx
|
|
2244
|
-
var
|
|
2316
|
+
var import_react14 = require("react");
|
|
2245
2317
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
2246
2318
|
var DAY_MS = 864e5;
|
|
2247
2319
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -2260,12 +2332,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
2260
2332
|
function CountdownCard({ card }) {
|
|
2261
2333
|
const { data } = card;
|
|
2262
2334
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
2263
|
-
const [nowMs, setNowMs] = (0,
|
|
2264
|
-
const [reduced] = (0,
|
|
2335
|
+
const [nowMs, setNowMs] = (0, import_react14.useState)(() => Date.now());
|
|
2336
|
+
const [reduced] = (0, import_react14.useState)(
|
|
2265
2337
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
2266
2338
|
);
|
|
2267
2339
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
2268
|
-
(0,
|
|
2340
|
+
(0, import_react14.useEffect)(() => {
|
|
2269
2341
|
if (reduced) return;
|
|
2270
2342
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
2271
2343
|
return () => window.clearInterval(id);
|
|
@@ -2273,7 +2345,7 @@ function CountdownCard({ card }) {
|
|
|
2273
2345
|
const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
|
|
2274
2346
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ss-w-fandash-card ss-w-fandash-countdown", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-fandash-countdown-top", children: [
|
|
2275
2347
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title ?? "Kick-off in" }),
|
|
2276
|
-
segments ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
2348
|
+
segments ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react14.Fragment, { children: [
|
|
2277
2349
|
i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
|
|
2278
2350
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
|
|
2279
2351
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
|
|
@@ -2284,7 +2356,7 @@ function CountdownCard({ card }) {
|
|
|
2284
2356
|
}
|
|
2285
2357
|
|
|
2286
2358
|
// src/components/dashboard/InjuriesCard.tsx
|
|
2287
|
-
var
|
|
2359
|
+
var import_react15 = require("react");
|
|
2288
2360
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2289
2361
|
var SHOWN = 3;
|
|
2290
2362
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2307,7 +2379,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2307
2379
|
}
|
|
2308
2380
|
function InjuriesCard({ card, ui }) {
|
|
2309
2381
|
const { data } = card;
|
|
2310
|
-
const [expanded, setExpanded] = (0,
|
|
2382
|
+
const [expanded, setExpanded] = (0, import_react15.useState)(false);
|
|
2311
2383
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2312
2384
|
const limit = expanded ? void 0 : SHOWN;
|
|
2313
2385
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2411,7 +2483,7 @@ function LinksCard({ card }) {
|
|
|
2411
2483
|
}
|
|
2412
2484
|
|
|
2413
2485
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2414
|
-
var
|
|
2486
|
+
var import_react16 = require("react");
|
|
2415
2487
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
2416
2488
|
function initials3(name) {
|
|
2417
2489
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2421,7 +2493,7 @@ function PostMatchCrest({
|
|
|
2421
2493
|
name,
|
|
2422
2494
|
side
|
|
2423
2495
|
}) {
|
|
2424
|
-
const [failed, setFailed] = (0,
|
|
2496
|
+
const [failed, setFailed] = (0, import_react16.useState)(false);
|
|
2425
2497
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2426
2498
|
if (url && !failed) {
|
|
2427
2499
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2620,10 +2692,10 @@ function RestingFormCard({ card }) {
|
|
|
2620
2692
|
}
|
|
2621
2693
|
|
|
2622
2694
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2623
|
-
var
|
|
2695
|
+
var import_react18 = require("react");
|
|
2624
2696
|
|
|
2625
2697
|
// src/useLiveClock.ts
|
|
2626
|
-
var
|
|
2698
|
+
var import_react17 = require("react");
|
|
2627
2699
|
var TICK_MS = 6e4;
|
|
2628
2700
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2629
2701
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2632,10 +2704,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2632
2704
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2633
2705
|
}
|
|
2634
2706
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2635
|
-
const [displayMinute, setDisplayMinute] = (0,
|
|
2707
|
+
const [displayMinute, setDisplayMinute] = (0, import_react17.useState)(
|
|
2636
2708
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2637
2709
|
);
|
|
2638
|
-
(0,
|
|
2710
|
+
(0, import_react17.useEffect)(() => {
|
|
2639
2711
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2640
2712
|
if (!clockRunning) return;
|
|
2641
2713
|
const id = window.setInterval(() => {
|
|
@@ -2652,7 +2724,7 @@ function initials5(name) {
|
|
|
2652
2724
|
return name.trim().slice(0, 3).toUpperCase();
|
|
2653
2725
|
}
|
|
2654
2726
|
function LiveCrest({ url, name, side }) {
|
|
2655
|
-
const [failed, setFailed] = (0,
|
|
2727
|
+
const [failed, setFailed] = (0, import_react18.useState)(false);
|
|
2656
2728
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
2657
2729
|
if (url && !failed) {
|
|
2658
2730
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2671,7 +2743,7 @@ function LiveScoreCard({
|
|
|
2671
2743
|
}) {
|
|
2672
2744
|
const { data } = card;
|
|
2673
2745
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
2674
|
-
const seedRef = (0,
|
|
2746
|
+
const seedRef = (0, import_react18.useRef)(null);
|
|
2675
2747
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
2676
2748
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
2677
2749
|
}
|
|
@@ -2711,7 +2783,7 @@ function LiveScoreCard({
|
|
|
2711
2783
|
}
|
|
2712
2784
|
|
|
2713
2785
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
2714
|
-
var
|
|
2786
|
+
var import_react19 = require("react");
|
|
2715
2787
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2716
2788
|
var SHOWN2 = 3;
|
|
2717
2789
|
function EventIcon({ type }) {
|
|
@@ -2753,7 +2825,7 @@ function EventRow({ e }) {
|
|
|
2753
2825
|
] });
|
|
2754
2826
|
}
|
|
2755
2827
|
function LiveTimelineCard({ card, ui }) {
|
|
2756
|
-
const [expanded, setExpanded] = (0,
|
|
2828
|
+
const [expanded, setExpanded] = (0, import_react19.useState)(false);
|
|
2757
2829
|
const events = card.data.events;
|
|
2758
2830
|
if (!events || events.length === 0) return null;
|
|
2759
2831
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -2778,7 +2850,7 @@ function LiveTimelineCard({ card, ui }) {
|
|
|
2778
2850
|
}
|
|
2779
2851
|
|
|
2780
2852
|
// src/components/dashboard/MomentumWave.tsx
|
|
2781
|
-
var
|
|
2853
|
+
var import_react20 = require("react");
|
|
2782
2854
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2783
2855
|
var WIDTH = 270;
|
|
2784
2856
|
var HEIGHT = 56;
|
|
@@ -2802,7 +2874,7 @@ function MomentumWave({
|
|
|
2802
2874
|
}) {
|
|
2803
2875
|
if (points.length === 0) return null;
|
|
2804
2876
|
const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
|
|
2805
|
-
const clipId = (0,
|
|
2877
|
+
const clipId = (0, import_react20.useId)();
|
|
2806
2878
|
const topClipId = `${clipId}-top`;
|
|
2807
2879
|
const bottomClipId = `${clipId}-bottom`;
|
|
2808
2880
|
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
@@ -2993,16 +3065,16 @@ function turnAnchor(messages) {
|
|
|
2993
3065
|
}
|
|
2994
3066
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
2995
3067
|
const { state, send } = controller;
|
|
2996
|
-
const listRef = (0,
|
|
2997
|
-
const endRef = (0,
|
|
2998
|
-
const pinnedRef = (0,
|
|
2999
|
-
const anchoredTurnRef = (0,
|
|
3000
|
-
(0,
|
|
3068
|
+
const listRef = (0, import_react21.useRef)(null);
|
|
3069
|
+
const endRef = (0, import_react21.useRef)(null);
|
|
3070
|
+
const pinnedRef = (0, import_react21.useRef)(true);
|
|
3071
|
+
const anchoredTurnRef = (0, import_react21.useRef)(null);
|
|
3072
|
+
(0, import_react21.useEffect)(() => {
|
|
3001
3073
|
const anchor = turnAnchor(state.messages);
|
|
3002
3074
|
const list = listRef.current;
|
|
3003
3075
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
3004
3076
|
anchoredTurnRef.current = anchor.turnId;
|
|
3005
|
-
if (endRef.current && list) endRef.current.style.height = `${list.clientHeight}px
|
|
3077
|
+
if (endRef.current && list) endRef.current.style.height = state.isStreaming ? `${list.clientHeight}px` : "";
|
|
3006
3078
|
pinnedRef.current = false;
|
|
3007
3079
|
list?.querySelector(`[data-msg-id="${anchor.anchorId}"]`)?.scrollIntoView({ block: "start" });
|
|
3008
3080
|
return;
|
|
@@ -3053,10 +3125,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3053
3125
|
}
|
|
3054
3126
|
|
|
3055
3127
|
// src/components/ChatInput.tsx
|
|
3056
|
-
var
|
|
3128
|
+
var import_react22 = require("react");
|
|
3057
3129
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
3058
3130
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
3059
|
-
const [value, setValue] = (0,
|
|
3131
|
+
const [value, setValue] = (0, import_react22.useState)("");
|
|
3060
3132
|
const submit = () => {
|
|
3061
3133
|
const t = value.trim();
|
|
3062
3134
|
if (!t) return;
|
|
@@ -3363,8 +3435,8 @@ function OddsPanel({ monitor, ui, slip }) {
|
|
|
3363
3435
|
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
3364
3436
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
3365
3437
|
function useIsModal() {
|
|
3366
|
-
const [modal, setModal] = (0,
|
|
3367
|
-
(0,
|
|
3438
|
+
const [modal, setModal] = (0, import_react23.useState)(false);
|
|
3439
|
+
(0, import_react23.useEffect)(() => {
|
|
3368
3440
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
3369
3441
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
3370
3442
|
const sync = () => setModal(mql.matches);
|
|
@@ -3377,7 +3449,7 @@ function useIsModal() {
|
|
|
3377
3449
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3378
3450
|
const { state, close, stop, send, reset } = controller;
|
|
3379
3451
|
const isModal = useIsModal();
|
|
3380
|
-
(0,
|
|
3452
|
+
(0, import_react23.useEffect)(() => {
|
|
3381
3453
|
const onKey = (e) => {
|
|
3382
3454
|
if (e.key === "Escape") close();
|
|
3383
3455
|
};
|
|
@@ -3388,7 +3460,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3388
3460
|
monitor?.setActiveTab("chat");
|
|
3389
3461
|
send(text);
|
|
3390
3462
|
};
|
|
3391
|
-
(0,
|
|
3463
|
+
(0, import_react23.useEffect)(() => {
|
|
3392
3464
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3393
3465
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3394
3466
|
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
@@ -3401,7 +3473,8 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3401
3473
|
headerTitle: ui.headerTitle,
|
|
3402
3474
|
onNewConversation: reset,
|
|
3403
3475
|
canReset: state.messages.length > 0,
|
|
3404
|
-
newConversationLabel: ui.newConversation
|
|
3476
|
+
newConversationLabel: ui.newConversation,
|
|
3477
|
+
moreOptionsLabel: ui.moreOptions
|
|
3405
3478
|
}
|
|
3406
3479
|
),
|
|
3407
3480
|
monitor && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
@@ -3519,14 +3592,19 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3519
3592
|
.ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }
|
|
3520
3593
|
.ss-w-header-mark { width: 18px; height: 18px; display: block; object-fit: contain; }
|
|
3521
3594
|
.ss-w-header-title { font-weight: 660; flex: 1; }
|
|
3522
|
-
.ss-w-newconv { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3523
3595
|
.ss-w-close { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3596
|
+
.ss-w-menu-wrap { position: relative; display: grid; place-items: center; }
|
|
3597
|
+
.ss-w-kebab { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
|
|
3598
|
+
.ss-w-menu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 5; min-width: 184px; padding: 6px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,.18); }
|
|
3599
|
+
.ss-w-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; background: none; border: none; border-radius: 8px; color: var(--ss-w-ink); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
|
|
3600
|
+
.ss-w-menu-item:hover, .ss-w-menu-item:focus-visible { background: var(--ss-w-bubble); }
|
|
3601
|
+
.ss-w-menu-item svg { color: var(--ss-w-faint); font-size: 15px; flex: none; }
|
|
3524
3602
|
|
|
3525
3603
|
/* messages */
|
|
3526
3604
|
/* .ss-w-mon-list (Matches/Odds tab scrollers) shares .ss-w-list's box/scroll rules by design --
|
|
3527
3605
|
same visual rhythm as the chat message list -- but gets its own class so it isn't the same DOM
|
|
3528
3606
|
landmark as the chat list when both exist in the panel at once (Task 16 fix round, M4). */
|
|
3529
|
-
.ss-w-list, .ss-w-mon-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 12px; scroll-padding-top: 14px; }
|
|
3607
|
+
.ss-w-list, .ss-w-mon-list { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 14px; display: flex; flex-direction: column; gap: 12px; scroll-padding-top: 14px; }
|
|
3530
3608
|
/* Transient bottom spacer: reserves a viewport of room below a new turn so its
|
|
3531
3609
|
top can be scrolled into view while the reply is still streaming in. Height is
|
|
3532
3610
|
set imperatively and flex must not shrink it away. */
|
|
@@ -4260,11 +4338,11 @@ function injectStyles(root) {
|
|
|
4260
4338
|
function StatsWidget(props) {
|
|
4261
4339
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
4262
4340
|
const cfg = props.config;
|
|
4263
|
-
const built = (0,
|
|
4264
|
-
const ui = (0,
|
|
4265
|
-
const rootRef = (0,
|
|
4266
|
-
const introDoneRef = (0,
|
|
4267
|
-
const markIntroDone = (0,
|
|
4341
|
+
const built = (0, import_react24.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
4342
|
+
const ui = (0, import_react24.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
4343
|
+
const rootRef = (0, import_react24.useRef)(null);
|
|
4344
|
+
const introDoneRef = (0, import_react24.useRef)(false);
|
|
4345
|
+
const markIntroDone = (0, import_react24.useCallback)(() => {
|
|
4268
4346
|
introDoneRef.current = true;
|
|
4269
4347
|
}, []);
|
|
4270
4348
|
const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
|
|
@@ -4275,7 +4353,7 @@ function StatsWidget(props) {
|
|
|
4275
4353
|
storageKey: `ss-w-pins:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`,
|
|
4276
4354
|
isOpen: controller.state.isOpen
|
|
4277
4355
|
});
|
|
4278
|
-
const slip = (0,
|
|
4356
|
+
const slip = (0, import_react24.useMemo)(
|
|
4279
4357
|
() => {
|
|
4280
4358
|
const handler = props.config?.onAddToSlip;
|
|
4281
4359
|
return {
|
|
@@ -4298,11 +4376,11 @@ function StatsWidget(props) {
|
|
|
4298
4376
|
},
|
|
4299
4377
|
[props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
|
|
4300
4378
|
);
|
|
4301
|
-
const onDashboardTap = (0,
|
|
4379
|
+
const onDashboardTap = (0, import_react24.useMemo)(
|
|
4302
4380
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
4303
4381
|
[built]
|
|
4304
4382
|
);
|
|
4305
|
-
(0,
|
|
4383
|
+
(0, import_react24.useEffect)(() => {
|
|
4306
4384
|
if (!built || !ui.injectStyles) return;
|
|
4307
4385
|
const node = rootRef.current?.getRootNode();
|
|
4308
4386
|
if (node) injectStyles(node);
|