@sensiblestats/widget-react 0.34.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 +18 -0
- package/README.md +112 -0
- package/dist/index.cjs +142 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +133 -60
- package/dist/index.js.map +1 -1
- package/dist/styles.css +33 -2
- 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,7 +207,7 @@ function normalizeUi(cfg) {
|
|
|
207
207
|
cta: cfg.cta,
|
|
208
208
|
surface: cfg.surface,
|
|
209
209
|
ink: cfg.ink,
|
|
210
|
-
|
|
210
|
+
pinIcon: cfg.pinIcon ?? "pin",
|
|
211
211
|
position: cfg.position ?? "right",
|
|
212
212
|
offset: { x: cfg.offset?.x ?? 20, y: cfg.offset?.y ?? 20 },
|
|
213
213
|
radius: cfg.radius ?? 16,
|
|
@@ -1195,6 +1195,9 @@ function useLiveMonitor(opts) {
|
|
|
1195
1195
|
};
|
|
1196
1196
|
}
|
|
1197
1197
|
|
|
1198
|
+
// src/components/ChatFab.tsx
|
|
1199
|
+
var import_react5 = require("react");
|
|
1200
|
+
|
|
1198
1201
|
// src/components/BrandMark.tsx
|
|
1199
1202
|
var import_react4 = require("react");
|
|
1200
1203
|
|
|
@@ -1247,23 +1250,39 @@ function BrandMark({ logoUrl, className }) {
|
|
|
1247
1250
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1248
1251
|
function GreetingBubble({ text, onOpen, onDismiss, dismissLabel = "Dismiss" }) {
|
|
1249
1252
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ss-w-greeting", role: "note", children: [
|
|
1250
|
-
/* @__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
|
+
] }),
|
|
1251
1261
|
onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null
|
|
1252
1262
|
] });
|
|
1253
1263
|
}
|
|
1254
1264
|
|
|
1255
1265
|
// src/components/ChatFab.tsx
|
|
1256
1266
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1257
|
-
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss" }) {
|
|
1267
|
+
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart }) {
|
|
1258
1268
|
const bubble = popupText ?? greeting;
|
|
1259
|
-
|
|
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: [
|
|
1260
1279
|
bubble ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel }) : null,
|
|
1261
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" }) })
|
|
1262
1281
|
] });
|
|
1263
1282
|
}
|
|
1264
1283
|
|
|
1265
1284
|
// src/components/ChatPanel.tsx
|
|
1266
|
-
var
|
|
1285
|
+
var import_react22 = require("react");
|
|
1267
1286
|
|
|
1268
1287
|
// src/components/ChatHeader.tsx
|
|
1269
1288
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
@@ -1276,7 +1295,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle }
|
|
|
1276
1295
|
}
|
|
1277
1296
|
|
|
1278
1297
|
// src/components/MessageList.tsx
|
|
1279
|
-
var
|
|
1298
|
+
var import_react20 = require("react");
|
|
1280
1299
|
|
|
1281
1300
|
// src/components/Markdown.tsx
|
|
1282
1301
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
@@ -1294,7 +1313,7 @@ function Markdown({ text }) {
|
|
|
1294
1313
|
}
|
|
1295
1314
|
|
|
1296
1315
|
// src/components/EntityCardList.tsx
|
|
1297
|
-
var
|
|
1316
|
+
var import_react6 = require("react");
|
|
1298
1317
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1299
1318
|
var MAX_VISIBLE = 3;
|
|
1300
1319
|
var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
|
|
@@ -1378,7 +1397,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
|
|
|
1378
1397
|
)) });
|
|
1379
1398
|
}
|
|
1380
1399
|
function EntityCardItem({ c }) {
|
|
1381
|
-
const [active, setActive] = (0,
|
|
1400
|
+
const [active, setActive] = (0, import_react6.useState)(0);
|
|
1382
1401
|
const hasBody = c.scopes.length > 0;
|
|
1383
1402
|
if (!hasBody) {
|
|
1384
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 }) });
|
|
@@ -1392,7 +1411,7 @@ function EntityCardItem({ c }) {
|
|
|
1392
1411
|
] });
|
|
1393
1412
|
}
|
|
1394
1413
|
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
1395
|
-
const [expanded, setExpanded] = (0,
|
|
1414
|
+
const [expanded, setExpanded] = (0, import_react6.useState)(false);
|
|
1396
1415
|
if (cards.length === 0) return null;
|
|
1397
1416
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
1398
1417
|
const overflow = cards.length - MAX_VISIBLE;
|
|
@@ -1403,18 +1422,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
1403
1422
|
}
|
|
1404
1423
|
|
|
1405
1424
|
// src/components/BettingInsightList.tsx
|
|
1406
|
-
var
|
|
1425
|
+
var import_react11 = require("react");
|
|
1407
1426
|
|
|
1408
1427
|
// src/components/AddToSlipButton.tsx
|
|
1409
|
-
var
|
|
1428
|
+
var import_react8 = require("react");
|
|
1410
1429
|
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
1411
1430
|
|
|
1412
1431
|
// src/components/Toast.tsx
|
|
1413
|
-
var
|
|
1432
|
+
var import_react7 = require("react");
|
|
1414
1433
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1415
1434
|
var DISMISS_MS = 4e3;
|
|
1416
1435
|
function Toast({ toast, onDismiss }) {
|
|
1417
|
-
(0,
|
|
1436
|
+
(0, import_react7.useEffect)(() => {
|
|
1418
1437
|
if (!toast) return;
|
|
1419
1438
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
1420
1439
|
return () => clearTimeout(timer);
|
|
@@ -1432,10 +1451,10 @@ function withTimeout(value) {
|
|
|
1432
1451
|
]);
|
|
1433
1452
|
}
|
|
1434
1453
|
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1435
|
-
const [pending, setPending] = (0,
|
|
1436
|
-
const [settled, setSettled] = (0,
|
|
1437
|
-
const [toast, setToast] = (0,
|
|
1438
|
-
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), []);
|
|
1439
1458
|
const handler = slip.onAddToSlip;
|
|
1440
1459
|
if (!ui || !handler || !selection) return null;
|
|
1441
1460
|
const onClick = async () => {
|
|
@@ -1499,14 +1518,14 @@ function OddsMovementBadge({
|
|
|
1499
1518
|
}
|
|
1500
1519
|
|
|
1501
1520
|
// src/components/Crest.tsx
|
|
1502
|
-
var
|
|
1521
|
+
var import_react9 = require("react");
|
|
1503
1522
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1504
1523
|
function initials2(name) {
|
|
1505
1524
|
return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
|
|
1506
1525
|
}
|
|
1507
1526
|
function Crest2({ url, name, kind }) {
|
|
1508
1527
|
const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
|
|
1509
|
-
const [failed, setFailed] = (0,
|
|
1528
|
+
const [failed, setFailed] = (0, import_react9.useState)(false);
|
|
1510
1529
|
if (url && !failed) {
|
|
1511
1530
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1512
1531
|
}
|
|
@@ -1514,11 +1533,16 @@ function Crest2({ url, name, kind }) {
|
|
|
1514
1533
|
}
|
|
1515
1534
|
|
|
1516
1535
|
// src/components/PinToggle.tsx
|
|
1517
|
-
var
|
|
1536
|
+
var import_react10 = require("react");
|
|
1518
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
|
+
};
|
|
1519
1543
|
function PinToggle({ pin, monitor, ui }) {
|
|
1520
|
-
const [toast, setToast] = (0,
|
|
1521
|
-
const dismiss = (0,
|
|
1544
|
+
const [toast, setToast] = (0, import_react10.useState)(null);
|
|
1545
|
+
const dismiss = (0, import_react10.useCallback)(() => setToast(null), []);
|
|
1522
1546
|
if (!monitor || !monitor.enabled) return null;
|
|
1523
1547
|
const key = pinKey(pin);
|
|
1524
1548
|
const pinned = monitor.isPinned(key);
|
|
@@ -1531,6 +1555,7 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1531
1555
|
setToast({ kind: "error", text: ui.marketLimitToast });
|
|
1532
1556
|
}
|
|
1533
1557
|
};
|
|
1558
|
+
const glyph = PIN_GLYPHS[ui.pinIcon];
|
|
1534
1559
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ss-w-pin-wrap", children: [
|
|
1535
1560
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1536
1561
|
"button",
|
|
@@ -1538,10 +1563,22 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1538
1563
|
type: "button",
|
|
1539
1564
|
className: "ss-w-pin",
|
|
1540
1565
|
"data-on": pinned || void 0,
|
|
1566
|
+
"data-icon": ui.pinIcon,
|
|
1541
1567
|
"aria-pressed": pinned,
|
|
1542
1568
|
"aria-label": pinned ? labels.unpin : labels.pin,
|
|
1543
1569
|
onClick,
|
|
1544
|
-
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
|
+
) })
|
|
1545
1582
|
}
|
|
1546
1583
|
),
|
|
1547
1584
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Toast, { toast, onDismiss: dismiss })
|
|
@@ -1557,7 +1594,7 @@ function StatRows({ stats }) {
|
|
|
1557
1594
|
] }, `${s.label}:${s.value}`)) });
|
|
1558
1595
|
}
|
|
1559
1596
|
function ScopedStats({ scopes }) {
|
|
1560
|
-
const [active, setActive] = (0,
|
|
1597
|
+
const [active, setActive] = (0, import_react11.useState)(0);
|
|
1561
1598
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
1562
1599
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-bi-scoped", children: [
|
|
1563
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)) }),
|
|
@@ -1568,7 +1605,7 @@ function hasStatsContent(it) {
|
|
|
1568
1605
|
return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
|
|
1569
1606
|
}
|
|
1570
1607
|
function ReasonStrip({ it, ui }) {
|
|
1571
|
-
const [open, setOpen] = (0,
|
|
1608
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
1572
1609
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1573
1610
|
const hasStats = hasStatsContent(it);
|
|
1574
1611
|
const hasText = !!it.text;
|
|
@@ -1678,7 +1715,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
1678
1715
|
}
|
|
1679
1716
|
|
|
1680
1717
|
// src/components/MarketOddsCardList.tsx
|
|
1681
|
-
var
|
|
1718
|
+
var import_react12 = require("react");
|
|
1682
1719
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1683
1720
|
function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
1684
1721
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -1695,7 +1732,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1695
1732
|
] });
|
|
1696
1733
|
}
|
|
1697
1734
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds }) {
|
|
1698
|
-
const [open, setOpen] = (0,
|
|
1735
|
+
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
1699
1736
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1700
1737
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
1701
1738
|
const showPin = !!monitor && vm.pinEnabled;
|
|
@@ -1988,7 +2025,7 @@ function FixtureCard({ card }) {
|
|
|
1988
2025
|
}
|
|
1989
2026
|
|
|
1990
2027
|
// src/components/dashboard/CountdownCard.tsx
|
|
1991
|
-
var
|
|
2028
|
+
var import_react13 = require("react");
|
|
1992
2029
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1993
2030
|
var DAY_MS = 864e5;
|
|
1994
2031
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -2007,12 +2044,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
2007
2044
|
function CountdownCard({ card }) {
|
|
2008
2045
|
const { data } = card;
|
|
2009
2046
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
2010
|
-
const [nowMs, setNowMs] = (0,
|
|
2011
|
-
const [reduced] = (0,
|
|
2047
|
+
const [nowMs, setNowMs] = (0, import_react13.useState)(() => Date.now());
|
|
2048
|
+
const [reduced] = (0, import_react13.useState)(
|
|
2012
2049
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
2013
2050
|
);
|
|
2014
2051
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
2015
|
-
(0,
|
|
2052
|
+
(0, import_react13.useEffect)(() => {
|
|
2016
2053
|
if (reduced) return;
|
|
2017
2054
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
2018
2055
|
return () => window.clearInterval(id);
|
|
@@ -2020,7 +2057,7 @@ function CountdownCard({ card }) {
|
|
|
2020
2057
|
const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
|
|
2021
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: [
|
|
2022
2059
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title ?? "Kick-off in" }),
|
|
2023
|
-
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: [
|
|
2024
2061
|
i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
|
|
2025
2062
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
|
|
2026
2063
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
|
|
@@ -2031,7 +2068,7 @@ function CountdownCard({ card }) {
|
|
|
2031
2068
|
}
|
|
2032
2069
|
|
|
2033
2070
|
// src/components/dashboard/InjuriesCard.tsx
|
|
2034
|
-
var
|
|
2071
|
+
var import_react14 = require("react");
|
|
2035
2072
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2036
2073
|
var SHOWN = 3;
|
|
2037
2074
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2054,7 +2091,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2054
2091
|
}
|
|
2055
2092
|
function InjuriesCard({ card, ui }) {
|
|
2056
2093
|
const { data } = card;
|
|
2057
|
-
const [expanded, setExpanded] = (0,
|
|
2094
|
+
const [expanded, setExpanded] = (0, import_react14.useState)(false);
|
|
2058
2095
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2059
2096
|
const limit = expanded ? void 0 : SHOWN;
|
|
2060
2097
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2158,7 +2195,7 @@ function LinksCard({ card }) {
|
|
|
2158
2195
|
}
|
|
2159
2196
|
|
|
2160
2197
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2161
|
-
var
|
|
2198
|
+
var import_react15 = require("react");
|
|
2162
2199
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
2163
2200
|
function initials3(name) {
|
|
2164
2201
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2168,7 +2205,7 @@ function PostMatchCrest({
|
|
|
2168
2205
|
name,
|
|
2169
2206
|
side
|
|
2170
2207
|
}) {
|
|
2171
|
-
const [failed, setFailed] = (0,
|
|
2208
|
+
const [failed, setFailed] = (0, import_react15.useState)(false);
|
|
2172
2209
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2173
2210
|
if (url && !failed) {
|
|
2174
2211
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2367,10 +2404,10 @@ function RestingFormCard({ card }) {
|
|
|
2367
2404
|
}
|
|
2368
2405
|
|
|
2369
2406
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2370
|
-
var
|
|
2407
|
+
var import_react17 = require("react");
|
|
2371
2408
|
|
|
2372
2409
|
// src/useLiveClock.ts
|
|
2373
|
-
var
|
|
2410
|
+
var import_react16 = require("react");
|
|
2374
2411
|
var TICK_MS = 6e4;
|
|
2375
2412
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2376
2413
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2379,10 +2416,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2379
2416
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2380
2417
|
}
|
|
2381
2418
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2382
|
-
const [displayMinute, setDisplayMinute] = (0,
|
|
2419
|
+
const [displayMinute, setDisplayMinute] = (0, import_react16.useState)(
|
|
2383
2420
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2384
2421
|
);
|
|
2385
|
-
(0,
|
|
2422
|
+
(0, import_react16.useEffect)(() => {
|
|
2386
2423
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2387
2424
|
if (!clockRunning) return;
|
|
2388
2425
|
const id = window.setInterval(() => {
|
|
@@ -2399,7 +2436,7 @@ function initials5(name) {
|
|
|
2399
2436
|
return name.trim().slice(0, 3).toUpperCase();
|
|
2400
2437
|
}
|
|
2401
2438
|
function LiveCrest({ url, name, side }) {
|
|
2402
|
-
const [failed, setFailed] = (0,
|
|
2439
|
+
const [failed, setFailed] = (0, import_react17.useState)(false);
|
|
2403
2440
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
2404
2441
|
if (url && !failed) {
|
|
2405
2442
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2418,7 +2455,7 @@ function LiveScoreCard({
|
|
|
2418
2455
|
}) {
|
|
2419
2456
|
const { data } = card;
|
|
2420
2457
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
2421
|
-
const seedRef = (0,
|
|
2458
|
+
const seedRef = (0, import_react17.useRef)(null);
|
|
2422
2459
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
2423
2460
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
2424
2461
|
}
|
|
@@ -2458,7 +2495,7 @@ function LiveScoreCard({
|
|
|
2458
2495
|
}
|
|
2459
2496
|
|
|
2460
2497
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
2461
|
-
var
|
|
2498
|
+
var import_react18 = require("react");
|
|
2462
2499
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2463
2500
|
var SHOWN2 = 3;
|
|
2464
2501
|
function EventIcon({ type }) {
|
|
@@ -2500,7 +2537,7 @@ function EventRow({ e }) {
|
|
|
2500
2537
|
] });
|
|
2501
2538
|
}
|
|
2502
2539
|
function LiveTimelineCard({ card, ui }) {
|
|
2503
|
-
const [expanded, setExpanded] = (0,
|
|
2540
|
+
const [expanded, setExpanded] = (0, import_react18.useState)(false);
|
|
2504
2541
|
const events = card.data.events;
|
|
2505
2542
|
if (!events || events.length === 0) return null;
|
|
2506
2543
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -2525,7 +2562,7 @@ function LiveTimelineCard({ card, ui }) {
|
|
|
2525
2562
|
}
|
|
2526
2563
|
|
|
2527
2564
|
// src/components/dashboard/MomentumWave.tsx
|
|
2528
|
-
var
|
|
2565
|
+
var import_react19 = require("react");
|
|
2529
2566
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2530
2567
|
var WIDTH = 270;
|
|
2531
2568
|
var HEIGHT = 56;
|
|
@@ -2549,7 +2586,7 @@ function MomentumWave({
|
|
|
2549
2586
|
}) {
|
|
2550
2587
|
if (points.length === 0) return null;
|
|
2551
2588
|
const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
|
|
2552
|
-
const clipId = (0,
|
|
2589
|
+
const clipId = (0, import_react19.useId)();
|
|
2553
2590
|
const topClipId = `${clipId}-top`;
|
|
2554
2591
|
const bottomClipId = `${clipId}-bottom`;
|
|
2555
2592
|
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
@@ -2740,11 +2777,11 @@ function turnAnchor(messages) {
|
|
|
2740
2777
|
}
|
|
2741
2778
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
2742
2779
|
const { state, send } = controller;
|
|
2743
|
-
const listRef = (0,
|
|
2744
|
-
const endRef = (0,
|
|
2745
|
-
const pinnedRef = (0,
|
|
2746
|
-
const anchoredTurnRef = (0,
|
|
2747
|
-
(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)(() => {
|
|
2748
2785
|
const anchor = turnAnchor(state.messages);
|
|
2749
2786
|
const list = listRef.current;
|
|
2750
2787
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -2800,10 +2837,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
2800
2837
|
}
|
|
2801
2838
|
|
|
2802
2839
|
// src/components/ChatInput.tsx
|
|
2803
|
-
var
|
|
2840
|
+
var import_react21 = require("react");
|
|
2804
2841
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
2805
2842
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
2806
|
-
const [value, setValue] = (0,
|
|
2843
|
+
const [value, setValue] = (0, import_react21.useState)("");
|
|
2807
2844
|
const submit = () => {
|
|
2808
2845
|
const t = value.trim();
|
|
2809
2846
|
if (!t) return;
|
|
@@ -3050,8 +3087,8 @@ function OddsPanel({ monitor, ui, slip }) {
|
|
|
3050
3087
|
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
3051
3088
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
3052
3089
|
function useIsModal() {
|
|
3053
|
-
const [modal, setModal] = (0,
|
|
3054
|
-
(0,
|
|
3090
|
+
const [modal, setModal] = (0, import_react22.useState)(false);
|
|
3091
|
+
(0, import_react22.useEffect)(() => {
|
|
3055
3092
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
3056
3093
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
3057
3094
|
const sync = () => setModal(mql.matches);
|
|
@@ -3064,7 +3101,7 @@ function useIsModal() {
|
|
|
3064
3101
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3065
3102
|
const { state, close, stop, send } = controller;
|
|
3066
3103
|
const isModal = useIsModal();
|
|
3067
|
-
(0,
|
|
3104
|
+
(0, import_react22.useEffect)(() => {
|
|
3068
3105
|
const onKey = (e) => {
|
|
3069
3106
|
if (e.key === "Escape") close();
|
|
3070
3107
|
};
|
|
@@ -3075,7 +3112,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3075
3112
|
monitor?.setActiveTab("chat");
|
|
3076
3113
|
send(text);
|
|
3077
3114
|
};
|
|
3078
|
-
(0,
|
|
3115
|
+
(0, import_react22.useEffect)(() => {
|
|
3079
3116
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3080
3117
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3081
3118
|
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
@@ -3098,8 +3135,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3098
3135
|
),
|
|
3099
3136
|
monitor && monitor.activeTab === "matches" && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(MatchesPanel, { monitor, ui, slip }),
|
|
3100
3137
|
monitor && monitor.activeTab === "odds" && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(OddsPanel, { monitor, ui, slip }),
|
|
3101
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: handleSend, onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
3102
|
-
ui.attribution !== false ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "ss-w-attribution", children: "Powered by SensibleStats" }) : null
|
|
3138
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: handleSend, onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
3103
3139
|
] });
|
|
3104
3140
|
}
|
|
3105
3141
|
|
|
@@ -3147,8 +3183,40 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3147
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); }
|
|
3148
3184
|
.ss-w-fab-mark { width: 30px; height: 30px; display: block; object-fit: contain; }
|
|
3149
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); }
|
|
3150
|
-
.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; }
|
|
3151
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); } }
|
|
3152
3220
|
|
|
3153
3221
|
/* panel */
|
|
3154
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); }
|
|
@@ -3387,7 +3455,6 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3387
3455
|
}
|
|
3388
3456
|
.ss-w-send { width: 36px; height: 36px; flex: none; border: none; border-radius: 50%; background: var(--ss-w-accent); color: #fff; cursor: pointer; display: grid; place-items: center; font-size: 16px; }
|
|
3389
3457
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
3390
|
-
.ss-w-attribution { flex: 0 0 auto; padding: 6px 12px 8px; text-align: center; font-size: 11px; color: var(--ss-w-faint); }
|
|
3391
3458
|
|
|
3392
3459
|
/* add to slip */
|
|
3393
3460
|
.ss-w-slip { position: relative; display: flex; }
|
|
@@ -3881,16 +3948,20 @@ function injectStyles(root) {
|
|
|
3881
3948
|
function StatsWidget(props) {
|
|
3882
3949
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
3883
3950
|
const cfg = props.config;
|
|
3884
|
-
const built = (0,
|
|
3885
|
-
const ui = (0,
|
|
3886
|
-
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
|
+
}, []);
|
|
3887
3958
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
3888
3959
|
const monitor = useLiveMonitor({
|
|
3889
3960
|
getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
|
|
3890
3961
|
storageKey: `ss-w-pins:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`,
|
|
3891
3962
|
isOpen: controller.state.isOpen
|
|
3892
3963
|
});
|
|
3893
|
-
const slip = (0,
|
|
3964
|
+
const slip = (0, import_react23.useMemo)(
|
|
3894
3965
|
() => {
|
|
3895
3966
|
const handler = props.config?.onAddToSlip;
|
|
3896
3967
|
return {
|
|
@@ -3913,11 +3984,11 @@ function StatsWidget(props) {
|
|
|
3913
3984
|
},
|
|
3914
3985
|
[props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
|
|
3915
3986
|
);
|
|
3916
|
-
const onDashboardTap = (0,
|
|
3987
|
+
const onDashboardTap = (0, import_react23.useMemo)(
|
|
3917
3988
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
3918
3989
|
[built]
|
|
3919
3990
|
);
|
|
3920
|
-
(0,
|
|
3991
|
+
(0, import_react23.useEffect)(() => {
|
|
3921
3992
|
if (!built || !ui.injectStyles) return;
|
|
3922
3993
|
const node = rootRef.current?.getRootNode();
|
|
3923
3994
|
if (node) injectStyles(node);
|
|
@@ -3933,7 +4004,9 @@ function StatsWidget(props) {
|
|
|
3933
4004
|
logoUrl: ui.logoUrl,
|
|
3934
4005
|
onOpen: controller.open,
|
|
3935
4006
|
onDismissPopup: controller.dismissPopup,
|
|
3936
|
-
dismissLabel: ui.dismissGreeting
|
|
4007
|
+
dismissLabel: ui.dismissGreeting,
|
|
4008
|
+
introAllowed: !introDoneRef.current,
|
|
4009
|
+
onIntroStart: markIntroDone
|
|
3937
4010
|
}
|
|
3938
4011
|
) });
|
|
3939
4012
|
}
|