@sensiblestats/widget-react 0.35.0 → 0.36.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 +12 -0
- package/README.md +112 -0
- package/dist/index.cjs +141 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +132 -56
- package/dist/index.js.map +1 -1
- package/dist/styles.css +33 -1
- package/package.json +1 -1
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_react23 = require("react");
|
|
40
40
|
var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
|
|
41
41
|
|
|
42
42
|
// src/kickoff.ts
|
|
@@ -207,6 +207,7 @@ function normalizeUi(cfg) {
|
|
|
207
207
|
cta: cfg.cta,
|
|
208
208
|
surface: cfg.surface,
|
|
209
209
|
ink: cfg.ink,
|
|
210
|
+
pinIcon: cfg.pinIcon ?? "pin",
|
|
210
211
|
position: cfg.position ?? "right",
|
|
211
212
|
offset: { x: cfg.offset?.x ?? 20, y: cfg.offset?.y ?? 20 },
|
|
212
213
|
radius: cfg.radius ?? 16,
|
|
@@ -1194,6 +1195,9 @@ function useLiveMonitor(opts) {
|
|
|
1194
1195
|
};
|
|
1195
1196
|
}
|
|
1196
1197
|
|
|
1198
|
+
// src/components/ChatFab.tsx
|
|
1199
|
+
var import_react5 = require("react");
|
|
1200
|
+
|
|
1197
1201
|
// src/components/BrandMark.tsx
|
|
1198
1202
|
var import_react4 = require("react");
|
|
1199
1203
|
|
|
@@ -1246,23 +1250,39 @@ function BrandMark({ logoUrl, className }) {
|
|
|
1246
1250
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1247
1251
|
function GreetingBubble({ text, onOpen, onDismiss, dismissLabel = "Dismiss" }) {
|
|
1248
1252
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ss-w-greeting", role: "note", children: [
|
|
1249
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", className: "ss-w-greeting-body", onClick: onOpen, children: [
|
|
1254
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "ss-w-greeting-dots", "aria-hidden": "true", children: [
|
|
1255
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", {}),
|
|
1256
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", {}),
|
|
1257
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("i", {})
|
|
1258
|
+
] }),
|
|
1259
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ss-w-greeting-text", children: text })
|
|
1260
|
+
] }),
|
|
1250
1261
|
onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null
|
|
1251
1262
|
] });
|
|
1252
1263
|
}
|
|
1253
1264
|
|
|
1254
1265
|
// src/components/ChatFab.tsx
|
|
1255
1266
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1256
|
-
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss" }) {
|
|
1267
|
+
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart }) {
|
|
1257
1268
|
const bubble = popupText ?? greeting;
|
|
1258
|
-
|
|
1269
|
+
const [intro, setIntro] = (0, import_react5.useState)(false);
|
|
1270
|
+
const armedRef = (0, import_react5.useRef)(introAllowed);
|
|
1271
|
+
(0, import_react5.useEffect)(() => {
|
|
1272
|
+
if (armedRef.current && bubble) {
|
|
1273
|
+
armedRef.current = false;
|
|
1274
|
+
setIntro(true);
|
|
1275
|
+
onIntroStart?.();
|
|
1276
|
+
}
|
|
1277
|
+
}, [bubble, onIntroStart]);
|
|
1278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: intro ? "ss-w-fab-wrap ss-w-intro" : "ss-w-fab-wrap", children: [
|
|
1259
1279
|
bubble ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel }) : null,
|
|
1260
1280
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "ss-w-fab", "aria-label": label, onClick: onOpen, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BrandMark, { logoUrl, className: "ss-w-fab-mark" }) })
|
|
1261
1281
|
] });
|
|
1262
1282
|
}
|
|
1263
1283
|
|
|
1264
1284
|
// src/components/ChatPanel.tsx
|
|
1265
|
-
var
|
|
1285
|
+
var import_react22 = require("react");
|
|
1266
1286
|
|
|
1267
1287
|
// src/components/ChatHeader.tsx
|
|
1268
1288
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
@@ -1275,7 +1295,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle }
|
|
|
1275
1295
|
}
|
|
1276
1296
|
|
|
1277
1297
|
// src/components/MessageList.tsx
|
|
1278
|
-
var
|
|
1298
|
+
var import_react20 = require("react");
|
|
1279
1299
|
|
|
1280
1300
|
// src/components/Markdown.tsx
|
|
1281
1301
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -1293,7 +1313,7 @@ function Markdown({ text }) {
|
|
|
1293
1313
|
}
|
|
1294
1314
|
|
|
1295
1315
|
// src/components/EntityCardList.tsx
|
|
1296
|
-
var
|
|
1316
|
+
var import_react6 = require("react");
|
|
1297
1317
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1298
1318
|
var MAX_VISIBLE = 3;
|
|
1299
1319
|
var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
|
|
@@ -1377,7 +1397,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
|
|
|
1377
1397
|
)) });
|
|
1378
1398
|
}
|
|
1379
1399
|
function EntityCardItem({ c }) {
|
|
1380
|
-
const [active, setActive] = (0,
|
|
1400
|
+
const [active, setActive] = (0, import_react6.useState)(0);
|
|
1381
1401
|
const hasBody = c.scopes.length > 0;
|
|
1382
1402
|
if (!hasBody) {
|
|
1383
1403
|
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 }) });
|
|
@@ -1391,7 +1411,7 @@ function EntityCardItem({ c }) {
|
|
|
1391
1411
|
] });
|
|
1392
1412
|
}
|
|
1393
1413
|
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
1394
|
-
const [expanded, setExpanded] = (0,
|
|
1414
|
+
const [expanded, setExpanded] = (0, import_react6.useState)(false);
|
|
1395
1415
|
if (cards.length === 0) return null;
|
|
1396
1416
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
1397
1417
|
const overflow = cards.length - MAX_VISIBLE;
|
|
@@ -1402,18 +1422,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
1402
1422
|
}
|
|
1403
1423
|
|
|
1404
1424
|
// src/components/BettingInsightList.tsx
|
|
1405
|
-
var
|
|
1425
|
+
var import_react11 = require("react");
|
|
1406
1426
|
|
|
1407
1427
|
// src/components/AddToSlipButton.tsx
|
|
1408
|
-
var
|
|
1428
|
+
var import_react8 = require("react");
|
|
1409
1429
|
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
1410
1430
|
|
|
1411
1431
|
// src/components/Toast.tsx
|
|
1412
|
-
var
|
|
1432
|
+
var import_react7 = require("react");
|
|
1413
1433
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1414
1434
|
var DISMISS_MS = 4e3;
|
|
1415
1435
|
function Toast({ toast, onDismiss }) {
|
|
1416
|
-
(0,
|
|
1436
|
+
(0, import_react7.useEffect)(() => {
|
|
1417
1437
|
if (!toast) return;
|
|
1418
1438
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
1419
1439
|
return () => clearTimeout(timer);
|
|
@@ -1431,10 +1451,10 @@ function withTimeout(value) {
|
|
|
1431
1451
|
]);
|
|
1432
1452
|
}
|
|
1433
1453
|
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1434
|
-
const [pending, setPending] = (0,
|
|
1435
|
-
const [settled, setSettled] = (0,
|
|
1436
|
-
const [toast, setToast] = (0,
|
|
1437
|
-
const dismiss = (0,
|
|
1454
|
+
const [pending, setPending] = (0, import_react8.useState)(false);
|
|
1455
|
+
const [settled, setSettled] = (0, import_react8.useState)(false);
|
|
1456
|
+
const [toast, setToast] = (0, import_react8.useState)(null);
|
|
1457
|
+
const dismiss = (0, import_react8.useCallback)(() => setToast(null), []);
|
|
1438
1458
|
const handler = slip.onAddToSlip;
|
|
1439
1459
|
if (!ui || !handler || !selection) return null;
|
|
1440
1460
|
const onClick = async () => {
|
|
@@ -1498,14 +1518,14 @@ function OddsMovementBadge({
|
|
|
1498
1518
|
}
|
|
1499
1519
|
|
|
1500
1520
|
// src/components/Crest.tsx
|
|
1501
|
-
var
|
|
1521
|
+
var import_react9 = require("react");
|
|
1502
1522
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1503
1523
|
function initials2(name) {
|
|
1504
1524
|
return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
|
|
1505
1525
|
}
|
|
1506
1526
|
function Crest2({ url, name, kind }) {
|
|
1507
1527
|
const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
|
|
1508
|
-
const [failed, setFailed] = (0,
|
|
1528
|
+
const [failed, setFailed] = (0, import_react9.useState)(false);
|
|
1509
1529
|
if (url && !failed) {
|
|
1510
1530
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1511
1531
|
}
|
|
@@ -1513,11 +1533,16 @@ function Crest2({ url, name, kind }) {
|
|
|
1513
1533
|
}
|
|
1514
1534
|
|
|
1515
1535
|
// src/components/PinToggle.tsx
|
|
1516
|
-
var
|
|
1536
|
+
var import_react10 = require("react");
|
|
1517
1537
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1538
|
+
var PIN_GLYPHS = {
|
|
1539
|
+
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" },
|
|
1540
|
+
star: { viewBox: "0 0 24 24", strokeWidth: 2, d: "M12 2.5l2.9 5.9 6.5.95-4.7 4.58 1.1 6.47L12 17.35 6.1 20.4l1.1-6.47L2.5 9.35l6.5-.95z" },
|
|
1541
|
+
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" }
|
|
1542
|
+
};
|
|
1518
1543
|
function PinToggle({ pin, monitor, ui }) {
|
|
1519
|
-
const [toast, setToast] = (0,
|
|
1520
|
-
const dismiss = (0,
|
|
1544
|
+
const [toast, setToast] = (0, import_react10.useState)(null);
|
|
1545
|
+
const dismiss = (0, import_react10.useCallback)(() => setToast(null), []);
|
|
1521
1546
|
if (!monitor || !monitor.enabled) return null;
|
|
1522
1547
|
const key = pinKey(pin);
|
|
1523
1548
|
const pinned = monitor.isPinned(key);
|
|
@@ -1530,6 +1555,7 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1530
1555
|
setToast({ kind: "error", text: ui.marketLimitToast });
|
|
1531
1556
|
}
|
|
1532
1557
|
};
|
|
1558
|
+
const glyph = PIN_GLYPHS[ui.pinIcon];
|
|
1533
1559
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-pin-wrap", children: [
|
|
1534
1560
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1535
1561
|
"button",
|
|
@@ -1537,10 +1563,22 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1537
1563
|
type: "button",
|
|
1538
1564
|
className: "ss-w-pin",
|
|
1539
1565
|
"data-on": pinned || void 0,
|
|
1566
|
+
"data-icon": ui.pinIcon,
|
|
1540
1567
|
"aria-pressed": pinned,
|
|
1541
1568
|
"aria-label": pinned ? labels.unpin : labels.pin,
|
|
1542
1569
|
onClick,
|
|
1543
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { viewBox:
|
|
1570
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { viewBox: glyph.viewBox, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1571
|
+
"path",
|
|
1572
|
+
{
|
|
1573
|
+
transform: glyph.transform,
|
|
1574
|
+
d: glyph.d,
|
|
1575
|
+
fill: pinned ? "currentColor" : "none",
|
|
1576
|
+
stroke: pinned ? "none" : "currentColor",
|
|
1577
|
+
strokeWidth: glyph.strokeWidth,
|
|
1578
|
+
strokeLinejoin: "round",
|
|
1579
|
+
strokeLinecap: "round"
|
|
1580
|
+
}
|
|
1581
|
+
) })
|
|
1544
1582
|
}
|
|
1545
1583
|
),
|
|
1546
1584
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Toast, { toast, onDismiss: dismiss })
|
|
@@ -1556,7 +1594,7 @@ function StatRows({ stats }) {
|
|
|
1556
1594
|
] }, `${s.label}:${s.value}`)) });
|
|
1557
1595
|
}
|
|
1558
1596
|
function ScopedStats({ scopes }) {
|
|
1559
|
-
const [active, setActive] = (0,
|
|
1597
|
+
const [active, setActive] = (0, import_react11.useState)(0);
|
|
1560
1598
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
1561
1599
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
1562
1600
|
/* @__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)) }),
|
|
@@ -1567,7 +1605,7 @@ function hasStatsContent(it) {
|
|
|
1567
1605
|
return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
|
|
1568
1606
|
}
|
|
1569
1607
|
function ReasonStrip({ it, ui }) {
|
|
1570
|
-
const [open, setOpen] = (0,
|
|
1608
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
1571
1609
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1572
1610
|
const hasStats = hasStatsContent(it);
|
|
1573
1611
|
const hasText = !!it.text;
|
|
@@ -1677,7 +1715,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
1677
1715
|
}
|
|
1678
1716
|
|
|
1679
1717
|
// src/components/MarketOddsCardList.tsx
|
|
1680
|
-
var
|
|
1718
|
+
var import_react12 = require("react");
|
|
1681
1719
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1682
1720
|
function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
1683
1721
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -1694,7 +1732,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1694
1732
|
] });
|
|
1695
1733
|
}
|
|
1696
1734
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds }) {
|
|
1697
|
-
const [open, setOpen] = (0,
|
|
1735
|
+
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
1698
1736
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1699
1737
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
1700
1738
|
const showPin = !!monitor && vm.pinEnabled;
|
|
@@ -1987,7 +2025,7 @@ function FixtureCard({ card }) {
|
|
|
1987
2025
|
}
|
|
1988
2026
|
|
|
1989
2027
|
// src/components/dashboard/CountdownCard.tsx
|
|
1990
|
-
var
|
|
2028
|
+
var import_react13 = require("react");
|
|
1991
2029
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1992
2030
|
var DAY_MS = 864e5;
|
|
1993
2031
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -2006,12 +2044,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
2006
2044
|
function CountdownCard({ card }) {
|
|
2007
2045
|
const { data } = card;
|
|
2008
2046
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
2009
|
-
const [nowMs, setNowMs] = (0,
|
|
2010
|
-
const [reduced] = (0,
|
|
2047
|
+
const [nowMs, setNowMs] = (0, import_react13.useState)(() => Date.now());
|
|
2048
|
+
const [reduced] = (0, import_react13.useState)(
|
|
2011
2049
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
2012
2050
|
);
|
|
2013
2051
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
2014
|
-
(0,
|
|
2052
|
+
(0, import_react13.useEffect)(() => {
|
|
2015
2053
|
if (reduced) return;
|
|
2016
2054
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
2017
2055
|
return () => window.clearInterval(id);
|
|
@@ -2019,7 +2057,7 @@ function CountdownCard({ card }) {
|
|
|
2019
2057
|
const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
|
|
2020
2058
|
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: [
|
|
2021
2059
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title ?? "Kick-off in" }),
|
|
2022
|
-
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)(
|
|
2060
|
+
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: [
|
|
2023
2061
|
i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
|
|
2024
2062
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
|
|
2025
2063
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
|
|
@@ -2030,7 +2068,7 @@ function CountdownCard({ card }) {
|
|
|
2030
2068
|
}
|
|
2031
2069
|
|
|
2032
2070
|
// src/components/dashboard/InjuriesCard.tsx
|
|
2033
|
-
var
|
|
2071
|
+
var import_react14 = require("react");
|
|
2034
2072
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2035
2073
|
var SHOWN = 3;
|
|
2036
2074
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2053,7 +2091,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2053
2091
|
}
|
|
2054
2092
|
function InjuriesCard({ card, ui }) {
|
|
2055
2093
|
const { data } = card;
|
|
2056
|
-
const [expanded, setExpanded] = (0,
|
|
2094
|
+
const [expanded, setExpanded] = (0, import_react14.useState)(false);
|
|
2057
2095
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2058
2096
|
const limit = expanded ? void 0 : SHOWN;
|
|
2059
2097
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2157,7 +2195,7 @@ function LinksCard({ card }) {
|
|
|
2157
2195
|
}
|
|
2158
2196
|
|
|
2159
2197
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2160
|
-
var
|
|
2198
|
+
var import_react15 = require("react");
|
|
2161
2199
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
2162
2200
|
function initials3(name) {
|
|
2163
2201
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2167,7 +2205,7 @@ function PostMatchCrest({
|
|
|
2167
2205
|
name,
|
|
2168
2206
|
side
|
|
2169
2207
|
}) {
|
|
2170
|
-
const [failed, setFailed] = (0,
|
|
2208
|
+
const [failed, setFailed] = (0, import_react15.useState)(false);
|
|
2171
2209
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2172
2210
|
if (url && !failed) {
|
|
2173
2211
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2366,10 +2404,10 @@ function RestingFormCard({ card }) {
|
|
|
2366
2404
|
}
|
|
2367
2405
|
|
|
2368
2406
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2369
|
-
var
|
|
2407
|
+
var import_react17 = require("react");
|
|
2370
2408
|
|
|
2371
2409
|
// src/useLiveClock.ts
|
|
2372
|
-
var
|
|
2410
|
+
var import_react16 = require("react");
|
|
2373
2411
|
var TICK_MS = 6e4;
|
|
2374
2412
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2375
2413
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2378,10 +2416,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2378
2416
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2379
2417
|
}
|
|
2380
2418
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2381
|
-
const [displayMinute, setDisplayMinute] = (0,
|
|
2419
|
+
const [displayMinute, setDisplayMinute] = (0, import_react16.useState)(
|
|
2382
2420
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2383
2421
|
);
|
|
2384
|
-
(0,
|
|
2422
|
+
(0, import_react16.useEffect)(() => {
|
|
2385
2423
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2386
2424
|
if (!clockRunning) return;
|
|
2387
2425
|
const id = window.setInterval(() => {
|
|
@@ -2398,7 +2436,7 @@ function initials5(name) {
|
|
|
2398
2436
|
return name.trim().slice(0, 3).toUpperCase();
|
|
2399
2437
|
}
|
|
2400
2438
|
function LiveCrest({ url, name, side }) {
|
|
2401
|
-
const [failed, setFailed] = (0,
|
|
2439
|
+
const [failed, setFailed] = (0, import_react17.useState)(false);
|
|
2402
2440
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
2403
2441
|
if (url && !failed) {
|
|
2404
2442
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2417,7 +2455,7 @@ function LiveScoreCard({
|
|
|
2417
2455
|
}) {
|
|
2418
2456
|
const { data } = card;
|
|
2419
2457
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
2420
|
-
const seedRef = (0,
|
|
2458
|
+
const seedRef = (0, import_react17.useRef)(null);
|
|
2421
2459
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
2422
2460
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
2423
2461
|
}
|
|
@@ -2457,7 +2495,7 @@ function LiveScoreCard({
|
|
|
2457
2495
|
}
|
|
2458
2496
|
|
|
2459
2497
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
2460
|
-
var
|
|
2498
|
+
var import_react18 = require("react");
|
|
2461
2499
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2462
2500
|
var SHOWN2 = 3;
|
|
2463
2501
|
function EventIcon({ type }) {
|
|
@@ -2499,7 +2537,7 @@ function EventRow({ e }) {
|
|
|
2499
2537
|
] });
|
|
2500
2538
|
}
|
|
2501
2539
|
function LiveTimelineCard({ card, ui }) {
|
|
2502
|
-
const [expanded, setExpanded] = (0,
|
|
2540
|
+
const [expanded, setExpanded] = (0, import_react18.useState)(false);
|
|
2503
2541
|
const events = card.data.events;
|
|
2504
2542
|
if (!events || events.length === 0) return null;
|
|
2505
2543
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -2524,7 +2562,7 @@ function LiveTimelineCard({ card, ui }) {
|
|
|
2524
2562
|
}
|
|
2525
2563
|
|
|
2526
2564
|
// src/components/dashboard/MomentumWave.tsx
|
|
2527
|
-
var
|
|
2565
|
+
var import_react19 = require("react");
|
|
2528
2566
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2529
2567
|
var WIDTH = 270;
|
|
2530
2568
|
var HEIGHT = 56;
|
|
@@ -2548,7 +2586,7 @@ function MomentumWave({
|
|
|
2548
2586
|
}) {
|
|
2549
2587
|
if (points.length === 0) return null;
|
|
2550
2588
|
const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
|
|
2551
|
-
const clipId = (0,
|
|
2589
|
+
const clipId = (0, import_react19.useId)();
|
|
2552
2590
|
const topClipId = `${clipId}-top`;
|
|
2553
2591
|
const bottomClipId = `${clipId}-bottom`;
|
|
2554
2592
|
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
@@ -2739,11 +2777,11 @@ function turnAnchor(messages) {
|
|
|
2739
2777
|
}
|
|
2740
2778
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
2741
2779
|
const { state, send } = controller;
|
|
2742
|
-
const listRef = (0,
|
|
2743
|
-
const endRef = (0,
|
|
2744
|
-
const pinnedRef = (0,
|
|
2745
|
-
const anchoredTurnRef = (0,
|
|
2746
|
-
(0,
|
|
2780
|
+
const listRef = (0, import_react20.useRef)(null);
|
|
2781
|
+
const endRef = (0, import_react20.useRef)(null);
|
|
2782
|
+
const pinnedRef = (0, import_react20.useRef)(true);
|
|
2783
|
+
const anchoredTurnRef = (0, import_react20.useRef)(null);
|
|
2784
|
+
(0, import_react20.useEffect)(() => {
|
|
2747
2785
|
const anchor = turnAnchor(state.messages);
|
|
2748
2786
|
const list = listRef.current;
|
|
2749
2787
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -2799,10 +2837,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
2799
2837
|
}
|
|
2800
2838
|
|
|
2801
2839
|
// src/components/ChatInput.tsx
|
|
2802
|
-
var
|
|
2840
|
+
var import_react21 = require("react");
|
|
2803
2841
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
2804
2842
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
2805
|
-
const [value, setValue] = (0,
|
|
2843
|
+
const [value, setValue] = (0, import_react21.useState)("");
|
|
2806
2844
|
const submit = () => {
|
|
2807
2845
|
const t = value.trim();
|
|
2808
2846
|
if (!t) return;
|
|
@@ -3049,8 +3087,8 @@ function OddsPanel({ monitor, ui, slip }) {
|
|
|
3049
3087
|
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
3050
3088
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
3051
3089
|
function useIsModal() {
|
|
3052
|
-
const [modal, setModal] = (0,
|
|
3053
|
-
(0,
|
|
3090
|
+
const [modal, setModal] = (0, import_react22.useState)(false);
|
|
3091
|
+
(0, import_react22.useEffect)(() => {
|
|
3054
3092
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
3055
3093
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
3056
3094
|
const sync = () => setModal(mql.matches);
|
|
@@ -3063,7 +3101,7 @@ function useIsModal() {
|
|
|
3063
3101
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3064
3102
|
const { state, close, stop, send } = controller;
|
|
3065
3103
|
const isModal = useIsModal();
|
|
3066
|
-
(0,
|
|
3104
|
+
(0, import_react22.useEffect)(() => {
|
|
3067
3105
|
const onKey = (e) => {
|
|
3068
3106
|
if (e.key === "Escape") close();
|
|
3069
3107
|
};
|
|
@@ -3074,7 +3112,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3074
3112
|
monitor?.setActiveTab("chat");
|
|
3075
3113
|
send(text);
|
|
3076
3114
|
};
|
|
3077
|
-
(0,
|
|
3115
|
+
(0, import_react22.useEffect)(() => {
|
|
3078
3116
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3079
3117
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3080
3118
|
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
@@ -3145,8 +3183,40 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3145
3183
|
.ss-w-fab { width: 56px; height: 56px; border: none; border-radius: 50%; background: var(--ss-w-nav); color: #fff; cursor: pointer; display: grid; place-items: center; box-shadow: 0 6px 20px rgba(0,0,0,.28); }
|
|
3146
3184
|
.ss-w-fab-mark { width: 30px; height: 30px; display: block; object-fit: contain; }
|
|
3147
3185
|
.ss-w-greeting { max-width: 240px; display: flex; align-items: flex-start; background: var(--ss-w-panel); color: var(--ss-w-ink); border: 1px solid var(--ss-w-line); border-radius: 12px; box-shadow: 0 4px 14px rgba(0,0,0,.14); }
|
|
3148
|
-
.ss-w-greeting-body { flex: 1; min-width: 0; text-align: left; background: none; border: none; color: inherit; font: inherit; cursor: pointer; padding: 9px 4px 9px 12px; }
|
|
3186
|
+
.ss-w-greeting-body { position: relative; flex: 1; min-width: 0; text-align: left; background: none; border: none; color: inherit; font: inherit; cursor: pointer; padding: 9px 4px 9px 12px; }
|
|
3149
3187
|
.ss-w-greeting-x { flex: none; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 17px; line-height: 1; padding: 7px 9px 0 2px; }
|
|
3188
|
+
.ss-w-greeting-text { display: block; }
|
|
3189
|
+
/* Typing indicator: overlays the text area so bubble height stays put; hidden at
|
|
3190
|
+
rest, revealed only during the one-shot intro (see prefers-reduced-motion block). */
|
|
3191
|
+
.ss-w-greeting-dots { position: absolute; top: 0; bottom: 0; left: 12px; display: flex; align-items: center; gap: 4px; opacity: 0; pointer-events: none; }
|
|
3192
|
+
.ss-w-greeting-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--ss-w-faint); display: block; }
|
|
3193
|
+
/* One-shot launcher attention intro: FAB nudges, then the greeting types in.
|
|
3194
|
+
Gated behind no-preference so reduce-motion users get the greeting shown
|
|
3195
|
+
statically (text visible, no nudge, no typing). ChatFab adds .ss-w-intro once
|
|
3196
|
+
per page load; every animation runs a single time and settles at rest. */
|
|
3197
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
3198
|
+
.ss-w-intro .ss-w-fab { animation: ss-w-nudge .8s ease both; }
|
|
3199
|
+
.ss-w-intro .ss-w-greeting { animation: ss-w-greet-in .45s cubic-bezier(.2,.8,.3,1.15) .8s both; }
|
|
3200
|
+
.ss-w-intro .ss-w-greeting-text { animation: ss-w-greet-textin .4s ease 1.65s both; }
|
|
3201
|
+
.ss-w-intro .ss-w-greeting-dots { animation: ss-w-greet-dots .95s ease .85s both; }
|
|
3202
|
+
.ss-w-intro .ss-w-greeting-dots i { animation: ss-w-greet-dotbounce .5s ease .85s 2; }
|
|
3203
|
+
.ss-w-intro .ss-w-greeting-dots i:nth-child(2) { animation-delay: 1s; }
|
|
3204
|
+
.ss-w-intro .ss-w-greeting-dots i:nth-child(3) { animation-delay: 1.15s; }
|
|
3205
|
+
}
|
|
3206
|
+
@keyframes ss-w-nudge {
|
|
3207
|
+
0%, 100% { transform: none; }
|
|
3208
|
+
22% { transform: translateY(-9px) rotate(-9deg); }
|
|
3209
|
+
44% { transform: translateY(0) rotate(7deg); }
|
|
3210
|
+
64% { transform: translateY(-4px) rotate(-4deg); }
|
|
3211
|
+
82% { transform: rotate(2deg); }
|
|
3212
|
+
}
|
|
3213
|
+
@keyframes ss-w-greet-in {
|
|
3214
|
+
from { opacity: 0; transform: translateY(8px) scale(.92); }
|
|
3215
|
+
to { opacity: 1; transform: none; }
|
|
3216
|
+
}
|
|
3217
|
+
@keyframes ss-w-greet-textin { from { opacity: 0; } to { opacity: 1; } }
|
|
3218
|
+
@keyframes ss-w-greet-dots { 0% { opacity: 0; } 14% { opacity: 1; } 78% { opacity: 1; } 100% { opacity: 0; } }
|
|
3219
|
+
@keyframes ss-w-greet-dotbounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
|
|
3150
3220
|
|
|
3151
3221
|
/* panel */
|
|
3152
3222
|
.ss-w-panel { position: fixed; bottom: calc(var(--ss-w-offset-y) + 72px); z-index: 2147483000; display: flex; flex-direction: column; width: min(460px, calc(100vw - 40px)); height: min(640px, calc(100vh - 120px)); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: var(--ss-w-radius); overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,.24); }
|
|
@@ -3878,16 +3948,20 @@ function injectStyles(root) {
|
|
|
3878
3948
|
function StatsWidget(props) {
|
|
3879
3949
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
3880
3950
|
const cfg = props.config;
|
|
3881
|
-
const built = (0,
|
|
3882
|
-
const ui = (0,
|
|
3883
|
-
const rootRef = (0,
|
|
3951
|
+
const built = (0, import_react23.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
3952
|
+
const ui = (0, import_react23.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
3953
|
+
const rootRef = (0, import_react23.useRef)(null);
|
|
3954
|
+
const introDoneRef = (0, import_react23.useRef)(false);
|
|
3955
|
+
const markIntroDone = (0, import_react23.useCallback)(() => {
|
|
3956
|
+
introDoneRef.current = true;
|
|
3957
|
+
}, []);
|
|
3884
3958
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
3885
3959
|
const monitor = useLiveMonitor({
|
|
3886
3960
|
getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
|
|
3887
3961
|
storageKey: `ss-w-pins:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`,
|
|
3888
3962
|
isOpen: controller.state.isOpen
|
|
3889
3963
|
});
|
|
3890
|
-
const slip = (0,
|
|
3964
|
+
const slip = (0, import_react23.useMemo)(
|
|
3891
3965
|
() => {
|
|
3892
3966
|
const handler = props.config?.onAddToSlip;
|
|
3893
3967
|
return {
|
|
@@ -3910,11 +3984,11 @@ function StatsWidget(props) {
|
|
|
3910
3984
|
},
|
|
3911
3985
|
[props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
|
|
3912
3986
|
);
|
|
3913
|
-
const onDashboardTap = (0,
|
|
3987
|
+
const onDashboardTap = (0, import_react23.useMemo)(
|
|
3914
3988
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
3915
3989
|
[built]
|
|
3916
3990
|
);
|
|
3917
|
-
(0,
|
|
3991
|
+
(0, import_react23.useEffect)(() => {
|
|
3918
3992
|
if (!built || !ui.injectStyles) return;
|
|
3919
3993
|
const node = rootRef.current?.getRootNode();
|
|
3920
3994
|
if (node) injectStyles(node);
|
|
@@ -3930,7 +4004,9 @@ function StatsWidget(props) {
|
|
|
3930
4004
|
logoUrl: ui.logoUrl,
|
|
3931
4005
|
onOpen: controller.open,
|
|
3932
4006
|
onDismissPopup: controller.dismissPopup,
|
|
3933
|
-
dismissLabel: ui.dismissGreeting
|
|
4007
|
+
dismissLabel: ui.dismissGreeting,
|
|
4008
|
+
introAllowed: !introDoneRef.current,
|
|
4009
|
+
onIntroStart: markIntroDone
|
|
3934
4010
|
}
|
|
3935
4011
|
) });
|
|
3936
4012
|
}
|