@sensiblestats/widget-react 0.24.0 → 0.26.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 +22 -0
- package/dist/index.cjs +319 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +281 -73
- package/dist/index.js.map +1 -1
- package/dist/styles.css +42 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/StatsWidget.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef4 } from "react";
|
|
3
3
|
import { StatsWidgetClient } from "@sensiblestats/widget-sdk";
|
|
4
4
|
|
|
5
5
|
// src/kickoff.ts
|
|
@@ -225,7 +225,7 @@ function themeAttrs(ui) {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// src/useChatStream.ts
|
|
228
|
-
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
|
228
|
+
import { useCallback, useEffect as useEffect2, useMemo, useReducer, useRef as useRef2 } from "react";
|
|
229
229
|
import { WidgetSdkError as WidgetSdkError2 } from "@sensiblestats/widget-sdk";
|
|
230
230
|
|
|
231
231
|
// src/reducer.ts
|
|
@@ -567,20 +567,63 @@ function reducer(state, action) {
|
|
|
567
567
|
return { ...state, error: action.error, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
568
568
|
case "STOP":
|
|
569
569
|
return { ...state, isStreaming: false, status: null, actionsLoading: false, messages: mapActive(state, (t) => ({ ...t, done: true })) };
|
|
570
|
+
case "LIVE_REFRESH":
|
|
571
|
+
return {
|
|
572
|
+
...state,
|
|
573
|
+
messages: state.messages.map(
|
|
574
|
+
(m) => m.role === "assistant" && m.id === action.messageId && m.clubFanDashboard ? { ...m, clubFanDashboard: { ...m.clubFanDashboard, cards: action.cards } } : m
|
|
575
|
+
)
|
|
576
|
+
};
|
|
570
577
|
}
|
|
571
578
|
}
|
|
572
579
|
|
|
580
|
+
// src/useLiveDashboardPoll.ts
|
|
581
|
+
import { useEffect, useRef } from "react";
|
|
582
|
+
var POLL_INTERVAL_MS = 6e4;
|
|
583
|
+
function findLiveMessageId(messages) {
|
|
584
|
+
for (const m of messages.slice().reverse()) {
|
|
585
|
+
if (m.role === "assistant" && m.clubFanDashboard?.phase === "live") return m.id;
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
590
|
+
const messageId = findLiveMessageId(messages);
|
|
591
|
+
const inFlightRef = useRef(false);
|
|
592
|
+
useEffect(() => {
|
|
593
|
+
if (!messageId) return;
|
|
594
|
+
const tick = async () => {
|
|
595
|
+
if (inFlightRef.current) return;
|
|
596
|
+
inFlightRef.current = true;
|
|
597
|
+
try {
|
|
598
|
+
const payload = await getLiveDashboard();
|
|
599
|
+
if (payload && payload.phase === "live") {
|
|
600
|
+
dispatch({ kind: "LIVE_REFRESH", messageId, cards: payload.cards });
|
|
601
|
+
} else {
|
|
602
|
+
window.clearInterval(id);
|
|
603
|
+
}
|
|
604
|
+
} catch {
|
|
605
|
+
} finally {
|
|
606
|
+
inFlightRef.current = false;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
const id = window.setInterval(() => {
|
|
610
|
+
void tick();
|
|
611
|
+
}, POLL_INTERVAL_MS);
|
|
612
|
+
return () => window.clearInterval(id);
|
|
613
|
+
}, [messageId, dispatch, getLiveDashboard]);
|
|
614
|
+
}
|
|
615
|
+
|
|
573
616
|
// src/useChatStream.ts
|
|
574
617
|
var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
|
|
575
618
|
function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
576
619
|
const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
|
|
577
620
|
const [state, dispatch] = useReducer(reducer, seed);
|
|
578
|
-
const idRef =
|
|
579
|
-
const handleRef =
|
|
580
|
-
const fallbackTimerRef =
|
|
581
|
-
const stateRef =
|
|
621
|
+
const idRef = useRef2(0);
|
|
622
|
+
const handleRef = useRef2(null);
|
|
623
|
+
const fallbackTimerRef = useRef2(null);
|
|
624
|
+
const stateRef = useRef2(state);
|
|
582
625
|
stateRef.current = state;
|
|
583
|
-
const greetedRef =
|
|
626
|
+
const greetedRef = useRef2(false);
|
|
584
627
|
const nextId = () => `m${idRef.current++}`;
|
|
585
628
|
const clearFallbackTimer = useCallback(() => {
|
|
586
629
|
if (fallbackTimerRef.current != null) {
|
|
@@ -636,7 +679,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
636
679
|
const last = stateRef.current.lastUserInput;
|
|
637
680
|
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
638
681
|
}, [run]);
|
|
639
|
-
|
|
682
|
+
useEffect2(() => {
|
|
640
683
|
const message = greetingMessage?.trim();
|
|
641
684
|
if (!message || greetedRef.current) return;
|
|
642
685
|
greetedRef.current = true;
|
|
@@ -670,6 +713,8 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
|
|
|
670
713
|
else open();
|
|
671
714
|
}, [close, open]);
|
|
672
715
|
const dismissPopup = useCallback(() => dispatch({ kind: "DISMISS_POPUP" }), []);
|
|
716
|
+
const getLiveDashboard = useCallback(() => conversation.getLiveDashboard(), [conversation]);
|
|
717
|
+
useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
|
|
673
718
|
return { state, open, close, stop, toggle, send, retry, dismissPopup };
|
|
674
719
|
}
|
|
675
720
|
|
|
@@ -730,7 +775,7 @@ function ChatFab({ label, greeting, popupText, onOpen, onDismissPopup, dismissLa
|
|
|
730
775
|
}
|
|
731
776
|
|
|
732
777
|
// src/components/ChatPanel.tsx
|
|
733
|
-
import { useEffect as
|
|
778
|
+
import { useEffect as useEffect6, useState as useState8 } from "react";
|
|
734
779
|
|
|
735
780
|
// src/components/ChatHeader.tsx
|
|
736
781
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -743,7 +788,7 @@ function ChatHeader({ onClose, closeLabel = "Close chat" }) {
|
|
|
743
788
|
}
|
|
744
789
|
|
|
745
790
|
// src/components/MessageList.tsx
|
|
746
|
-
import { useEffect as
|
|
791
|
+
import { useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
747
792
|
|
|
748
793
|
// src/components/Markdown.tsx
|
|
749
794
|
import ReactMarkdown from "react-markdown";
|
|
@@ -898,11 +943,11 @@ function resultToToast(result, shownOdds, ui) {
|
|
|
898
943
|
}
|
|
899
944
|
|
|
900
945
|
// src/components/Toast.tsx
|
|
901
|
-
import { useEffect as
|
|
946
|
+
import { useEffect as useEffect3 } from "react";
|
|
902
947
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
903
948
|
var DISMISS_MS = 4e3;
|
|
904
949
|
function Toast({ toast, onDismiss }) {
|
|
905
|
-
|
|
950
|
+
useEffect3(() => {
|
|
906
951
|
if (!toast) return;
|
|
907
952
|
const timer = setTimeout(onDismiss, DISMISS_MS);
|
|
908
953
|
return () => clearTimeout(timer);
|
|
@@ -1370,7 +1415,7 @@ function FixtureCard({ card, onTap }) {
|
|
|
1370
1415
|
}
|
|
1371
1416
|
|
|
1372
1417
|
// src/components/dashboard/CountdownCard.tsx
|
|
1373
|
-
import { Fragment as Fragment6, useEffect as
|
|
1418
|
+
import { Fragment as Fragment6, useEffect as useEffect4, useState as useState6 } from "react";
|
|
1374
1419
|
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1375
1420
|
var DAY_MS = 864e5;
|
|
1376
1421
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -1394,7 +1439,7 @@ function CountdownCard({ card }) {
|
|
|
1394
1439
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
1395
1440
|
);
|
|
1396
1441
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
1397
|
-
|
|
1442
|
+
useEffect4(() => {
|
|
1398
1443
|
if (reduced) return;
|
|
1399
1444
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
1400
1445
|
return () => window.clearInterval(id);
|
|
@@ -1591,77 +1636,198 @@ function PostMatchNextCard({ card }) {
|
|
|
1591
1636
|
] });
|
|
1592
1637
|
}
|
|
1593
1638
|
|
|
1594
|
-
// src/components/dashboard/
|
|
1639
|
+
// src/components/dashboard/RestingNextCard.tsx
|
|
1595
1640
|
import { jsx as jsx30, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
1641
|
+
function RestingNextCard({ card, onTap }) {
|
|
1642
|
+
const { data } = card;
|
|
1643
|
+
return /* @__PURE__ */ jsxs26("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-resting-next", onClick: () => onTap(card), children: [
|
|
1644
|
+
card.title ? /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1645
|
+
/* @__PURE__ */ jsxs26("div", { className: "ss-w-fandash-resting-next-row", children: [
|
|
1646
|
+
/* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-resting-next-when", children: data.whenLabel }),
|
|
1647
|
+
data.competition ? /* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-resting-next-competition", children: data.competition }) : null
|
|
1648
|
+
] }),
|
|
1649
|
+
/* @__PURE__ */ jsx30("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
|
|
1650
|
+
] });
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// src/components/dashboard/RestingStandingsCard.tsx
|
|
1654
|
+
import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
1655
|
+
function RestingStandingsCard({ card, onTap }) {
|
|
1656
|
+
const { data } = card;
|
|
1657
|
+
return /* @__PURE__ */ jsxs27("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-resting-standings", onClick: () => onTap(card), children: [
|
|
1658
|
+
card.title ? /* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1659
|
+
/* @__PURE__ */ jsxs27("div", { className: "ss-w-fandash-resting-standings-row", children: [
|
|
1660
|
+
/* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-resting-standings-position", children: data.positionLabel }),
|
|
1661
|
+
/* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-resting-standings-record", children: data.recordLabel })
|
|
1662
|
+
] }),
|
|
1663
|
+
/* @__PURE__ */ jsx31("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
|
|
1664
|
+
] });
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/components/dashboard/RestingSeasonCard.tsx
|
|
1668
|
+
import { jsx as jsx32, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
1669
|
+
function RestingSeasonCard({ card, onTap }) {
|
|
1670
|
+
const { data } = card;
|
|
1671
|
+
return /* @__PURE__ */ jsxs28("button", { type: "button", className: "ss-w-fandash-card ss-w-fandash-card--tap ss-w-fandash-resting-season", onClick: () => onTap(card), children: [
|
|
1672
|
+
card.title ? /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1673
|
+
/* @__PURE__ */ jsxs28("div", { className: "ss-w-fandash-resting-season-rows", children: [
|
|
1674
|
+
data.scorerLabel ? /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-resting-season-row", children: data.scorerLabel }) : null,
|
|
1675
|
+
data.playerLabel ? /* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-resting-season-row", children: data.playerLabel }) : null,
|
|
1676
|
+
/* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-resting-season-row", children: data.tallyLabel })
|
|
1677
|
+
] }),
|
|
1678
|
+
/* @__PURE__ */ jsx32("span", { className: "ss-w-fandash-card-cta", children: card.actionLabel })
|
|
1679
|
+
] });
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// src/components/dashboard/RestingFormCard.tsx
|
|
1683
|
+
import { jsx as jsx33, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
1684
|
+
function resultClass2(result) {
|
|
1685
|
+
const upper = result.toUpperCase();
|
|
1686
|
+
if (upper === "W") return "ss-w-fandash-form-pill--win";
|
|
1687
|
+
if (upper === "L") return "ss-w-fandash-form-pill--loss";
|
|
1688
|
+
return "ss-w-fandash-form-pill--draw";
|
|
1689
|
+
}
|
|
1690
|
+
function RestingFormCard({ card }) {
|
|
1691
|
+
const { data } = card;
|
|
1692
|
+
return /* @__PURE__ */ jsxs29("div", { className: "ss-w-fandash-card ss-w-fandash-resting-form", children: [
|
|
1693
|
+
card.title ? /* @__PURE__ */ jsx33("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1694
|
+
/* @__PURE__ */ jsx33("span", { className: "ss-w-fandash-form", children: data.letters.map((r, i) => /* @__PURE__ */ jsx33("span", { className: `ss-w-fandash-form-pill ${resultClass2(r)}`, children: r }, `${r}-${i}`)) })
|
|
1695
|
+
] });
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// src/components/dashboard/LiveScoreCard.tsx
|
|
1699
|
+
import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
1700
|
+
function LiveScoreCard({ card }) {
|
|
1701
|
+
const { data } = card;
|
|
1702
|
+
return /* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
1703
|
+
card.title ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1704
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-postmatch-scoreboard", children: [
|
|
1705
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team", children: data.homeName }),
|
|
1706
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.homeScore }),
|
|
1707
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-sep", children: "\u2013" }),
|
|
1708
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-score", children: data.awayScore }),
|
|
1709
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-postmatch-team ss-w-fandash-postmatch-team--away", children: data.awayName })
|
|
1710
|
+
] }),
|
|
1711
|
+
/* @__PURE__ */ jsxs30("div", { className: "ss-w-fandash-live-meta", children: [
|
|
1712
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-minute", children: data.minuteLabel }),
|
|
1713
|
+
/* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-status", children: data.statusLabel }),
|
|
1714
|
+
data.competition ? /* @__PURE__ */ jsx34("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
1715
|
+
] })
|
|
1716
|
+
] });
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// src/components/dashboard/LiveTimelineCard.tsx
|
|
1720
|
+
import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
1721
|
+
function LiveTimelineCard({ card }) {
|
|
1722
|
+
return /* @__PURE__ */ jsxs31("div", { className: "ss-w-fandash-card ss-w-fandash-live-timeline", children: [
|
|
1723
|
+
card.title ? /* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1724
|
+
/* @__PURE__ */ jsx35("ul", { className: "ss-w-fandash-live-event-list", children: card.data.events.map((e, i) => /* @__PURE__ */ jsxs31("li", { className: e.scoredByClub ? "ss-w-fandash-live-event ss-w-fandash-live-event--club" : "ss-w-fandash-live-event", children: [
|
|
1725
|
+
/* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-minute", children: e.minuteLabel }),
|
|
1726
|
+
/* @__PURE__ */ jsx35("span", { className: "ss-w-fandash-live-event-text", children: e.text })
|
|
1727
|
+
] }, `${e.minuteLabel}-${i}`)) })
|
|
1728
|
+
] });
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/components/dashboard/LiveStatsCard.tsx
|
|
1732
|
+
import { jsx as jsx36, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
1733
|
+
function LiveStatsCard({ card }) {
|
|
1734
|
+
const { data } = card;
|
|
1735
|
+
return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-stats", children: [
|
|
1736
|
+
card.title ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-eyebrow", children: card.title }) : null,
|
|
1737
|
+
/* @__PURE__ */ jsx36("div", { className: "ss-w-fandash-live-stat-rows", children: data.rows.map((r, i) => /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-stat-row", children: [
|
|
1738
|
+
/* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.clubValue }),
|
|
1739
|
+
/* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-label", children: r.label }),
|
|
1740
|
+
/* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-stat-value", children: r.oppValue })
|
|
1741
|
+
] }, `${r.label}-${i}`)) }),
|
|
1742
|
+
data.momentumLabel ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-momentum", children: data.momentumLabel }) : null
|
|
1743
|
+
] });
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/components/dashboard/ClubFanDashboard.tsx
|
|
1747
|
+
import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
1596
1748
|
function ClubFanDashboard({ dashboard, onTap, ui }) {
|
|
1597
1749
|
const orderedCards = [
|
|
1598
1750
|
...dashboard.cards.filter((c) => c.cardType === "countdown"),
|
|
1599
1751
|
...dashboard.cards.filter((c) => c.cardType !== "countdown")
|
|
1600
1752
|
];
|
|
1601
|
-
return /* @__PURE__ */
|
|
1602
|
-
dashboard.greeting ? /* @__PURE__ */
|
|
1603
|
-
/* @__PURE__ */
|
|
1753
|
+
return /* @__PURE__ */ jsxs33("div", { className: "ss-w-fandash", children: [
|
|
1754
|
+
dashboard.greeting ? /* @__PURE__ */ jsx37("span", { className: "ss-w-fandash-greeting", children: dashboard.greeting }) : null,
|
|
1755
|
+
/* @__PURE__ */ jsx37("div", { className: "ss-w-fandash-cards", children: orderedCards.map((card, i) => /* @__PURE__ */ jsx37(FanDashboardCard, { card, onTap, ui }, `${card.cardType}-${i}`)) })
|
|
1604
1756
|
] });
|
|
1605
1757
|
}
|
|
1606
1758
|
function FanDashboardCard({ card, onTap, ui }) {
|
|
1607
1759
|
switch (card.cardType) {
|
|
1608
1760
|
case "fixture":
|
|
1609
|
-
return /* @__PURE__ */
|
|
1761
|
+
return /* @__PURE__ */ jsx37(FixtureCard, { card, onTap });
|
|
1610
1762
|
case "countdown":
|
|
1611
|
-
return /* @__PURE__ */
|
|
1763
|
+
return /* @__PURE__ */ jsx37(CountdownCard, { card });
|
|
1612
1764
|
case "injuries":
|
|
1613
|
-
return /* @__PURE__ */
|
|
1765
|
+
return /* @__PURE__ */ jsx37(InjuriesCard, { card });
|
|
1614
1766
|
case "reasonToBet":
|
|
1615
|
-
return /* @__PURE__ */
|
|
1767
|
+
return /* @__PURE__ */ jsx37(ReasonToBetCard, { card });
|
|
1616
1768
|
case "whereToWatch":
|
|
1617
|
-
return /* @__PURE__ */
|
|
1769
|
+
return /* @__PURE__ */ jsx37(WhereToWatchCard, { card });
|
|
1618
1770
|
case "congestedWeek":
|
|
1619
|
-
return /* @__PURE__ */
|
|
1771
|
+
return /* @__PURE__ */ jsx37(CongestedWeekCard, { card, onTap });
|
|
1620
1772
|
case "transfers":
|
|
1621
|
-
return /* @__PURE__ */
|
|
1773
|
+
return /* @__PURE__ */ jsx37(TransfersCard, { card });
|
|
1622
1774
|
case "links":
|
|
1623
|
-
return /* @__PURE__ */
|
|
1775
|
+
return /* @__PURE__ */ jsx37(LinksCard, { card });
|
|
1624
1776
|
case "postMatchResult":
|
|
1625
|
-
return /* @__PURE__ */
|
|
1777
|
+
return /* @__PURE__ */ jsx37(PostMatchResultCard, { card });
|
|
1626
1778
|
case "postMatchGoals":
|
|
1627
|
-
return /* @__PURE__ */
|
|
1779
|
+
return /* @__PURE__ */ jsx37(PostMatchGoalsCard, { card, onTap });
|
|
1628
1780
|
case "postMatchStats":
|
|
1629
|
-
return /* @__PURE__ */
|
|
1781
|
+
return /* @__PURE__ */ jsx37(PostMatchStatsCard, { card, onTap });
|
|
1630
1782
|
case "postMatchTopPerformer":
|
|
1631
|
-
return /* @__PURE__ */
|
|
1783
|
+
return /* @__PURE__ */ jsx37(PostMatchTopPerformerCard, { card, onTap });
|
|
1632
1784
|
case "postMatchNext":
|
|
1633
|
-
return /* @__PURE__ */
|
|
1785
|
+
return /* @__PURE__ */ jsx37(PostMatchNextCard, { card });
|
|
1786
|
+
case "restingNext":
|
|
1787
|
+
return /* @__PURE__ */ jsx37(RestingNextCard, { card, onTap });
|
|
1788
|
+
case "restingStandings":
|
|
1789
|
+
return /* @__PURE__ */ jsx37(RestingStandingsCard, { card, onTap });
|
|
1790
|
+
case "restingSeason":
|
|
1791
|
+
return /* @__PURE__ */ jsx37(RestingSeasonCard, { card, onTap });
|
|
1792
|
+
case "restingForm":
|
|
1793
|
+
return /* @__PURE__ */ jsx37(RestingFormCard, { card });
|
|
1794
|
+
case "liveScore":
|
|
1795
|
+
return /* @__PURE__ */ jsx37(LiveScoreCard, { card });
|
|
1796
|
+
case "liveTimeline":
|
|
1797
|
+
return /* @__PURE__ */ jsx37(LiveTimelineCard, { card });
|
|
1798
|
+
case "liveStats":
|
|
1799
|
+
return /* @__PURE__ */ jsx37(LiveStatsCard, { card });
|
|
1634
1800
|
default:
|
|
1635
1801
|
return null;
|
|
1636
1802
|
}
|
|
1637
1803
|
}
|
|
1638
1804
|
|
|
1639
1805
|
// src/components/ChatMessage.tsx
|
|
1640
|
-
import { jsx as
|
|
1806
|
+
import { jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
1641
1807
|
function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
|
|
1642
1808
|
if (message.role === "user") {
|
|
1643
|
-
return /* @__PURE__ */
|
|
1809
|
+
return /* @__PURE__ */ jsx38("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx38("div", { className: "ss-w-bubble", children: message.text }) });
|
|
1644
1810
|
}
|
|
1645
1811
|
const onFanDashboardTap = (card) => {
|
|
1646
1812
|
onAct({ message: card.actionLabel, actionRef: card.actionRef, label: card.actionLabel || card.title || "" });
|
|
1647
1813
|
};
|
|
1648
|
-
return /* @__PURE__ */
|
|
1649
|
-
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1650
|
-
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */
|
|
1651
|
-
message.clubFanDashboard ? /* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
/* @__PURE__ */
|
|
1656
|
-
/* @__PURE__ */
|
|
1814
|
+
return /* @__PURE__ */ jsxs34("div", { className: "ss-w-msg ss-w-bot", children: [
|
|
1815
|
+
message.text && !message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(Markdown, { text: message.text }) : null,
|
|
1816
|
+
message.greetingDashboard && !message.clubFanDashboard ? /* @__PURE__ */ jsx38(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
|
|
1817
|
+
message.clubFanDashboard ? /* @__PURE__ */ jsx38(ClubFanDashboard, { dashboard: message.clubFanDashboard, onTap: onFanDashboardTap, ui }) : null,
|
|
1818
|
+
/* @__PURE__ */ jsx38(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
|
|
1819
|
+
/* @__PURE__ */ jsx38(BettingInsightList, { insights: message.insights, ui, slip }),
|
|
1820
|
+
/* @__PURE__ */ jsx38(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
|
|
1821
|
+
/* @__PURE__ */ jsx38(OddsMovementCardList, { cards: message.oddsMovement, ui }),
|
|
1822
|
+
/* @__PURE__ */ jsx38(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
|
|
1657
1823
|
] });
|
|
1658
1824
|
}
|
|
1659
1825
|
|
|
1660
1826
|
// src/components/IntentChips.tsx
|
|
1661
|
-
import { jsx as
|
|
1827
|
+
import { jsx as jsx39 } from "react/jsx-runtime";
|
|
1662
1828
|
function IntentChips({ chips, onPick }) {
|
|
1663
1829
|
if (chips.length === 0) return null;
|
|
1664
|
-
return /* @__PURE__ */
|
|
1830
|
+
return /* @__PURE__ */ jsx39("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx39("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
|
|
1665
1831
|
}
|
|
1666
1832
|
|
|
1667
1833
|
// src/scroll.ts
|
|
@@ -1671,18 +1837,18 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
|
|
|
1671
1837
|
}
|
|
1672
1838
|
|
|
1673
1839
|
// src/components/MessageList.tsx
|
|
1674
|
-
import { jsx as
|
|
1840
|
+
import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
1675
1841
|
function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
1676
1842
|
const { state, send } = controller;
|
|
1677
|
-
const listRef =
|
|
1678
|
-
const endRef =
|
|
1679
|
-
const pinnedRef =
|
|
1680
|
-
|
|
1843
|
+
const listRef = useRef3(null);
|
|
1844
|
+
const endRef = useRef3(null);
|
|
1845
|
+
const pinnedRef = useRef3(true);
|
|
1846
|
+
useEffect5(() => {
|
|
1681
1847
|
if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
|
|
1682
1848
|
}, [state.messages, state.status]);
|
|
1683
1849
|
const hasUserMessage = state.messages.some((m) => m.role === "user");
|
|
1684
1850
|
const hasDashboard = state.messages.some((m) => m.role === "assistant" && (m.greetingDashboard || m.clubFanDashboard));
|
|
1685
|
-
return /* @__PURE__ */
|
|
1851
|
+
return /* @__PURE__ */ jsxs35(
|
|
1686
1852
|
"div",
|
|
1687
1853
|
{
|
|
1688
1854
|
className: "ss-w-list",
|
|
@@ -1692,7 +1858,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1692
1858
|
if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
|
|
1693
1859
|
},
|
|
1694
1860
|
children: [
|
|
1695
|
-
state.messages.map((m) => /* @__PURE__ */
|
|
1861
|
+
state.messages.map((m) => /* @__PURE__ */ jsx40(
|
|
1696
1862
|
ChatMessage,
|
|
1697
1863
|
{
|
|
1698
1864
|
message: m,
|
|
@@ -1707,15 +1873,15 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1707
1873
|
},
|
|
1708
1874
|
m.id
|
|
1709
1875
|
)),
|
|
1710
|
-
state.isStreaming && state.actionsLoading ? /* @__PURE__ */
|
|
1876
|
+
state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx40(ActionButtons, { actions: [], loading: true, onAct: () => {
|
|
1711
1877
|
} }) : null,
|
|
1712
|
-
state.status ? /* @__PURE__ */
|
|
1713
|
-
state.error ? /* @__PURE__ */
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
/* @__PURE__ */
|
|
1878
|
+
state.status ? /* @__PURE__ */ jsx40("div", { className: "ss-w-status", children: state.status }) : null,
|
|
1879
|
+
state.error ? /* @__PURE__ */ jsxs35("div", { className: "ss-w-error", role: "alert", children: [
|
|
1880
|
+
/* @__PURE__ */ jsx40("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
|
|
1881
|
+
/* @__PURE__ */ jsx40("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
|
|
1716
1882
|
] }) : null,
|
|
1717
|
-
!hasUserMessage && !hasDashboard ? /* @__PURE__ */
|
|
1718
|
-
/* @__PURE__ */
|
|
1883
|
+
!hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx40(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
|
|
1884
|
+
/* @__PURE__ */ jsx40("div", { ref: endRef })
|
|
1719
1885
|
]
|
|
1720
1886
|
}
|
|
1721
1887
|
);
|
|
@@ -1723,7 +1889,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
|
|
|
1723
1889
|
|
|
1724
1890
|
// src/components/ChatInput.tsx
|
|
1725
1891
|
import { useState as useState7 } from "react";
|
|
1726
|
-
import { jsx as
|
|
1892
|
+
import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
1727
1893
|
function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
|
|
1728
1894
|
const [value, setValue] = useState7("");
|
|
1729
1895
|
const submit = () => {
|
|
@@ -1738,18 +1904,18 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
1738
1904
|
submit();
|
|
1739
1905
|
}
|
|
1740
1906
|
};
|
|
1741
|
-
return /* @__PURE__ */
|
|
1742
|
-
/* @__PURE__ */
|
|
1743
|
-
streaming ? /* @__PURE__ */
|
|
1907
|
+
return /* @__PURE__ */ jsxs36("div", { className: "ss-w-input", children: [
|
|
1908
|
+
/* @__PURE__ */ jsx41("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
|
|
1909
|
+
streaming ? /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx41("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx41("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx41(SendIcon, {}) })
|
|
1744
1910
|
] });
|
|
1745
1911
|
}
|
|
1746
1912
|
|
|
1747
1913
|
// src/components/ChatPanel.tsx
|
|
1748
|
-
import { jsx as
|
|
1914
|
+
import { jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
1749
1915
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
1750
1916
|
function useIsModal() {
|
|
1751
1917
|
const [modal, setModal] = useState8(false);
|
|
1752
|
-
|
|
1918
|
+
useEffect6(() => {
|
|
1753
1919
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
1754
1920
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
1755
1921
|
const sync = () => setModal(mql.matches);
|
|
@@ -1762,17 +1928,17 @@ function useIsModal() {
|
|
|
1762
1928
|
function ChatPanel({ controller, ui, slip, onDashboardTap }) {
|
|
1763
1929
|
const { state, close, stop, send } = controller;
|
|
1764
1930
|
const isModal = useIsModal();
|
|
1765
|
-
|
|
1931
|
+
useEffect6(() => {
|
|
1766
1932
|
const onKey = (e) => {
|
|
1767
1933
|
if (e.key === "Escape") close();
|
|
1768
1934
|
};
|
|
1769
1935
|
window.addEventListener("keydown", onKey);
|
|
1770
1936
|
return () => window.removeEventListener("keydown", onKey);
|
|
1771
1937
|
}, [close]);
|
|
1772
|
-
return /* @__PURE__ */
|
|
1773
|
-
/* @__PURE__ */
|
|
1774
|
-
/* @__PURE__ */
|
|
1775
|
-
/* @__PURE__ */
|
|
1938
|
+
return /* @__PURE__ */ jsxs37("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
1939
|
+
/* @__PURE__ */ jsx42(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
|
|
1940
|
+
/* @__PURE__ */ jsx42(MessageList, { controller, ui, slip, onDashboardTap }),
|
|
1941
|
+
/* @__PURE__ */ jsx42(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
|
|
1776
1942
|
] });
|
|
1777
1943
|
}
|
|
1778
1944
|
|
|
@@ -2267,10 +2433,52 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
2267
2433
|
.ss-w-fandash-postmatch-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
2268
2434
|
.ss-w-fandash-postmatch-next-opponent { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
|
|
2269
2435
|
.ss-w-fandash-postmatch-next-when { flex: none; font-size: 11.5px; color: var(--ss-w-faint); }
|
|
2436
|
+
|
|
2437
|
+
/* resting next: the between-matches fixture teaser -- when-label left, competition badge right */
|
|
2438
|
+
.ss-w-fandash-resting-next-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
2439
|
+
.ss-w-fandash-resting-next-when { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 650; }
|
|
2440
|
+
.ss-w-fandash-resting-next-competition { flex: none; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--ss-w-faint); }
|
|
2441
|
+
|
|
2442
|
+
/* resting standings: position figure left, W-D-L/points record right */
|
|
2443
|
+
.ss-w-fandash-resting-standings-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
|
|
2444
|
+
.ss-w-fandash-resting-standings-position { font-size: 15px; font-weight: 750; color: var(--ss-w-ink); }
|
|
2445
|
+
.ss-w-fandash-resting-standings-record { font-size: 11.5px; color: var(--ss-w-faint); text-align: right; }
|
|
2446
|
+
|
|
2447
|
+
/* resting season: top scorer / key player / season tally as stacked rows */
|
|
2448
|
+
.ss-w-fandash-resting-season-rows { display: flex; flex-direction: column; gap: 4px; }
|
|
2449
|
+
.ss-w-fandash-resting-season-row { font-size: 12.5px; color: var(--ss-w-ink); }
|
|
2450
|
+
|
|
2451
|
+
/* resting form: reuses the fixture last-five pill classes (ss-w-fandash-form / -form-pill) */
|
|
2452
|
+
.ss-w-fandash-resting-form .ss-w-fandash-form { margin-top: 2px; }
|
|
2453
|
+
|
|
2454
|
+
/* live score: reuses the post-match scoreboard classes for the team/score row, with its own
|
|
2455
|
+
meta row -- minute called out in the accent color, status and competition beside it */
|
|
2456
|
+
.ss-w-fandash-live-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11.5px; color: var(--ss-w-faint); }
|
|
2457
|
+
.ss-w-fandash-live-minute { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-accent); }
|
|
2458
|
+
.ss-w-fandash-live-status { font-weight: 650; color: var(--ss-w-ink); }
|
|
2459
|
+
.ss-w-fandash-live-competition { color: var(--ss-w-faint); }
|
|
2460
|
+
|
|
2461
|
+
/* live timeline: a plain event list, the club's own events picked out in the accent color --
|
|
2462
|
+
same mechanism as the post-match goal list, plus a minute column */
|
|
2463
|
+
.ss-w-fandash-live-event-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
|
|
2464
|
+
.ss-w-fandash-live-event { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--ss-w-faint); }
|
|
2465
|
+
.ss-w-fandash-live-event--club { color: var(--ss-w-ink); font-weight: 650; }
|
|
2466
|
+
.ss-w-fandash-live-event-minute { flex: none; font-variant-numeric: tabular-nums; color: var(--ss-w-faint); }
|
|
2467
|
+
.ss-w-fandash-live-event--club .ss-w-fandash-live-event-minute { color: var(--ss-w-accent); }
|
|
2468
|
+
.ss-w-fandash-live-event-text { min-width: 0; }
|
|
2469
|
+
|
|
2470
|
+
/* live stats: club value / stat label / opponent value, same 3-column layout as post-match
|
|
2471
|
+
stats, plus an optional momentum line underneath */
|
|
2472
|
+
.ss-w-fandash-live-stat-rows { display: flex; flex-direction: column; gap: 5px; }
|
|
2473
|
+
.ss-w-fandash-live-stat-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px; }
|
|
2474
|
+
.ss-w-fandash-live-stat-value { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
2475
|
+
.ss-w-fandash-live-stat-row .ss-w-fandash-live-stat-value:last-child { text-align: right; }
|
|
2476
|
+
.ss-w-fandash-live-stat-label { font-size: 10.5px; text-align: center; color: var(--ss-w-faint); text-transform: uppercase; letter-spacing: .03em; }
|
|
2477
|
+
.ss-w-fandash-live-momentum { font-size: 11.5px; font-weight: 650; color: var(--ss-w-accent); }
|
|
2270
2478
|
`;
|
|
2271
2479
|
|
|
2272
2480
|
// src/StatsWidget.tsx
|
|
2273
|
-
import { jsx as
|
|
2481
|
+
import { jsx as jsx43 } from "react/jsx-runtime";
|
|
2274
2482
|
function buildClient(props) {
|
|
2275
2483
|
try {
|
|
2276
2484
|
if ("client" in props && props.client) {
|
|
@@ -2297,7 +2505,7 @@ function StatsWidget(props) {
|
|
|
2297
2505
|
const cfg = props.config;
|
|
2298
2506
|
const built = useMemo2(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
2299
2507
|
const ui = useMemo2(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
2300
|
-
const rootRef =
|
|
2508
|
+
const rootRef = useRef4(null);
|
|
2301
2509
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
|
|
2302
2510
|
const slip = useMemo2(
|
|
2303
2511
|
() => ({
|
|
@@ -2316,14 +2524,14 @@ function StatsWidget(props) {
|
|
|
2316
2524
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
|
|
2317
2525
|
[built]
|
|
2318
2526
|
);
|
|
2319
|
-
|
|
2527
|
+
useEffect7(() => {
|
|
2320
2528
|
if (!built || !ui.injectStyles) return;
|
|
2321
2529
|
const node = rootRef.current?.getRootNode();
|
|
2322
2530
|
if (node) injectStyles(node);
|
|
2323
2531
|
}, [built, ui.injectStyles]);
|
|
2324
2532
|
if (!built) return null;
|
|
2325
2533
|
const attrs = themeAttrs(ui);
|
|
2326
|
-
return /* @__PURE__ */
|
|
2534
|
+
return /* @__PURE__ */ jsx43("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx43(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx43(
|
|
2327
2535
|
ChatFab,
|
|
2328
2536
|
{
|
|
2329
2537
|
label: ui.launcherLabel,
|