@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/StatsWidget.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useCallback as useCallback5, useEffect as useEffect10, useMemo as useMemo2, useRef as useRef7 } from "react";
|
|
3
3
|
import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
|
|
4
4
|
|
|
5
5
|
// src/kickoff.ts
|
|
@@ -170,7 +170,7 @@ function normalizeUi(cfg) {
|
|
|
170
170
|
cta: cfg.cta,
|
|
171
171
|
surface: cfg.surface,
|
|
172
172
|
ink: cfg.ink,
|
|
173
|
-
|
|
173
|
+
pinIcon: cfg.pinIcon ?? "pin",
|
|
174
174
|
position: cfg.position ?? "right",
|
|
175
175
|
offset: { x: cfg.offset?.x ?? 20, y: cfg.offset?.y ?? 20 },
|
|
176
176
|
radius: cfg.radius ?? 16,
|
|
@@ -1158,6 +1158,9 @@ function useLiveMonitor(opts) {
|
|
|
1158
1158
|
};
|
|
1159
1159
|
}
|
|
1160
1160
|
|
|
1161
|
+
// src/components/ChatFab.tsx
|
|
1162
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
|
|
1163
|
+
|
|
1161
1164
|
// src/components/BrandMark.tsx
|
|
1162
1165
|
import { useState as useState2 } from "react";
|
|
1163
1166
|
|
|
@@ -1210,23 +1213,39 @@ function BrandMark({ logoUrl, className }) {
|
|
|
1210
1213
|
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1211
1214
|
function GreetingBubble({ text, onOpen, onDismiss, dismissLabel = "Dismiss" }) {
|
|
1212
1215
|
return /* @__PURE__ */ jsxs("div", { className: "ss-w-greeting", role: "note", children: [
|
|
1213
|
-
/* @__PURE__ */
|
|
1216
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: "ss-w-greeting-body", onClick: onOpen, children: [
|
|
1217
|
+
/* @__PURE__ */ jsxs("span", { className: "ss-w-greeting-dots", "aria-hidden": "true", children: [
|
|
1218
|
+
/* @__PURE__ */ jsx3("i", {}),
|
|
1219
|
+
/* @__PURE__ */ jsx3("i", {}),
|
|
1220
|
+
/* @__PURE__ */ jsx3("i", {})
|
|
1221
|
+
] }),
|
|
1222
|
+
/* @__PURE__ */ jsx3("span", { className: "ss-w-greeting-text", children: text })
|
|
1223
|
+
] }),
|
|
1214
1224
|
onDismiss ? /* @__PURE__ */ jsx3("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null
|
|
1215
1225
|
] });
|
|
1216
1226
|
}
|
|
1217
1227
|
|
|
1218
1228
|
// src/components/ChatFab.tsx
|
|
1219
1229
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1220
|
-
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss" }) {
|
|
1230
|
+
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart }) {
|
|
1221
1231
|
const bubble = popupText ?? greeting;
|
|
1222
|
-
|
|
1232
|
+
const [intro, setIntro] = useState3(false);
|
|
1233
|
+
const armedRef = useRef4(introAllowed);
|
|
1234
|
+
useEffect4(() => {
|
|
1235
|
+
if (armedRef.current && bubble) {
|
|
1236
|
+
armedRef.current = false;
|
|
1237
|
+
setIntro(true);
|
|
1238
|
+
onIntroStart?.();
|
|
1239
|
+
}
|
|
1240
|
+
}, [bubble, onIntroStart]);
|
|
1241
|
+
return /* @__PURE__ */ jsxs2("div", { className: intro ? "ss-w-fab-wrap ss-w-intro" : "ss-w-fab-wrap", children: [
|
|
1223
1242
|
bubble ? /* @__PURE__ */ jsx4(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel }) : null,
|
|
1224
1243
|
/* @__PURE__ */ jsx4("button", { type: "button", className: "ss-w-fab", "aria-label": label, onClick: onOpen, children: /* @__PURE__ */ jsx4(BrandMark, { logoUrl, className: "ss-w-fab-mark" }) })
|
|
1225
1244
|
] });
|
|
1226
1245
|
}
|
|
1227
1246
|
|
|
1228
1247
|
// src/components/ChatPanel.tsx
|
|
1229
|
-
import { useEffect as
|
|
1248
|
+
import { useEffect as useEffect9, useState as useState17 } from "react";
|
|
1230
1249
|
|
|
1231
1250
|
// src/components/ChatHeader.tsx
|
|
1232
1251
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -1239,7 +1258,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle }
|
|
|
1239
1258
|
}
|
|
1240
1259
|
|
|
1241
1260
|
// src/components/MessageList.tsx
|
|
1242
|
-
import { useEffect as
|
|
1261
|
+
import { useEffect as useEffect8, useRef as useRef6 } from "react";
|
|
1243
1262
|
|
|
1244
1263
|
// src/components/Markdown.tsx
|
|
1245
1264
|
import ReactMarkdown from "react-markdown";
|
|
@@ -1257,7 +1276,7 @@ function Markdown({ text }) {
|
|
|
1257
1276
|
}
|
|
1258
1277
|
|
|
1259
1278
|
// src/components/EntityCardList.tsx
|
|
1260
|
-
import { useState as
|
|
1279
|
+
import { useState as useState4 } from "react";
|
|
1261
1280
|
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1262
1281
|
var MAX_VISIBLE = 3;
|
|
1263
1282
|
var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
|
|
@@ -1341,7 +1360,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
|
|
|
1341
1360
|
)) });
|
|
1342
1361
|
}
|
|
1343
1362
|
function EntityCardItem({ c }) {
|
|
1344
|
-
const [active, setActive] =
|
|
1363
|
+
const [active, setActive] = useState4(0);
|
|
1345
1364
|
const hasBody = c.scopes.length > 0;
|
|
1346
1365
|
if (!hasBody) {
|
|
1347
1366
|
return /* @__PURE__ */ jsx7("div", { className: "ss-w-ecard ss-w-ecard-bare", "data-kind": c.kind, children: /* @__PURE__ */ jsx7(CardHeader, { c, showScopeCrest: false }) });
|
|
@@ -1355,7 +1374,7 @@ function EntityCardItem({ c }) {
|
|
|
1355
1374
|
] });
|
|
1356
1375
|
}
|
|
1357
1376
|
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
1358
|
-
const [expanded, setExpanded] =
|
|
1377
|
+
const [expanded, setExpanded] = useState4(false);
|
|
1359
1378
|
if (cards.length === 0) return null;
|
|
1360
1379
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
1361
1380
|
const overflow = cards.length - MAX_VISIBLE;
|
|
@@ -1366,18 +1385,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
1366
1385
|
}
|
|
1367
1386
|
|
|
1368
1387
|
// src/components/BettingInsightList.tsx
|
|
1369
|
-
import { useState as
|
|
1388
|
+
import { useState as useState8 } from "react";
|
|
1370
1389
|
|
|
1371
1390
|
// src/components/AddToSlipButton.tsx
|
|
1372
|
-
import { useCallback as useCallback3, useState as
|
|
1391
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
1373
1392
|
import { isAddToSlipResult } from "@sensiblestats/widget-sdk";
|
|
1374
1393
|
|
|
1375
1394
|
// src/components/Toast.tsx
|
|
1376
|
-
import { useEffect as
|
|
1395
|
+
import { useEffect as useEffect5 } from "react";
|
|
1377
1396
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1378
1397
|
var DISMISS_MS = 4e3;
|
|
1379
1398
|
function Toast({ toast, onDismiss }) {
|
|
1380
|
-
|
|
1399
|
+
useEffect5(() => {
|
|
1381
1400
|
if (!toast) return;
|
|
1382
1401
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
1383
1402
|
return () => clearTimeout(timer);
|
|
@@ -1395,9 +1414,9 @@ function withTimeout(value) {
|
|
|
1395
1414
|
]);
|
|
1396
1415
|
}
|
|
1397
1416
|
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1398
|
-
const [pending, setPending] =
|
|
1399
|
-
const [settled, setSettled] =
|
|
1400
|
-
const [toast, setToast] =
|
|
1417
|
+
const [pending, setPending] = useState5(false);
|
|
1418
|
+
const [settled, setSettled] = useState5(false);
|
|
1419
|
+
const [toast, setToast] = useState5(null);
|
|
1401
1420
|
const dismiss = useCallback3(() => setToast(null), []);
|
|
1402
1421
|
const handler = slip.onAddToSlip;
|
|
1403
1422
|
if (!ui || !handler || !selection) return null;
|
|
@@ -1462,14 +1481,14 @@ function OddsMovementBadge({
|
|
|
1462
1481
|
}
|
|
1463
1482
|
|
|
1464
1483
|
// src/components/Crest.tsx
|
|
1465
|
-
import { useState as
|
|
1484
|
+
import { useState as useState6 } from "react";
|
|
1466
1485
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1467
1486
|
function initials2(name) {
|
|
1468
1487
|
return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
|
|
1469
1488
|
}
|
|
1470
1489
|
function Crest2({ url, name, kind }) {
|
|
1471
1490
|
const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
|
|
1472
|
-
const [failed, setFailed] =
|
|
1491
|
+
const [failed, setFailed] = useState6(false);
|
|
1473
1492
|
if (url && !failed) {
|
|
1474
1493
|
return /* @__PURE__ */ jsx11("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1475
1494
|
}
|
|
@@ -1477,10 +1496,15 @@ function Crest2({ url, name, kind }) {
|
|
|
1477
1496
|
}
|
|
1478
1497
|
|
|
1479
1498
|
// src/components/PinToggle.tsx
|
|
1480
|
-
import { useCallback as useCallback4, useState as
|
|
1499
|
+
import { useCallback as useCallback4, useState as useState7 } from "react";
|
|
1481
1500
|
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1501
|
+
var PIN_GLYPHS = {
|
|
1502
|
+
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" },
|
|
1503
|
+
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" },
|
|
1504
|
+
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" }
|
|
1505
|
+
};
|
|
1482
1506
|
function PinToggle({ pin, monitor, ui }) {
|
|
1483
|
-
const [toast, setToast] =
|
|
1507
|
+
const [toast, setToast] = useState7(null);
|
|
1484
1508
|
const dismiss = useCallback4(() => setToast(null), []);
|
|
1485
1509
|
if (!monitor || !monitor.enabled) return null;
|
|
1486
1510
|
const key = pinKey(pin);
|
|
@@ -1494,6 +1518,7 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1494
1518
|
setToast({ kind: "error", text: ui.marketLimitToast });
|
|
1495
1519
|
}
|
|
1496
1520
|
};
|
|
1521
|
+
const glyph = PIN_GLYPHS[ui.pinIcon];
|
|
1497
1522
|
return /* @__PURE__ */ jsxs7("div", { className: "ss-w-pin-wrap", children: [
|
|
1498
1523
|
/* @__PURE__ */ jsx12(
|
|
1499
1524
|
"button",
|
|
@@ -1501,10 +1526,22 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1501
1526
|
type: "button",
|
|
1502
1527
|
className: "ss-w-pin",
|
|
1503
1528
|
"data-on": pinned || void 0,
|
|
1529
|
+
"data-icon": ui.pinIcon,
|
|
1504
1530
|
"aria-pressed": pinned,
|
|
1505
1531
|
"aria-label": pinned ? labels.unpin : labels.pin,
|
|
1506
1532
|
onClick,
|
|
1507
|
-
children: /* @__PURE__ */ jsx12("svg", { viewBox:
|
|
1533
|
+
children: /* @__PURE__ */ jsx12("svg", { viewBox: glyph.viewBox, "aria-hidden": "true", children: /* @__PURE__ */ jsx12(
|
|
1534
|
+
"path",
|
|
1535
|
+
{
|
|
1536
|
+
transform: glyph.transform,
|
|
1537
|
+
d: glyph.d,
|
|
1538
|
+
fill: pinned ? "currentColor" : "none",
|
|
1539
|
+
stroke: pinned ? "none" : "currentColor",
|
|
1540
|
+
strokeWidth: glyph.strokeWidth,
|
|
1541
|
+
strokeLinejoin: "round",
|
|
1542
|
+
strokeLinecap: "round"
|
|
1543
|
+
}
|
|
1544
|
+
) })
|
|
1508
1545
|
}
|
|
1509
1546
|
),
|
|
1510
1547
|
/* @__PURE__ */ jsx12(Toast, { toast, onDismiss: dismiss })
|
|
@@ -1520,7 +1557,7 @@ function StatRows({ stats }) {
|
|
|
1520
1557
|
] }, `${s.label}:${s.value}`)) });
|
|
1521
1558
|
}
|
|
1522
1559
|
function ScopedStats({ scopes }) {
|
|
1523
|
-
const [active, setActive] =
|
|
1560
|
+
const [active, setActive] = useState8(0);
|
|
1524
1561
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
1525
1562
|
return /* @__PURE__ */ jsxs8("div", { className: "ss-w-bi-scoped", children: [
|
|
1526
1563
|
/* @__PURE__ */ jsx13("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ jsx13("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
|
|
@@ -1531,7 +1568,7 @@ function hasStatsContent(it) {
|
|
|
1531
1568
|
return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
|
|
1532
1569
|
}
|
|
1533
1570
|
function ReasonStrip({ it, ui }) {
|
|
1534
|
-
const [open, setOpen] =
|
|
1571
|
+
const [open, setOpen] = useState8(false);
|
|
1535
1572
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1536
1573
|
const hasStats = hasStatsContent(it);
|
|
1537
1574
|
const hasText = !!it.text;
|
|
@@ -1641,7 +1678,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
1641
1678
|
}
|
|
1642
1679
|
|
|
1643
1680
|
// src/components/MarketOddsCardList.tsx
|
|
1644
|
-
import { useState as
|
|
1681
|
+
import { useState as useState9 } from "react";
|
|
1645
1682
|
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1646
1683
|
function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
1647
1684
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -1658,7 +1695,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1658
1695
|
] });
|
|
1659
1696
|
}
|
|
1660
1697
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds }) {
|
|
1661
|
-
const [open, setOpen] =
|
|
1698
|
+
const [open, setOpen] = useState9(false);
|
|
1662
1699
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1663
1700
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
1664
1701
|
const showPin = !!monitor && vm.pinEnabled;
|
|
@@ -1951,7 +1988,7 @@ function FixtureCard({ card }) {
|
|
|
1951
1988
|
}
|
|
1952
1989
|
|
|
1953
1990
|
// src/components/dashboard/CountdownCard.tsx
|
|
1954
|
-
import { Fragment as Fragment6, useEffect as
|
|
1991
|
+
import { Fragment as Fragment6, useEffect as useEffect6, useState as useState10 } from "react";
|
|
1955
1992
|
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1956
1993
|
var DAY_MS = 864e5;
|
|
1957
1994
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -1970,12 +2007,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
1970
2007
|
function CountdownCard({ card }) {
|
|
1971
2008
|
const { data } = card;
|
|
1972
2009
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
1973
|
-
const [nowMs, setNowMs] =
|
|
1974
|
-
const [reduced] =
|
|
2010
|
+
const [nowMs, setNowMs] = useState10(() => Date.now());
|
|
2011
|
+
const [reduced] = useState10(
|
|
1975
2012
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
1976
2013
|
);
|
|
1977
2014
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
1978
|
-
|
|
2015
|
+
useEffect6(() => {
|
|
1979
2016
|
if (reduced) return;
|
|
1980
2017
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
1981
2018
|
return () => window.clearInterval(id);
|
|
@@ -1994,7 +2031,7 @@ function CountdownCard({ card }) {
|
|
|
1994
2031
|
}
|
|
1995
2032
|
|
|
1996
2033
|
// src/components/dashboard/InjuriesCard.tsx
|
|
1997
|
-
import { useState as
|
|
2034
|
+
import { useState as useState11 } from "react";
|
|
1998
2035
|
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1999
2036
|
var SHOWN = 3;
|
|
2000
2037
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2017,7 +2054,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2017
2054
|
}
|
|
2018
2055
|
function InjuriesCard({ card, ui }) {
|
|
2019
2056
|
const { data } = card;
|
|
2020
|
-
const [expanded, setExpanded] =
|
|
2057
|
+
const [expanded, setExpanded] = useState11(false);
|
|
2021
2058
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2022
2059
|
const limit = expanded ? void 0 : SHOWN;
|
|
2023
2060
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2121,7 +2158,7 @@ function LinksCard({ card }) {
|
|
|
2121
2158
|
}
|
|
2122
2159
|
|
|
2123
2160
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2124
|
-
import { useState as
|
|
2161
|
+
import { useState as useState12 } from "react";
|
|
2125
2162
|
import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2126
2163
|
function initials3(name) {
|
|
2127
2164
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2131,7 +2168,7 @@ function PostMatchCrest({
|
|
|
2131
2168
|
name,
|
|
2132
2169
|
side
|
|
2133
2170
|
}) {
|
|
2134
|
-
const [failed, setFailed] =
|
|
2171
|
+
const [failed, setFailed] = useState12(false);
|
|
2135
2172
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2136
2173
|
if (url && !failed) {
|
|
2137
2174
|
return /* @__PURE__ */ jsx27("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2330,10 +2367,10 @@ function RestingFormCard({ card }) {
|
|
|
2330
2367
|
}
|
|
2331
2368
|
|
|
2332
2369
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2333
|
-
import { useRef as
|
|
2370
|
+
import { useRef as useRef5, useState as useState14 } from "react";
|
|
2334
2371
|
|
|
2335
2372
|
// src/useLiveClock.ts
|
|
2336
|
-
import { useEffect as
|
|
2373
|
+
import { useEffect as useEffect7, useState as useState13 } from "react";
|
|
2337
2374
|
var TICK_MS = 6e4;
|
|
2338
2375
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2339
2376
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2342,10 +2379,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2342
2379
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2343
2380
|
}
|
|
2344
2381
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2345
|
-
const [displayMinute, setDisplayMinute] =
|
|
2382
|
+
const [displayMinute, setDisplayMinute] = useState13(
|
|
2346
2383
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2347
2384
|
);
|
|
2348
|
-
|
|
2385
|
+
useEffect7(() => {
|
|
2349
2386
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2350
2387
|
if (!clockRunning) return;
|
|
2351
2388
|
const id = window.setInterval(() => {
|
|
@@ -2362,7 +2399,7 @@ function initials5(name) {
|
|
|
2362
2399
|
return name.trim().slice(0, 3).toUpperCase();
|
|
2363
2400
|
}
|
|
2364
2401
|
function LiveCrest({ url, name, side }) {
|
|
2365
|
-
const [failed, setFailed] =
|
|
2402
|
+
const [failed, setFailed] = useState14(false);
|
|
2366
2403
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
2367
2404
|
if (url && !failed) {
|
|
2368
2405
|
return /* @__PURE__ */ jsx36("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2381,7 +2418,7 @@ function LiveScoreCard({
|
|
|
2381
2418
|
}) {
|
|
2382
2419
|
const { data } = card;
|
|
2383
2420
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
2384
|
-
const seedRef =
|
|
2421
|
+
const seedRef = useRef5(null);
|
|
2385
2422
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
2386
2423
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
2387
2424
|
}
|
|
@@ -2421,7 +2458,7 @@ function LiveScoreCard({
|
|
|
2421
2458
|
}
|
|
2422
2459
|
|
|
2423
2460
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
2424
|
-
import { useState as
|
|
2461
|
+
import { useState as useState15 } from "react";
|
|
2425
2462
|
import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2426
2463
|
var SHOWN2 = 3;
|
|
2427
2464
|
function EventIcon({ type }) {
|
|
@@ -2463,7 +2500,7 @@ function EventRow({ e }) {
|
|
|
2463
2500
|
] });
|
|
2464
2501
|
}
|
|
2465
2502
|
function LiveTimelineCard({ card, ui }) {
|
|
2466
|
-
const [expanded, setExpanded] =
|
|
2503
|
+
const [expanded, setExpanded] = useState15(false);
|
|
2467
2504
|
const events = card.data.events;
|
|
2468
2505
|
if (!events || events.length === 0) return null;
|
|
2469
2506
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -2703,11 +2740,11 @@ function turnAnchor(messages) {
|
|
|
2703
2740
|
}
|
|
2704
2741
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
2705
2742
|
const { state, send } = controller;
|
|
2706
|
-
const listRef =
|
|
2707
|
-
const endRef =
|
|
2708
|
-
const pinnedRef =
|
|
2709
|
-
const anchoredTurnRef =
|
|
2710
|
-
|
|
2743
|
+
const listRef = useRef6(null);
|
|
2744
|
+
const endRef = useRef6(null);
|
|
2745
|
+
const pinnedRef = useRef6(true);
|
|
2746
|
+
const anchoredTurnRef = useRef6(null);
|
|
2747
|
+
useEffect8(() => {
|
|
2711
2748
|
const anchor = turnAnchor(state.messages);
|
|
2712
2749
|
const list = listRef.current;
|
|
2713
2750
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -2763,10 +2800,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
2763
2800
|
}
|
|
2764
2801
|
|
|
2765
2802
|
// src/components/ChatInput.tsx
|
|
2766
|
-
import { useState as
|
|
2803
|
+
import { useState as useState16 } from "react";
|
|
2767
2804
|
import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
2768
2805
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
2769
|
-
const [value, setValue] =
|
|
2806
|
+
const [value, setValue] = useState16("");
|
|
2770
2807
|
const submit = () => {
|
|
2771
2808
|
const t = value.trim();
|
|
2772
2809
|
if (!t) return;
|
|
@@ -3013,8 +3050,8 @@ function OddsPanel({ monitor, ui, slip }) {
|
|
|
3013
3050
|
import { jsx as jsx48, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
3014
3051
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
3015
3052
|
function useIsModal() {
|
|
3016
|
-
const [modal, setModal] =
|
|
3017
|
-
|
|
3053
|
+
const [modal, setModal] = useState17(false);
|
|
3054
|
+
useEffect9(() => {
|
|
3018
3055
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
3019
3056
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
3020
3057
|
const sync = () => setModal(mql.matches);
|
|
@@ -3027,7 +3064,7 @@ function useIsModal() {
|
|
|
3027
3064
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3028
3065
|
const { state, close, stop, send } = controller;
|
|
3029
3066
|
const isModal = useIsModal();
|
|
3030
|
-
|
|
3067
|
+
useEffect9(() => {
|
|
3031
3068
|
const onKey = (e) => {
|
|
3032
3069
|
if (e.key === "Escape") close();
|
|
3033
3070
|
};
|
|
@@ -3038,7 +3075,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3038
3075
|
monitor?.setActiveTab("chat");
|
|
3039
3076
|
send(text);
|
|
3040
3077
|
};
|
|
3041
|
-
|
|
3078
|
+
useEffect9(() => {
|
|
3042
3079
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3043
3080
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3044
3081
|
return /* @__PURE__ */ jsxs42("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
@@ -3061,8 +3098,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3061
3098
|
),
|
|
3062
3099
|
monitor && monitor.activeTab === "matches" && /* @__PURE__ */ jsx48(MatchesPanel, { monitor, ui, slip }),
|
|
3063
3100
|
monitor && monitor.activeTab === "odds" && /* @__PURE__ */ jsx48(OddsPanel, { monitor, ui, slip }),
|
|
3064
|
-
/* @__PURE__ */ jsx48(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: handleSend, onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
3065
|
-
ui.attribution !== false ? /* @__PURE__ */ jsx48("div", { className: "ss-w-attribution", children: "Powered by SensibleStats" }) : null
|
|
3101
|
+
/* @__PURE__ */ jsx48(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: handleSend, onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
3066
3102
|
] });
|
|
3067
3103
|
}
|
|
3068
3104
|
|
|
@@ -3110,8 +3146,40 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3110
3146
|
.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); }
|
|
3111
3147
|
.ss-w-fab-mark { width: 30px; height: 30px; display: block; object-fit: contain; }
|
|
3112
3148
|
.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); }
|
|
3113
|
-
.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; }
|
|
3149
|
+
.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; }
|
|
3114
3150
|
.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; }
|
|
3151
|
+
.ss-w-greeting-text { display: block; }
|
|
3152
|
+
/* Typing indicator: overlays the text area so bubble height stays put; hidden at
|
|
3153
|
+
rest, revealed only during the one-shot intro (see prefers-reduced-motion block). */
|
|
3154
|
+
.ss-w-greeting-dots { position: absolute; top: 0; bottom: 0; left: 12px; display: flex; align-items: center; gap: 4px; opacity: 0; pointer-events: none; }
|
|
3155
|
+
.ss-w-greeting-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--ss-w-faint); display: block; }
|
|
3156
|
+
/* One-shot launcher attention intro: FAB nudges, then the greeting types in.
|
|
3157
|
+
Gated behind no-preference so reduce-motion users get the greeting shown
|
|
3158
|
+
statically (text visible, no nudge, no typing). ChatFab adds .ss-w-intro once
|
|
3159
|
+
per page load; every animation runs a single time and settles at rest. */
|
|
3160
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
3161
|
+
.ss-w-intro .ss-w-fab { animation: ss-w-nudge .8s ease both; }
|
|
3162
|
+
.ss-w-intro .ss-w-greeting { animation: ss-w-greet-in .45s cubic-bezier(.2,.8,.3,1.15) .8s both; }
|
|
3163
|
+
.ss-w-intro .ss-w-greeting-text { animation: ss-w-greet-textin .4s ease 1.65s both; }
|
|
3164
|
+
.ss-w-intro .ss-w-greeting-dots { animation: ss-w-greet-dots .95s ease .85s both; }
|
|
3165
|
+
.ss-w-intro .ss-w-greeting-dots i { animation: ss-w-greet-dotbounce .5s ease .85s 2; }
|
|
3166
|
+
.ss-w-intro .ss-w-greeting-dots i:nth-child(2) { animation-delay: 1s; }
|
|
3167
|
+
.ss-w-intro .ss-w-greeting-dots i:nth-child(3) { animation-delay: 1.15s; }
|
|
3168
|
+
}
|
|
3169
|
+
@keyframes ss-w-nudge {
|
|
3170
|
+
0%, 100% { transform: none; }
|
|
3171
|
+
22% { transform: translateY(-9px) rotate(-9deg); }
|
|
3172
|
+
44% { transform: translateY(0) rotate(7deg); }
|
|
3173
|
+
64% { transform: translateY(-4px) rotate(-4deg); }
|
|
3174
|
+
82% { transform: rotate(2deg); }
|
|
3175
|
+
}
|
|
3176
|
+
@keyframes ss-w-greet-in {
|
|
3177
|
+
from { opacity: 0; transform: translateY(8px) scale(.92); }
|
|
3178
|
+
to { opacity: 1; transform: none; }
|
|
3179
|
+
}
|
|
3180
|
+
@keyframes ss-w-greet-textin { from { opacity: 0; } to { opacity: 1; } }
|
|
3181
|
+
@keyframes ss-w-greet-dots { 0% { opacity: 0; } 14% { opacity: 1; } 78% { opacity: 1; } 100% { opacity: 0; } }
|
|
3182
|
+
@keyframes ss-w-greet-dotbounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
|
|
3115
3183
|
|
|
3116
3184
|
/* panel */
|
|
3117
3185
|
.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); }
|
|
@@ -3350,7 +3418,6 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3350
3418
|
}
|
|
3351
3419
|
.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; }
|
|
3352
3420
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
3353
|
-
.ss-w-attribution { flex: 0 0 auto; padding: 6px 12px 8px; text-align: center; font-size: 11px; color: var(--ss-w-faint); }
|
|
3354
3421
|
|
|
3355
3422
|
/* add to slip */
|
|
3356
3423
|
.ss-w-slip { position: relative; display: flex; }
|
|
@@ -3846,7 +3913,11 @@ function StatsWidget(props) {
|
|
|
3846
3913
|
const cfg = props.config;
|
|
3847
3914
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
3848
3915
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
3849
|
-
const rootRef =
|
|
3916
|
+
const rootRef = useRef7(null);
|
|
3917
|
+
const introDoneRef = useRef7(false);
|
|
3918
|
+
const markIntroDone = useCallback5(() => {
|
|
3919
|
+
introDoneRef.current = true;
|
|
3920
|
+
}, []);
|
|
3850
3921
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
3851
3922
|
const monitor = useLiveMonitor({
|
|
3852
3923
|
getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
|
|
@@ -3880,7 +3951,7 @@ function StatsWidget(props) {
|
|
|
3880
3951
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
3881
3952
|
[built]
|
|
3882
3953
|
);
|
|
3883
|
-
|
|
3954
|
+
useEffect10(() => {
|
|
3884
3955
|
if (!built || !ui.injectStyles) return;
|
|
3885
3956
|
const node = rootRef.current?.getRootNode();
|
|
3886
3957
|
if (node) injectStyles(node);
|
|
@@ -3896,7 +3967,9 @@ function StatsWidget(props) {
|
|
|
3896
3967
|
logoUrl: ui.logoUrl,
|
|
3897
3968
|
onOpen: controller.open,
|
|
3898
3969
|
onDismissPopup: controller.dismissPopup,
|
|
3899
|
-
dismissLabel: ui.dismissGreeting
|
|
3970
|
+
dismissLabel: ui.dismissGreeting,
|
|
3971
|
+
introAllowed: !introDoneRef.current,
|
|
3972
|
+
onIntroStart: markIntroDone
|
|
3900
3973
|
}
|
|
3901
3974
|
) });
|
|
3902
3975
|
}
|