@sensiblestats/widget-react 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.39.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+
3
9
  ## 0.38.0
4
10
 
5
11
  ### 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 import_react23 = require("react");
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,
@@ -1431,6 +1434,13 @@ function ChevronIcon() {
1431
1434
  function NewChatIcon() {
1432
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.jsx)("path", { fill: "none", d: "M12 5v14M5 12h14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) });
1433
1436
  }
1437
+ function KebabIcon() {
1438
+ 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: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "5", r: "1.6" }),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.6" }),
1441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "19", r: "1.6" })
1442
+ ] }) });
1443
+ }
1434
1444
  var DASHBOARD_EMOJI = {
1435
1445
  football: "\u26BD",
1436
1446
  "goal-net": "\u{1F945}",
@@ -1495,21 +1505,89 @@ function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup,
1495
1505
  }
1496
1506
 
1497
1507
  // src/components/ChatPanel.tsx
1498
- var import_react22 = require("react");
1508
+ var import_react23 = require("react");
1499
1509
 
1500
1510
  // src/components/ChatHeader.tsx
1511
+ var import_react6 = require("react");
1501
1512
  var import_jsx_runtime5 = require("react/jsx-runtime");
1502
- function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle, onNewConversation, canReset, newConversationLabel = "New conversation" }) {
1513
+ function ChatHeader({
1514
+ onClose,
1515
+ closeLabel = "Close chat",
1516
+ logoUrl,
1517
+ headerTitle,
1518
+ onNewConversation,
1519
+ canReset,
1520
+ newConversationLabel = "New conversation",
1521
+ moreOptionsLabel = "More options"
1522
+ }) {
1523
+ const [menuOpen, setMenuOpen] = (0, import_react6.useState)(false);
1524
+ const wrapRef = (0, import_react6.useRef)(null);
1525
+ const kebabRef = (0, import_react6.useRef)(null);
1526
+ const firstItemRef = (0, import_react6.useRef)(null);
1527
+ const hasMenu = !!(canReset && onNewConversation);
1528
+ (0, import_react6.useEffect)(() => {
1529
+ if (menuOpen) firstItemRef.current?.focus();
1530
+ }, [menuOpen]);
1531
+ (0, import_react6.useEffect)(() => {
1532
+ if (!menuOpen) return;
1533
+ const onPointer = (e) => {
1534
+ if (!wrapRef.current?.contains(e.target)) setMenuOpen(false);
1535
+ };
1536
+ const onKey = (e) => {
1537
+ if (e.key === "Escape") {
1538
+ setMenuOpen(false);
1539
+ kebabRef.current?.focus();
1540
+ }
1541
+ };
1542
+ document.addEventListener("mousedown", onPointer);
1543
+ document.addEventListener("keydown", onKey);
1544
+ return () => {
1545
+ document.removeEventListener("mousedown", onPointer);
1546
+ document.removeEventListener("keydown", onKey);
1547
+ };
1548
+ }, [menuOpen]);
1549
+ const runNewConversation = () => {
1550
+ setMenuOpen(false);
1551
+ onNewConversation?.();
1552
+ };
1503
1553
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "ss-w-header", children: [
1504
1554
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BrandMark, { logoUrl, className: "ss-w-header-mark" }),
1505
1555
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-w-header-title", children: headerTitle ?? "SensibleStats" }),
1506
- canReset && onNewConversation ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "ss-w-newconv", "aria-label": newConversationLabel, onClick: onNewConversation, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {}) }) : null,
1556
+ hasMenu ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-w-menu-wrap", ref: wrapRef, children: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1558
+ "button",
1559
+ {
1560
+ type: "button",
1561
+ className: "ss-w-kebab",
1562
+ ref: kebabRef,
1563
+ "aria-haspopup": "menu",
1564
+ "aria-expanded": menuOpen,
1565
+ "aria-label": moreOptionsLabel,
1566
+ onClick: () => setMenuOpen((open) => !open),
1567
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KebabIcon, {})
1568
+ }
1569
+ ),
1570
+ menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-w-menu", role: "menu", "aria-label": moreOptionsLabel, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1571
+ "button",
1572
+ {
1573
+ type: "button",
1574
+ className: "ss-w-menu-item",
1575
+ role: "menuitem",
1576
+ ref: firstItemRef,
1577
+ onClick: runNewConversation,
1578
+ children: [
1579
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {}),
1580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: newConversationLabel })
1581
+ ]
1582
+ }
1583
+ ) }) : null
1584
+ ] }) : null,
1507
1585
  /* @__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
1586
  ] });
1509
1587
  }
1510
1588
 
1511
1589
  // src/components/MessageList.tsx
1512
- var import_react20 = require("react");
1590
+ var import_react21 = require("react");
1513
1591
 
1514
1592
  // src/components/Markdown.tsx
1515
1593
  var import_react_markdown = __toESM(require("react-markdown"), 1);
@@ -1527,7 +1605,7 @@ function Markdown({ text }) {
1527
1605
  }
1528
1606
 
1529
1607
  // src/components/EntityCardList.tsx
1530
- var import_react6 = require("react");
1608
+ var import_react7 = require("react");
1531
1609
  var import_jsx_runtime7 = require("react/jsx-runtime");
1532
1610
  var MAX_VISIBLE = 3;
1533
1611
  var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
@@ -1611,7 +1689,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
1611
1689
  )) });
1612
1690
  }
1613
1691
  function EntityCardItem({ c }) {
1614
- const [active, setActive] = (0, import_react6.useState)(0);
1692
+ const [active, setActive] = (0, import_react7.useState)(0);
1615
1693
  const hasBody = c.scopes.length > 0;
1616
1694
  if (!hasBody) {
1617
1695
  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 +1703,7 @@ function EntityCardItem({ c }) {
1625
1703
  ] });
1626
1704
  }
1627
1705
  function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
1628
- const [expanded, setExpanded] = (0, import_react6.useState)(false);
1706
+ const [expanded, setExpanded] = (0, import_react7.useState)(false);
1629
1707
  if (cards.length === 0) return null;
1630
1708
  const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
1631
1709
  const overflow = cards.length - MAX_VISIBLE;
@@ -1636,18 +1714,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
1636
1714
  }
1637
1715
 
1638
1716
  // src/components/BettingInsightList.tsx
1639
- var import_react11 = require("react");
1717
+ var import_react12 = require("react");
1640
1718
 
1641
1719
  // src/components/AddToSlipButton.tsx
1642
- var import_react8 = require("react");
1720
+ var import_react9 = require("react");
1643
1721
  var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
1644
1722
 
1645
1723
  // src/components/Toast.tsx
1646
- var import_react7 = require("react");
1724
+ var import_react8 = require("react");
1647
1725
  var import_jsx_runtime8 = require("react/jsx-runtime");
1648
1726
  var DISMISS_MS = 4e3;
1649
1727
  function Toast({ toast, onDismiss }) {
1650
- (0, import_react7.useEffect)(() => {
1728
+ (0, import_react8.useEffect)(() => {
1651
1729
  if (!toast) return;
1652
1730
  const timer = setTimeout(onDismiss, DISMISS_MS);
1653
1731
  return () => clearTimeout(timer);
@@ -1665,10 +1743,10 @@ function withTimeout(value) {
1665
1743
  ]);
1666
1744
  }
1667
1745
  function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
1668
- const [pending, setPending] = (0, import_react8.useState)(false);
1669
- const [settled, setSettled] = (0, import_react8.useState)(false);
1670
- const [toast, setToast] = (0, import_react8.useState)(null);
1671
- const dismiss = (0, import_react8.useCallback)(() => setToast(null), []);
1746
+ const [pending, setPending] = (0, import_react9.useState)(false);
1747
+ const [settled, setSettled] = (0, import_react9.useState)(false);
1748
+ const [toast, setToast] = (0, import_react9.useState)(null);
1749
+ const dismiss = (0, import_react9.useCallback)(() => setToast(null), []);
1672
1750
  const handler = slip.onAddToSlip;
1673
1751
  if (!ui || !handler || !selection) return null;
1674
1752
  const onClick = async () => {
@@ -1732,14 +1810,14 @@ function OddsMovementBadge({
1732
1810
  }
1733
1811
 
1734
1812
  // src/components/Crest.tsx
1735
- var import_react9 = require("react");
1813
+ var import_react10 = require("react");
1736
1814
  var import_jsx_runtime11 = require("react/jsx-runtime");
1737
1815
  function initials2(name) {
1738
1816
  return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
1739
1817
  }
1740
1818
  function Crest2({ url, name, kind }) {
1741
1819
  const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
1742
- const [failed, setFailed] = (0, import_react9.useState)(false);
1820
+ const [failed, setFailed] = (0, import_react10.useState)(false);
1743
1821
  if (url && !failed) {
1744
1822
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1745
1823
  }
@@ -1747,7 +1825,7 @@ function Crest2({ url, name, kind }) {
1747
1825
  }
1748
1826
 
1749
1827
  // src/components/PinToggle.tsx
1750
- var import_react10 = require("react");
1828
+ var import_react11 = require("react");
1751
1829
  var import_jsx_runtime12 = require("react/jsx-runtime");
1752
1830
  var PIN_GLYPHS = {
1753
1831
  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 +1833,8 @@ var PIN_GLYPHS = {
1755
1833
  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
1834
  };
1757
1835
  function PinToggle({ pin, monitor, ui }) {
1758
- const [toast, setToast] = (0, import_react10.useState)(null);
1759
- const dismiss = (0, import_react10.useCallback)(() => setToast(null), []);
1836
+ const [toast, setToast] = (0, import_react11.useState)(null);
1837
+ const dismiss = (0, import_react11.useCallback)(() => setToast(null), []);
1760
1838
  if (!monitor || !monitor.enabled) return null;
1761
1839
  const key = pinKey(pin);
1762
1840
  const pinned = monitor.isPinned(key);
@@ -1808,7 +1886,7 @@ function StatRows({ stats }) {
1808
1886
  ] }, `${s.label}:${s.value}`)) });
1809
1887
  }
1810
1888
  function ScopedStats({ scopes }) {
1811
- const [active, setActive] = (0, import_react11.useState)(0);
1889
+ const [active, setActive] = (0, import_react12.useState)(0);
1812
1890
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
1813
1891
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-bi-scoped", children: [
1814
1892
  /* @__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 +1897,7 @@ function hasStatsContent(it) {
1819
1897
  return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
1820
1898
  }
1821
1899
  function ReasonStrip({ it, ui }) {
1822
- const [open, setOpen] = (0, import_react11.useState)(false);
1900
+ const [open, setOpen] = (0, import_react12.useState)(false);
1823
1901
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
1824
1902
  const hasStats = hasStatsContent(it);
1825
1903
  const hasText = !!it.text;
@@ -1929,7 +2007,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
1929
2007
  }
1930
2008
 
1931
2009
  // src/components/MarketOddsCardList.tsx
1932
- var import_react12 = require("react");
2010
+ var import_react13 = require("react");
1933
2011
  var import_jsx_runtime14 = require("react/jsx-runtime");
1934
2012
  function OddsRow({ vm, row, ui, slip, inSlipIds }) {
1935
2013
  const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
@@ -1946,7 +2024,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
1946
2024
  ] });
1947
2025
  }
1948
2026
  function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
1949
- const [open, setOpen] = (0, import_react12.useState)(false);
2027
+ const [open, setOpen] = (0, import_react13.useState)(false);
1950
2028
  const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
1951
2029
  const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
1952
2030
  const showPin = !!monitor && vm.pinEnabled;
@@ -2241,7 +2319,7 @@ function FixtureCard({ card }) {
2241
2319
  }
2242
2320
 
2243
2321
  // src/components/dashboard/CountdownCard.tsx
2244
- var import_react13 = require("react");
2322
+ var import_react14 = require("react");
2245
2323
  var import_jsx_runtime20 = require("react/jsx-runtime");
2246
2324
  var DAY_MS = 864e5;
2247
2325
  function remainingSegments(kickoffUnix, nowMs, withSeconds) {
@@ -2260,12 +2338,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
2260
2338
  function CountdownCard({ card }) {
2261
2339
  const { data } = card;
2262
2340
  const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
2263
- const [nowMs, setNowMs] = (0, import_react13.useState)(() => Date.now());
2264
- const [reduced] = (0, import_react13.useState)(
2341
+ const [nowMs, setNowMs] = (0, import_react14.useState)(() => Date.now());
2342
+ const [reduced] = (0, import_react14.useState)(
2265
2343
  () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
2266
2344
  );
2267
2345
  const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
2268
- (0, import_react13.useEffect)(() => {
2346
+ (0, import_react14.useEffect)(() => {
2269
2347
  if (reduced) return;
2270
2348
  const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
2271
2349
  return () => window.clearInterval(id);
@@ -2273,7 +2351,7 @@ function CountdownCard({ card }) {
2273
2351
  const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
2274
2352
  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
2353
  /* @__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)(import_react13.Fragment, { children: [
2354
+ 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
2355
  i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
2278
2356
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
2279
2357
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
@@ -2284,7 +2362,7 @@ function CountdownCard({ card }) {
2284
2362
  }
2285
2363
 
2286
2364
  // src/components/dashboard/InjuriesCard.tsx
2287
- var import_react14 = require("react");
2365
+ var import_react15 = require("react");
2288
2366
  var import_jsx_runtime21 = require("react/jsx-runtime");
2289
2367
  var SHOWN = 3;
2290
2368
  function ColHead({ name, crestUrl }) {
@@ -2307,7 +2385,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
2307
2385
  }
2308
2386
  function InjuriesCard({ card, ui }) {
2309
2387
  const { data } = card;
2310
- const [expanded, setExpanded] = (0, import_react14.useState)(false);
2388
+ const [expanded, setExpanded] = (0, import_react15.useState)(false);
2311
2389
  const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
2312
2390
  const limit = expanded ? void 0 : SHOWN;
2313
2391
  const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
@@ -2411,7 +2489,7 @@ function LinksCard({ card }) {
2411
2489
  }
2412
2490
 
2413
2491
  // src/components/dashboard/PostMatchResultCard.tsx
2414
- var import_react15 = require("react");
2492
+ var import_react16 = require("react");
2415
2493
  var import_jsx_runtime27 = require("react/jsx-runtime");
2416
2494
  function initials3(name) {
2417
2495
  return name.trim().slice(0, 3).toUpperCase();
@@ -2421,7 +2499,7 @@ function PostMatchCrest({
2421
2499
  name,
2422
2500
  side
2423
2501
  }) {
2424
- const [failed, setFailed] = (0, import_react15.useState)(false);
2502
+ const [failed, setFailed] = (0, import_react16.useState)(false);
2425
2503
  const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
2426
2504
  if (url && !failed) {
2427
2505
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
@@ -2620,10 +2698,10 @@ function RestingFormCard({ card }) {
2620
2698
  }
2621
2699
 
2622
2700
  // src/components/dashboard/LiveScoreCard.tsx
2623
- var import_react17 = require("react");
2701
+ var import_react18 = require("react");
2624
2702
 
2625
2703
  // src/useLiveClock.ts
2626
- var import_react16 = require("react");
2704
+ var import_react17 = require("react");
2627
2705
  var TICK_MS = 6e4;
2628
2706
  var MAX_LIVE_TICK_MINUTES = 15;
2629
2707
  function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
@@ -2632,10 +2710,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
2632
2710
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
2633
2711
  }
2634
2712
  function useLiveClock(minute, clockRunning, receivedAtMs) {
2635
- const [displayMinute, setDisplayMinute] = (0, import_react16.useState)(
2713
+ const [displayMinute, setDisplayMinute] = (0, import_react17.useState)(
2636
2714
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
2637
2715
  );
2638
- (0, import_react16.useEffect)(() => {
2716
+ (0, import_react17.useEffect)(() => {
2639
2717
  setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
2640
2718
  if (!clockRunning) return;
2641
2719
  const id = window.setInterval(() => {
@@ -2652,7 +2730,7 @@ function initials5(name) {
2652
2730
  return name.trim().slice(0, 3).toUpperCase();
2653
2731
  }
2654
2732
  function LiveCrest({ url, name, side }) {
2655
- const [failed, setFailed] = (0, import_react17.useState)(false);
2733
+ const [failed, setFailed] = (0, import_react18.useState)(false);
2656
2734
  const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
2657
2735
  if (url && !failed) {
2658
2736
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
@@ -2671,7 +2749,7 @@ function LiveScoreCard({
2671
2749
  }) {
2672
2750
  const { data } = card;
2673
2751
  const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
2674
- const seedRef = (0, import_react17.useRef)(null);
2752
+ const seedRef = (0, import_react18.useRef)(null);
2675
2753
  if (!seedRef.current || seedRef.current.key !== seedKey) {
2676
2754
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
2677
2755
  }
@@ -2711,7 +2789,7 @@ function LiveScoreCard({
2711
2789
  }
2712
2790
 
2713
2791
  // src/components/dashboard/LiveTimelineCard.tsx
2714
- var import_react18 = require("react");
2792
+ var import_react19 = require("react");
2715
2793
  var import_jsx_runtime37 = require("react/jsx-runtime");
2716
2794
  var SHOWN2 = 3;
2717
2795
  function EventIcon({ type }) {
@@ -2753,7 +2831,7 @@ function EventRow({ e }) {
2753
2831
  ] });
2754
2832
  }
2755
2833
  function LiveTimelineCard({ card, ui }) {
2756
- const [expanded, setExpanded] = (0, import_react18.useState)(false);
2834
+ const [expanded, setExpanded] = (0, import_react19.useState)(false);
2757
2835
  const events = card.data.events;
2758
2836
  if (!events || events.length === 0) return null;
2759
2837
  const sorted = [...events].sort((a, b) => b.minute - a.minute);
@@ -2778,7 +2856,7 @@ function LiveTimelineCard({ card, ui }) {
2778
2856
  }
2779
2857
 
2780
2858
  // src/components/dashboard/MomentumWave.tsx
2781
- var import_react19 = require("react");
2859
+ var import_react20 = require("react");
2782
2860
  var import_jsx_runtime38 = require("react/jsx-runtime");
2783
2861
  var WIDTH = 270;
2784
2862
  var HEIGHT = 56;
@@ -2802,7 +2880,7 @@ function MomentumWave({
2802
2880
  }) {
2803
2881
  if (points.length === 0) return null;
2804
2882
  const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
2805
- const clipId = (0, import_react19.useId)();
2883
+ const clipId = (0, import_react20.useId)();
2806
2884
  const topClipId = `${clipId}-top`;
2807
2885
  const bottomClipId = `${clipId}-bottom`;
2808
2886
  const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
@@ -2993,11 +3071,11 @@ function turnAnchor(messages) {
2993
3071
  }
2994
3072
  function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
2995
3073
  const { state, send } = controller;
2996
- const listRef = (0, import_react20.useRef)(null);
2997
- const endRef = (0, import_react20.useRef)(null);
2998
- const pinnedRef = (0, import_react20.useRef)(true);
2999
- const anchoredTurnRef = (0, import_react20.useRef)(null);
3000
- (0, import_react20.useEffect)(() => {
3074
+ const listRef = (0, import_react21.useRef)(null);
3075
+ const endRef = (0, import_react21.useRef)(null);
3076
+ const pinnedRef = (0, import_react21.useRef)(true);
3077
+ const anchoredTurnRef = (0, import_react21.useRef)(null);
3078
+ (0, import_react21.useEffect)(() => {
3001
3079
  const anchor = turnAnchor(state.messages);
3002
3080
  const list = listRef.current;
3003
3081
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -3053,10 +3131,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
3053
3131
  }
3054
3132
 
3055
3133
  // src/components/ChatInput.tsx
3056
- var import_react21 = require("react");
3134
+ var import_react22 = require("react");
3057
3135
  var import_jsx_runtime44 = require("react/jsx-runtime");
3058
3136
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
3059
- const [value, setValue] = (0, import_react21.useState)("");
3137
+ const [value, setValue] = (0, import_react22.useState)("");
3060
3138
  const submit = () => {
3061
3139
  const t = value.trim();
3062
3140
  if (!t) return;
@@ -3363,8 +3441,8 @@ function OddsPanel({ monitor, ui, slip }) {
3363
3441
  var import_jsx_runtime49 = require("react/jsx-runtime");
3364
3442
  var FULLSCREEN_QUERY = "(max-width: 639px)";
3365
3443
  function useIsModal() {
3366
- const [modal, setModal] = (0, import_react22.useState)(false);
3367
- (0, import_react22.useEffect)(() => {
3444
+ const [modal, setModal] = (0, import_react23.useState)(false);
3445
+ (0, import_react23.useEffect)(() => {
3368
3446
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
3369
3447
  const mql = window.matchMedia(FULLSCREEN_QUERY);
3370
3448
  const sync = () => setModal(mql.matches);
@@ -3377,7 +3455,7 @@ function useIsModal() {
3377
3455
  function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
3378
3456
  const { state, close, stop, send, reset } = controller;
3379
3457
  const isModal = useIsModal();
3380
- (0, import_react22.useEffect)(() => {
3458
+ (0, import_react23.useEffect)(() => {
3381
3459
  const onKey = (e) => {
3382
3460
  if (e.key === "Escape") close();
3383
3461
  };
@@ -3388,7 +3466,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
3388
3466
  monitor?.setActiveTab("chat");
3389
3467
  send(text);
3390
3468
  };
3391
- (0, import_react22.useEffect)(() => {
3469
+ (0, import_react23.useEffect)(() => {
3392
3470
  if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
3393
3471
  }, [monitor, state.isStreaming, state.messages.length]);
3394
3472
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
@@ -3401,7 +3479,8 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
3401
3479
  headerTitle: ui.headerTitle,
3402
3480
  onNewConversation: reset,
3403
3481
  canReset: state.messages.length > 0,
3404
- newConversationLabel: ui.newConversation
3482
+ newConversationLabel: ui.newConversation,
3483
+ moreOptionsLabel: ui.moreOptions
3405
3484
  }
3406
3485
  ),
3407
3486
  monitor && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
@@ -3519,8 +3598,13 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
3519
3598
  .ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }
3520
3599
  .ss-w-header-mark { width: 18px; height: 18px; display: block; object-fit: contain; }
3521
3600
  .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
3601
  .ss-w-close { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
3602
+ .ss-w-menu-wrap { position: relative; display: grid; place-items: center; }
3603
+ .ss-w-kebab { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
3604
+ .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); }
3605
+ .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; }
3606
+ .ss-w-menu-item:hover, .ss-w-menu-item:focus-visible { background: var(--ss-w-bubble); }
3607
+ .ss-w-menu-item svg { color: var(--ss-w-faint); font-size: 15px; flex: none; }
3524
3608
 
3525
3609
  /* messages */
3526
3610
  /* .ss-w-mon-list (Matches/Odds tab scrollers) shares .ss-w-list's box/scroll rules by design --
@@ -4260,11 +4344,11 @@ function injectStyles(root) {
4260
4344
  function StatsWidget(props) {
4261
4345
  const injectedClient = "client" in props ? props.client : void 0;
4262
4346
  const cfg = props.config;
4263
- const built = (0, import_react23.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
4264
- const ui = (0, import_react23.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
4265
- const rootRef = (0, import_react23.useRef)(null);
4266
- const introDoneRef = (0, import_react23.useRef)(false);
4267
- const markIntroDone = (0, import_react23.useCallback)(() => {
4347
+ const built = (0, import_react24.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
4348
+ const ui = (0, import_react24.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
4349
+ const rootRef = (0, import_react24.useRef)(null);
4350
+ const introDoneRef = (0, import_react24.useRef)(false);
4351
+ const markIntroDone = (0, import_react24.useCallback)(() => {
4268
4352
  introDoneRef.current = true;
4269
4353
  }, []);
4270
4354
  const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
@@ -4275,7 +4359,7 @@ function StatsWidget(props) {
4275
4359
  storageKey: `ss-w-pins:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`,
4276
4360
  isOpen: controller.state.isOpen
4277
4361
  });
4278
- const slip = (0, import_react23.useMemo)(
4362
+ const slip = (0, import_react24.useMemo)(
4279
4363
  () => {
4280
4364
  const handler = props.config?.onAddToSlip;
4281
4365
  return {
@@ -4298,11 +4382,11 @@ function StatsWidget(props) {
4298
4382
  },
4299
4383
  [props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
4300
4384
  );
4301
- const onDashboardTap = (0, import_react23.useMemo)(
4385
+ const onDashboardTap = (0, import_react24.useMemo)(
4302
4386
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
4303
4387
  [built]
4304
4388
  );
4305
- (0, import_react23.useEffect)(() => {
4389
+ (0, import_react24.useEffect)(() => {
4306
4390
  if (!built || !ui.injectStyles) return;
4307
4391
  const node = rootRef.current?.getRootNode();
4308
4392
  if (node) injectStyles(node);