@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.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,6 +170,7 @@ function normalizeUi(cfg) {
|
|
|
170
170
|
cta: cfg.cta,
|
|
171
171
|
surface: cfg.surface,
|
|
172
172
|
ink: cfg.ink,
|
|
173
|
+
pinIcon: cfg.pinIcon ?? "pin",
|
|
173
174
|
position: cfg.position ?? "right",
|
|
174
175
|
offset: { x: cfg.offset?.x ?? 20, y: cfg.offset?.y ?? 20 },
|
|
175
176
|
radius: cfg.radius ?? 16,
|
|
@@ -1157,6 +1158,9 @@ function useLiveMonitor(opts) {
|
|
|
1157
1158
|
};
|
|
1158
1159
|
}
|
|
1159
1160
|
|
|
1161
|
+
// src/components/ChatFab.tsx
|
|
1162
|
+
import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
|
|
1163
|
+
|
|
1160
1164
|
// src/components/BrandMark.tsx
|
|
1161
1165
|
import { useState as useState2 } from "react";
|
|
1162
1166
|
|
|
@@ -1209,23 +1213,39 @@ function BrandMark({ logoUrl, className }) {
|
|
|
1209
1213
|
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1210
1214
|
function GreetingBubble({ text, onOpen, onDismiss, dismissLabel = "Dismiss" }) {
|
|
1211
1215
|
return /* @__PURE__ */ jsxs("div", { className: "ss-w-greeting", role: "note", children: [
|
|
1212
|
-
/* @__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
|
+
] }),
|
|
1213
1224
|
onDismiss ? /* @__PURE__ */ jsx3("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null
|
|
1214
1225
|
] });
|
|
1215
1226
|
}
|
|
1216
1227
|
|
|
1217
1228
|
// src/components/ChatFab.tsx
|
|
1218
1229
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1219
|
-
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss" }) {
|
|
1230
|
+
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart }) {
|
|
1220
1231
|
const bubble = popupText ?? greeting;
|
|
1221
|
-
|
|
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: [
|
|
1222
1242
|
bubble ? /* @__PURE__ */ jsx4(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel }) : null,
|
|
1223
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" }) })
|
|
1224
1244
|
] });
|
|
1225
1245
|
}
|
|
1226
1246
|
|
|
1227
1247
|
// src/components/ChatPanel.tsx
|
|
1228
|
-
import { useEffect as
|
|
1248
|
+
import { useEffect as useEffect9, useState as useState17 } from "react";
|
|
1229
1249
|
|
|
1230
1250
|
// src/components/ChatHeader.tsx
|
|
1231
1251
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -1238,7 +1258,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle }
|
|
|
1238
1258
|
}
|
|
1239
1259
|
|
|
1240
1260
|
// src/components/MessageList.tsx
|
|
1241
|
-
import { useEffect as
|
|
1261
|
+
import { useEffect as useEffect8, useRef as useRef6 } from "react";
|
|
1242
1262
|
|
|
1243
1263
|
// src/components/Markdown.tsx
|
|
1244
1264
|
import ReactMarkdown from "react-markdown";
|
|
@@ -1256,7 +1276,7 @@ function Markdown({ text }) {
|
|
|
1256
1276
|
}
|
|
1257
1277
|
|
|
1258
1278
|
// src/components/EntityCardList.tsx
|
|
1259
|
-
import { useState as
|
|
1279
|
+
import { useState as useState4 } from "react";
|
|
1260
1280
|
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1261
1281
|
var MAX_VISIBLE = 3;
|
|
1262
1282
|
var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
|
|
@@ -1340,7 +1360,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
|
|
|
1340
1360
|
)) });
|
|
1341
1361
|
}
|
|
1342
1362
|
function EntityCardItem({ c }) {
|
|
1343
|
-
const [active, setActive] =
|
|
1363
|
+
const [active, setActive] = useState4(0);
|
|
1344
1364
|
const hasBody = c.scopes.length > 0;
|
|
1345
1365
|
if (!hasBody) {
|
|
1346
1366
|
return /* @__PURE__ */ jsx7("div", { className: "ss-w-ecard ss-w-ecard-bare", "data-kind": c.kind, children: /* @__PURE__ */ jsx7(CardHeader, { c, showScopeCrest: false }) });
|
|
@@ -1354,7 +1374,7 @@ function EntityCardItem({ c }) {
|
|
|
1354
1374
|
] });
|
|
1355
1375
|
}
|
|
1356
1376
|
function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
|
|
1357
|
-
const [expanded, setExpanded] =
|
|
1377
|
+
const [expanded, setExpanded] = useState4(false);
|
|
1358
1378
|
if (cards.length === 0) return null;
|
|
1359
1379
|
const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
|
|
1360
1380
|
const overflow = cards.length - MAX_VISIBLE;
|
|
@@ -1365,18 +1385,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
|
|
|
1365
1385
|
}
|
|
1366
1386
|
|
|
1367
1387
|
// src/components/BettingInsightList.tsx
|
|
1368
|
-
import { useState as
|
|
1388
|
+
import { useState as useState8 } from "react";
|
|
1369
1389
|
|
|
1370
1390
|
// src/components/AddToSlipButton.tsx
|
|
1371
|
-
import { useCallback as useCallback3, useState as
|
|
1391
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
1372
1392
|
import { isAddToSlipResult } from "@sensiblestats/widget-sdk";
|
|
1373
1393
|
|
|
1374
1394
|
// src/components/Toast.tsx
|
|
1375
|
-
import { useEffect as
|
|
1395
|
+
import { useEffect as useEffect5 } from "react";
|
|
1376
1396
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1377
1397
|
var DISMISS_MS = 4e3;
|
|
1378
1398
|
function Toast({ toast, onDismiss }) {
|
|
1379
|
-
|
|
1399
|
+
useEffect5(() => {
|
|
1380
1400
|
if (!toast) return;
|
|
1381
1401
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
1382
1402
|
return () => clearTimeout(timer);
|
|
@@ -1394,9 +1414,9 @@ function withTimeout(value) {
|
|
|
1394
1414
|
]);
|
|
1395
1415
|
}
|
|
1396
1416
|
function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
|
|
1397
|
-
const [pending, setPending] =
|
|
1398
|
-
const [settled, setSettled] =
|
|
1399
|
-
const [toast, setToast] =
|
|
1417
|
+
const [pending, setPending] = useState5(false);
|
|
1418
|
+
const [settled, setSettled] = useState5(false);
|
|
1419
|
+
const [toast, setToast] = useState5(null);
|
|
1400
1420
|
const dismiss = useCallback3(() => setToast(null), []);
|
|
1401
1421
|
const handler = slip.onAddToSlip;
|
|
1402
1422
|
if (!ui || !handler || !selection) return null;
|
|
@@ -1461,14 +1481,14 @@ function OddsMovementBadge({
|
|
|
1461
1481
|
}
|
|
1462
1482
|
|
|
1463
1483
|
// src/components/Crest.tsx
|
|
1464
|
-
import { useState as
|
|
1484
|
+
import { useState as useState6 } from "react";
|
|
1465
1485
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1466
1486
|
function initials2(name) {
|
|
1467
1487
|
return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
|
|
1468
1488
|
}
|
|
1469
1489
|
function Crest2({ url, name, kind }) {
|
|
1470
1490
|
const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
|
|
1471
|
-
const [failed, setFailed] =
|
|
1491
|
+
const [failed, setFailed] = useState6(false);
|
|
1472
1492
|
if (url && !failed) {
|
|
1473
1493
|
return /* @__PURE__ */ jsx11("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
1474
1494
|
}
|
|
@@ -1476,10 +1496,15 @@ function Crest2({ url, name, kind }) {
|
|
|
1476
1496
|
}
|
|
1477
1497
|
|
|
1478
1498
|
// src/components/PinToggle.tsx
|
|
1479
|
-
import { useCallback as useCallback4, useState as
|
|
1499
|
+
import { useCallback as useCallback4, useState as useState7 } from "react";
|
|
1480
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
|
+
};
|
|
1481
1506
|
function PinToggle({ pin, monitor, ui }) {
|
|
1482
|
-
const [toast, setToast] =
|
|
1507
|
+
const [toast, setToast] = useState7(null);
|
|
1483
1508
|
const dismiss = useCallback4(() => setToast(null), []);
|
|
1484
1509
|
if (!monitor || !monitor.enabled) return null;
|
|
1485
1510
|
const key = pinKey(pin);
|
|
@@ -1493,6 +1518,7 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1493
1518
|
setToast({ kind: "error", text: ui.marketLimitToast });
|
|
1494
1519
|
}
|
|
1495
1520
|
};
|
|
1521
|
+
const glyph = PIN_GLYPHS[ui.pinIcon];
|
|
1496
1522
|
return /* @__PURE__ */ jsxs7("div", { className: "ss-w-pin-wrap", children: [
|
|
1497
1523
|
/* @__PURE__ */ jsx12(
|
|
1498
1524
|
"button",
|
|
@@ -1500,10 +1526,22 @@ function PinToggle({ pin, monitor, ui }) {
|
|
|
1500
1526
|
type: "button",
|
|
1501
1527
|
className: "ss-w-pin",
|
|
1502
1528
|
"data-on": pinned || void 0,
|
|
1529
|
+
"data-icon": ui.pinIcon,
|
|
1503
1530
|
"aria-pressed": pinned,
|
|
1504
1531
|
"aria-label": pinned ? labels.unpin : labels.pin,
|
|
1505
1532
|
onClick,
|
|
1506
|
-
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
|
+
) })
|
|
1507
1545
|
}
|
|
1508
1546
|
),
|
|
1509
1547
|
/* @__PURE__ */ jsx12(Toast, { toast, onDismiss: dismiss })
|
|
@@ -1519,7 +1557,7 @@ function StatRows({ stats }) {
|
|
|
1519
1557
|
] }, `${s.label}:${s.value}`)) });
|
|
1520
1558
|
}
|
|
1521
1559
|
function ScopedStats({ scopes }) {
|
|
1522
|
-
const [active, setActive] =
|
|
1560
|
+
const [active, setActive] = useState8(0);
|
|
1523
1561
|
const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
|
|
1524
1562
|
return /* @__PURE__ */ jsxs8("div", { className: "ss-w-bi-scoped", children: [
|
|
1525
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)) }),
|
|
@@ -1530,7 +1568,7 @@ function hasStatsContent(it) {
|
|
|
1530
1568
|
return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
|
|
1531
1569
|
}
|
|
1532
1570
|
function ReasonStrip({ it, ui }) {
|
|
1533
|
-
const [open, setOpen] =
|
|
1571
|
+
const [open, setOpen] = useState8(false);
|
|
1534
1572
|
const hasScopes = !!it.scopes && it.scopes.length >= 2;
|
|
1535
1573
|
const hasStats = hasStatsContent(it);
|
|
1536
1574
|
const hasText = !!it.text;
|
|
@@ -1640,7 +1678,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
1640
1678
|
}
|
|
1641
1679
|
|
|
1642
1680
|
// src/components/MarketOddsCardList.tsx
|
|
1643
|
-
import { useState as
|
|
1681
|
+
import { useState as useState9 } from "react";
|
|
1644
1682
|
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1645
1683
|
function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
1646
1684
|
const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -1657,7 +1695,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
|
|
|
1657
1695
|
] });
|
|
1658
1696
|
}
|
|
1659
1697
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds }) {
|
|
1660
|
-
const [open, setOpen] =
|
|
1698
|
+
const [open, setOpen] = useState9(false);
|
|
1661
1699
|
const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
1662
1700
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
1663
1701
|
const showPin = !!monitor && vm.pinEnabled;
|
|
@@ -1950,7 +1988,7 @@ function FixtureCard({ card }) {
|
|
|
1950
1988
|
}
|
|
1951
1989
|
|
|
1952
1990
|
// src/components/dashboard/CountdownCard.tsx
|
|
1953
|
-
import { Fragment as Fragment6, useEffect as
|
|
1991
|
+
import { Fragment as Fragment6, useEffect as useEffect6, useState as useState10 } from "react";
|
|
1954
1992
|
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1955
1993
|
var DAY_MS = 864e5;
|
|
1956
1994
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -1969,12 +2007,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
1969
2007
|
function CountdownCard({ card }) {
|
|
1970
2008
|
const { data } = card;
|
|
1971
2009
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
1972
|
-
const [nowMs, setNowMs] =
|
|
1973
|
-
const [reduced] =
|
|
2010
|
+
const [nowMs, setNowMs] = useState10(() => Date.now());
|
|
2011
|
+
const [reduced] = useState10(
|
|
1974
2012
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
1975
2013
|
);
|
|
1976
2014
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
1977
|
-
|
|
2015
|
+
useEffect6(() => {
|
|
1978
2016
|
if (reduced) return;
|
|
1979
2017
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
1980
2018
|
return () => window.clearInterval(id);
|
|
@@ -1993,7 +2031,7 @@ function CountdownCard({ card }) {
|
|
|
1993
2031
|
}
|
|
1994
2032
|
|
|
1995
2033
|
// src/components/dashboard/InjuriesCard.tsx
|
|
1996
|
-
import { useState as
|
|
2034
|
+
import { useState as useState11 } from "react";
|
|
1997
2035
|
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1998
2036
|
var SHOWN = 3;
|
|
1999
2037
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2016,7 +2054,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2016
2054
|
}
|
|
2017
2055
|
function InjuriesCard({ card, ui }) {
|
|
2018
2056
|
const { data } = card;
|
|
2019
|
-
const [expanded, setExpanded] =
|
|
2057
|
+
const [expanded, setExpanded] = useState11(false);
|
|
2020
2058
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2021
2059
|
const limit = expanded ? void 0 : SHOWN;
|
|
2022
2060
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2120,7 +2158,7 @@ function LinksCard({ card }) {
|
|
|
2120
2158
|
}
|
|
2121
2159
|
|
|
2122
2160
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2123
|
-
import { useState as
|
|
2161
|
+
import { useState as useState12 } from "react";
|
|
2124
2162
|
import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2125
2163
|
function initials3(name) {
|
|
2126
2164
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2130,7 +2168,7 @@ function PostMatchCrest({
|
|
|
2130
2168
|
name,
|
|
2131
2169
|
side
|
|
2132
2170
|
}) {
|
|
2133
|
-
const [failed, setFailed] =
|
|
2171
|
+
const [failed, setFailed] = useState12(false);
|
|
2134
2172
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2135
2173
|
if (url && !failed) {
|
|
2136
2174
|
return /* @__PURE__ */ jsx27("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2329,10 +2367,10 @@ function RestingFormCard({ card }) {
|
|
|
2329
2367
|
}
|
|
2330
2368
|
|
|
2331
2369
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2332
|
-
import { useRef as
|
|
2370
|
+
import { useRef as useRef5, useState as useState14 } from "react";
|
|
2333
2371
|
|
|
2334
2372
|
// src/useLiveClock.ts
|
|
2335
|
-
import { useEffect as
|
|
2373
|
+
import { useEffect as useEffect7, useState as useState13 } from "react";
|
|
2336
2374
|
var TICK_MS = 6e4;
|
|
2337
2375
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2338
2376
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2341,10 +2379,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2341
2379
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2342
2380
|
}
|
|
2343
2381
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2344
|
-
const [displayMinute, setDisplayMinute] =
|
|
2382
|
+
const [displayMinute, setDisplayMinute] = useState13(
|
|
2345
2383
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2346
2384
|
);
|
|
2347
|
-
|
|
2385
|
+
useEffect7(() => {
|
|
2348
2386
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2349
2387
|
if (!clockRunning) return;
|
|
2350
2388
|
const id = window.setInterval(() => {
|
|
@@ -2361,7 +2399,7 @@ function initials5(name) {
|
|
|
2361
2399
|
return name.trim().slice(0, 3).toUpperCase();
|
|
2362
2400
|
}
|
|
2363
2401
|
function LiveCrest({ url, name, side }) {
|
|
2364
|
-
const [failed, setFailed] =
|
|
2402
|
+
const [failed, setFailed] = useState14(false);
|
|
2365
2403
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
2366
2404
|
if (url && !failed) {
|
|
2367
2405
|
return /* @__PURE__ */ jsx36("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2380,7 +2418,7 @@ function LiveScoreCard({
|
|
|
2380
2418
|
}) {
|
|
2381
2419
|
const { data } = card;
|
|
2382
2420
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
2383
|
-
const seedRef =
|
|
2421
|
+
const seedRef = useRef5(null);
|
|
2384
2422
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
2385
2423
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
2386
2424
|
}
|
|
@@ -2420,7 +2458,7 @@ function LiveScoreCard({
|
|
|
2420
2458
|
}
|
|
2421
2459
|
|
|
2422
2460
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
2423
|
-
import { useState as
|
|
2461
|
+
import { useState as useState15 } from "react";
|
|
2424
2462
|
import { jsx as jsx37, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
2425
2463
|
var SHOWN2 = 3;
|
|
2426
2464
|
function EventIcon({ type }) {
|
|
@@ -2462,7 +2500,7 @@ function EventRow({ e }) {
|
|
|
2462
2500
|
] });
|
|
2463
2501
|
}
|
|
2464
2502
|
function LiveTimelineCard({ card, ui }) {
|
|
2465
|
-
const [expanded, setExpanded] =
|
|
2503
|
+
const [expanded, setExpanded] = useState15(false);
|
|
2466
2504
|
const events = card.data.events;
|
|
2467
2505
|
if (!events || events.length === 0) return null;
|
|
2468
2506
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -2702,11 +2740,11 @@ function turnAnchor(messages) {
|
|
|
2702
2740
|
}
|
|
2703
2741
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
2704
2742
|
const { state, send } = controller;
|
|
2705
|
-
const listRef =
|
|
2706
|
-
const endRef =
|
|
2707
|
-
const pinnedRef =
|
|
2708
|
-
const anchoredTurnRef =
|
|
2709
|
-
|
|
2743
|
+
const listRef = useRef6(null);
|
|
2744
|
+
const endRef = useRef6(null);
|
|
2745
|
+
const pinnedRef = useRef6(true);
|
|
2746
|
+
const anchoredTurnRef = useRef6(null);
|
|
2747
|
+
useEffect8(() => {
|
|
2710
2748
|
const anchor = turnAnchor(state.messages);
|
|
2711
2749
|
const list = listRef.current;
|
|
2712
2750
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -2762,10 +2800,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
2762
2800
|
}
|
|
2763
2801
|
|
|
2764
2802
|
// src/components/ChatInput.tsx
|
|
2765
|
-
import { useState as
|
|
2803
|
+
import { useState as useState16 } from "react";
|
|
2766
2804
|
import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
2767
2805
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
2768
|
-
const [value, setValue] =
|
|
2806
|
+
const [value, setValue] = useState16("");
|
|
2769
2807
|
const submit = () => {
|
|
2770
2808
|
const t = value.trim();
|
|
2771
2809
|
if (!t) return;
|
|
@@ -3012,8 +3050,8 @@ function OddsPanel({ monitor, ui, slip }) {
|
|
|
3012
3050
|
import { jsx as jsx48, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
3013
3051
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
3014
3052
|
function useIsModal() {
|
|
3015
|
-
const [modal, setModal] =
|
|
3016
|
-
|
|
3053
|
+
const [modal, setModal] = useState17(false);
|
|
3054
|
+
useEffect9(() => {
|
|
3017
3055
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
3018
3056
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
3019
3057
|
const sync = () => setModal(mql.matches);
|
|
@@ -3026,7 +3064,7 @@ function useIsModal() {
|
|
|
3026
3064
|
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3027
3065
|
const { state, close, stop, send } = controller;
|
|
3028
3066
|
const isModal = useIsModal();
|
|
3029
|
-
|
|
3067
|
+
useEffect9(() => {
|
|
3030
3068
|
const onKey = (e) => {
|
|
3031
3069
|
if (e.key === "Escape") close();
|
|
3032
3070
|
};
|
|
@@ -3037,7 +3075,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3037
3075
|
monitor?.setActiveTab("chat");
|
|
3038
3076
|
send(text);
|
|
3039
3077
|
};
|
|
3040
|
-
|
|
3078
|
+
useEffect9(() => {
|
|
3041
3079
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
3042
3080
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
3043
3081
|
return /* @__PURE__ */ jsxs42("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
@@ -3108,8 +3146,40 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
3108
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); }
|
|
3109
3147
|
.ss-w-fab-mark { width: 30px; height: 30px; display: block; object-fit: contain; }
|
|
3110
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); }
|
|
3111
|
-
.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; }
|
|
3112
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); } }
|
|
3113
3183
|
|
|
3114
3184
|
/* panel */
|
|
3115
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); }
|
|
@@ -3843,7 +3913,11 @@ function StatsWidget(props) {
|
|
|
3843
3913
|
const cfg = props.config;
|
|
3844
3914
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
3845
3915
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
3846
|
-
const rootRef =
|
|
3916
|
+
const rootRef = useRef7(null);
|
|
3917
|
+
const introDoneRef = useRef7(false);
|
|
3918
|
+
const markIntroDone = useCallback5(() => {
|
|
3919
|
+
introDoneRef.current = true;
|
|
3920
|
+
}, []);
|
|
3847
3921
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
3848
3922
|
const monitor = useLiveMonitor({
|
|
3849
3923
|
getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
|
|
@@ -3877,7 +3951,7 @@ function StatsWidget(props) {
|
|
|
3877
3951
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
3878
3952
|
[built]
|
|
3879
3953
|
);
|
|
3880
|
-
|
|
3954
|
+
useEffect10(() => {
|
|
3881
3955
|
if (!built || !ui.injectStyles) return;
|
|
3882
3956
|
const node = rootRef.current?.getRootNode();
|
|
3883
3957
|
if (node) injectStyles(node);
|
|
@@ -3893,7 +3967,9 @@ function StatsWidget(props) {
|
|
|
3893
3967
|
logoUrl: ui.logoUrl,
|
|
3894
3968
|
onOpen: controller.open,
|
|
3895
3969
|
onDismissPopup: controller.dismissPopup,
|
|
3896
|
-
dismissLabel: ui.dismissGreeting
|
|
3970
|
+
dismissLabel: ui.dismissGreeting,
|
|
3971
|
+
introAllowed: !introDoneRef.current,
|
|
3972
|
+
onIntroStart: markIntroDone
|
|
3897
3973
|
}
|
|
3898
3974
|
) });
|
|
3899
3975
|
}
|