@sensiblestats/widget-react 0.47.2 → 0.48.1
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/dist/index.cjs +35 -66
- 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 +35 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +6 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.48.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A live match no longer loses its minute and drop out of the live section just because its score has not changed recently. The per-match staleness check read a last-changed timestamp as if it were a last-polled one.
|
|
8
|
+
|
|
9
|
+
## 0.48.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Today-tab fixture rows now read like a scoreboard: the score sits between the two teams instead of after them, a finished match shows FT (MS in Turkish) in place of its kickoff time, and a finished match no longer offers a pin unless it is already pinned.
|
|
14
|
+
|
|
3
15
|
## 0.47.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -129,6 +129,7 @@ var STRINGS = {
|
|
|
129
129
|
tabToday: "Today",
|
|
130
130
|
sectionLive: "Live",
|
|
131
131
|
halfTime: "HT",
|
|
132
|
+
fullTime: "FT",
|
|
132
133
|
staleStates: "Live scores can't be updated \u2014 showing the last known state."
|
|
133
134
|
},
|
|
134
135
|
tr: {
|
|
@@ -207,6 +208,7 @@ var STRINGS = {
|
|
|
207
208
|
tabToday: "Bug\xFCn",
|
|
208
209
|
sectionLive: "Canl\u0131",
|
|
209
210
|
halfTime: "\u0130Y",
|
|
211
|
+
fullTime: "MS",
|
|
210
212
|
staleStates: "Canl\u0131 skorlar g\xFCncellenemiyor \u2014 son bilinen durum g\xF6steriliyor."
|
|
211
213
|
}
|
|
212
214
|
};
|
|
@@ -286,6 +288,7 @@ function normalizeUi(cfg) {
|
|
|
286
288
|
tabToday: strings.tabToday,
|
|
287
289
|
sectionLive: strings.sectionLive,
|
|
288
290
|
halfTime: strings.halfTime,
|
|
291
|
+
fullTime: strings.fullTime,
|
|
289
292
|
staleStates: strings.staleStates,
|
|
290
293
|
pinLabel: strings.pinLabel,
|
|
291
294
|
unpinLabel: strings.unpinLabel,
|
|
@@ -1545,43 +1548,8 @@ function dueMatchIds(groups, states, nowMs) {
|
|
|
1545
1548
|
return [...due];
|
|
1546
1549
|
}
|
|
1547
1550
|
|
|
1548
|
-
// src/liveClock.ts
|
|
1549
|
-
var STALE_READING_MS = 5 * 6e4;
|
|
1550
|
-
function parseMinute(label) {
|
|
1551
|
-
if (!label) return null;
|
|
1552
|
-
const plus = /^(\d+)\+(\d+)'$/.exec(label);
|
|
1553
|
-
if (plus) return Number(plus[1]) + Number(plus[2]);
|
|
1554
|
-
const plain = /^(\d+)'$/.exec(label);
|
|
1555
|
-
return plain ? Number(plain[1]) : null;
|
|
1556
|
-
}
|
|
1557
|
-
function readingAgeMs(state, nowMs) {
|
|
1558
|
-
if (!state?.stateAsOfUtc) return null;
|
|
1559
|
-
const t = Date.parse(state.stateAsOfUtc);
|
|
1560
|
-
return Number.isNaN(t) ? null : Math.max(0, nowMs - t);
|
|
1561
|
-
}
|
|
1562
|
-
function isReadingStale(state, nowMs) {
|
|
1563
|
-
if (state?.isHalfTime) return false;
|
|
1564
|
-
const age = readingAgeMs(state, nowMs);
|
|
1565
|
-
return age !== null && age > STALE_READING_MS;
|
|
1566
|
-
}
|
|
1567
|
-
function displayMinute(state, nowMs) {
|
|
1568
|
-
if (!state || state.phase !== "live") return null;
|
|
1569
|
-
if (state.isHalfTime) return null;
|
|
1570
|
-
if (isReadingStale(state, nowMs)) return null;
|
|
1571
|
-
const base = parseMinute(state.minuteLabel);
|
|
1572
|
-
if (base === null) return null;
|
|
1573
|
-
const age = readingAgeMs(state, nowMs);
|
|
1574
|
-
if (age === null) return state.minuteLabel ?? null;
|
|
1575
|
-
const advanced = base + Math.floor(age / 6e4);
|
|
1576
|
-
return `${advanced}'`;
|
|
1577
|
-
}
|
|
1578
|
-
function isTreatedLive(state, pollStale, nowMs) {
|
|
1579
|
-
return state?.phase === "live" && !pollStale && !isReadingStale(state, nowMs);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
1551
|
// src/useMatchDiscovery.ts
|
|
1583
1552
|
var POLL_MS = 6e4;
|
|
1584
|
-
var CLOCK_MS = 1e4;
|
|
1585
1553
|
var ROLLOVER_CHECK_MS = 6e4;
|
|
1586
1554
|
var STALE_AFTER_FAILURES = 2;
|
|
1587
1555
|
function localDateOf(now) {
|
|
@@ -1594,7 +1562,6 @@ function useMatchDiscovery(opts) {
|
|
|
1594
1562
|
const [groups, setGroups] = (0, import_react4.useState)([]);
|
|
1595
1563
|
const [states, setStates] = (0, import_react4.useState)({});
|
|
1596
1564
|
const [failures, setFailures] = (0, import_react4.useState)(0);
|
|
1597
|
-
const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
|
|
1598
1565
|
const [expanded, setExpanded] = (0, import_react4.useState)({});
|
|
1599
1566
|
const [touched, setTouched] = (0, import_react4.useState)({});
|
|
1600
1567
|
const groupsRef = (0, import_react4.useRef)(groups);
|
|
@@ -1672,21 +1639,16 @@ function useMatchDiscovery(opts) {
|
|
|
1672
1639
|
}, POLL_MS);
|
|
1673
1640
|
return () => clearInterval(id);
|
|
1674
1641
|
}, [opts.isOpen, tick, groups]);
|
|
1675
|
-
(0, import_react4.useEffect)(() => {
|
|
1676
|
-
if (!opts.isOpen) return;
|
|
1677
|
-
const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
|
|
1678
|
-
return () => clearInterval(id);
|
|
1679
|
-
}, [opts.isOpen]);
|
|
1680
1642
|
const stale = failures >= STALE_AFTER_FAILURES;
|
|
1681
1643
|
const liveCount = (0, import_react4.useMemo)(
|
|
1682
|
-
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live"
|
|
1683
|
-
[states, stale
|
|
1644
|
+
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
|
|
1645
|
+
[states, stale]
|
|
1684
1646
|
);
|
|
1685
1647
|
const toggleGroup = (0, import_react4.useCallback)((competitionId) => {
|
|
1686
1648
|
setTouched((prev) => ({ ...prev, [competitionId]: true }));
|
|
1687
1649
|
setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
|
|
1688
1650
|
}, []);
|
|
1689
|
-
return { groups, states,
|
|
1651
|
+
return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
|
|
1690
1652
|
}
|
|
1691
1653
|
|
|
1692
1654
|
// src/eventLogContext.ts
|
|
@@ -3044,7 +3006,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
3044
3006
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
3045
3007
|
}
|
|
3046
3008
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
3047
|
-
const [
|
|
3009
|
+
const [displayMinute, setDisplayMinute] = (0, import_react19.useState)(
|
|
3048
3010
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
3049
3011
|
);
|
|
3050
3012
|
(0, import_react19.useEffect)(() => {
|
|
@@ -3055,7 +3017,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
|
3055
3017
|
}, TICK_MS);
|
|
3056
3018
|
return () => window.clearInterval(id);
|
|
3057
3019
|
}, [minute, clockRunning, receivedAtMs]);
|
|
3058
|
-
return
|
|
3020
|
+
return displayMinute;
|
|
3059
3021
|
}
|
|
3060
3022
|
|
|
3061
3023
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
@@ -3087,7 +3049,7 @@ function LiveScoreCard({
|
|
|
3087
3049
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
3088
3050
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
3089
3051
|
}
|
|
3090
|
-
const
|
|
3052
|
+
const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
|
|
3091
3053
|
const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
|
|
3092
3054
|
const scorers = scorersLine(goalEvents);
|
|
3093
3055
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
@@ -3097,7 +3059,7 @@ function LiveScoreCard({
|
|
|
3097
3059
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
|
|
3098
3060
|
data.statusLabel
|
|
3099
3061
|
] }),
|
|
3100
|
-
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${
|
|
3062
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
|
|
3101
3063
|
data.competition ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
3102
3064
|
] }),
|
|
3103
3065
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-live-main", children: [
|
|
@@ -3779,28 +3741,34 @@ function Side({ name, crest }) {
|
|
|
3779
3741
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-nm", children: name })
|
|
3780
3742
|
] });
|
|
3781
3743
|
}
|
|
3782
|
-
function FixtureRow({ match, state, stale,
|
|
3783
|
-
const
|
|
3784
|
-
const isLive = state?.phase === "live" && !stale && !readingStale;
|
|
3744
|
+
function FixtureRow({ match, state, stale, ui, monitor }) {
|
|
3745
|
+
const isLive = state?.phase === "live" && !stale;
|
|
3785
3746
|
const hasScore = (state?.phase === "live" || state?.phase === "fullTime") && typeof state.homeScore === "number" && typeof state.awayScore === "number";
|
|
3786
|
-
const ticked =
|
|
3787
|
-
const
|
|
3747
|
+
const ticked = state?.phase === "live" ? state.minuteLabel ?? null : null;
|
|
3748
|
+
const isFinished = state?.phase === "fullTime";
|
|
3749
|
+
const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : isFinished ? ui.fullTime : kickoffLabel(match.kickoffUnix);
|
|
3750
|
+
const pin = { kind: "match", matchId: match.matchId };
|
|
3751
|
+
const showPin = !isFinished || monitor.isPinned(pinKey(pin));
|
|
3788
3752
|
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: isLive || state?.isHalfTime ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
|
|
3789
3753
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: isLive || state?.isHalfTime ? "ss-w-drow-when ss-w-drow-when--live" : "ss-w-drow-when", children: when }),
|
|
3790
3754
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "ss-w-drow-fx", children: [
|
|
3791
3755
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Side, { name: match.homeTeamName, crest: match.homeTeamImagePath }),
|
|
3792
|
-
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-
|
|
3756
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: hasScore ? "ss-w-drow-sc" : "ss-w-drow-sc ss-w-drow-sc--idle", children: hasScore ? `${state.homeScore}\u2013${state.awayScore}` : "\u2013" }),
|
|
3793
3757
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Side, { name: match.awayTeamName, crest: match.awayTeamImagePath })
|
|
3794
3758
|
] }),
|
|
3795
|
-
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3796
|
-
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(PinToggle, { ui, monitor, pin: { kind: "match", matchId: match.matchId } })
|
|
3759
|
+
showPin ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(PinToggle, { ui, monitor, pin }) : null
|
|
3797
3760
|
] });
|
|
3798
3761
|
}
|
|
3799
3762
|
|
|
3763
|
+
// src/liveClock.ts
|
|
3764
|
+
function isTreatedLive(state, pollStale) {
|
|
3765
|
+
return state?.phase === "live" && !pollStale;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3800
3768
|
// src/components/CompetitionGroup.tsx
|
|
3801
3769
|
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
3802
|
-
function CompetitionGroup({ group, open, onToggle, states, stale,
|
|
3803
|
-
const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale
|
|
3770
|
+
function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
|
|
3771
|
+
const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale));
|
|
3804
3772
|
const hasLive = own.length !== group.matches.length;
|
|
3805
3773
|
if (own.length === 0) {
|
|
3806
3774
|
return null;
|
|
@@ -3819,15 +3787,15 @@ function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, mon
|
|
|
3819
3787
|
] }),
|
|
3820
3788
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "ss-w-grp-count", children: own.length })
|
|
3821
3789
|
] }),
|
|
3822
|
-
open && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FixtureRow, { match: m, state: states[m.matchId], stale,
|
|
3790
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FixtureRow, { match: m, state: states[m.matchId], stale, ui, monitor }, m.matchId)) })
|
|
3823
3791
|
] });
|
|
3824
3792
|
}
|
|
3825
3793
|
|
|
3826
3794
|
// src/components/TodayPanel.tsx
|
|
3827
3795
|
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
3828
3796
|
function TodayPanel({ discovery, ui, monitor }) {
|
|
3829
|
-
const { groups, states,
|
|
3830
|
-
const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale
|
|
3797
|
+
const { groups, states, stale, expanded, toggleGroup } = discovery;
|
|
3798
|
+
const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale)).map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
|
|
3831
3799
|
const nameCounts = groups.reduce((acc, g) => {
|
|
3832
3800
|
acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
|
|
3833
3801
|
return acc;
|
|
@@ -3845,7 +3813,6 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3845
3813
|
match,
|
|
3846
3814
|
state: states[match.matchId],
|
|
3847
3815
|
stale,
|
|
3848
|
-
nowMs,
|
|
3849
3816
|
ui,
|
|
3850
3817
|
monitor
|
|
3851
3818
|
},
|
|
@@ -3861,7 +3828,6 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3861
3828
|
disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
|
|
3862
3829
|
states,
|
|
3863
3830
|
stale,
|
|
3864
|
-
nowMs,
|
|
3865
3831
|
ui,
|
|
3866
3832
|
monitor
|
|
3867
3833
|
},
|
|
@@ -4786,7 +4752,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
4786
4752
|
.ss-w-grp-count { margin-left: auto; flex: none; font-size: 10.5px; font-weight: 700; color: var(--ss-w-faint); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 999px; padding: 1px 7px; font-variant-numeric: tabular-nums; }
|
|
4787
4753
|
.ss-w-grp-body { display: flex; flex-direction: column; padding: 2px 11px 6px; }
|
|
4788
4754
|
|
|
4789
|
-
.ss-w-drow { display: grid; grid-template-columns: 42px minmax(0,1fr)
|
|
4755
|
+
.ss-w-drow { display: grid; grid-template-columns: 42px minmax(0,1fr) 22px; align-items: center; gap: 9px; padding: 7px 0; border-bottom: 1px solid var(--ss-w-line); }
|
|
4790
4756
|
.ss-w-drow:last-child { border-bottom: none; }
|
|
4791
4757
|
.ss-w-drow-when { font-size: 11px; font-weight: 700; color: var(--ss-w-faint); font-variant-numeric: tabular-nums; }
|
|
4792
4758
|
.ss-w-drow-when--live { color: var(--ss-w-neg); }
|
|
@@ -4795,8 +4761,11 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
4795
4761
|
.ss-w-drow-fx { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
4796
4762
|
.ss-w-drow-side { display: flex; align-items: center; gap: 5px; min-width: 0; flex: 1 1 0; }
|
|
4797
4763
|
.ss-w-drow-nm { font-size: 12.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4798
|
-
.ss-w-drow-
|
|
4799
|
-
|
|
4764
|
+
/* Centre slot of .ss-w-drow-fx, between the two sides: flex:none so a widening score never
|
|
4765
|
+
steals width the team names need to avoid ellipsis. min-width holds the slot at the width of a
|
|
4766
|
+
one-digit scoreline so an unscored row's bare dash does not pull the column out of line -- the
|
|
4767
|
+
scores read as a column down the list, not a jitter. */
|
|
4768
|
+
.ss-w-drow-sc { flex: none; min-width: 26px; text-align: center; font-size: 12.5px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ss-w-ink); }
|
|
4800
4769
|
.ss-w-drow-sc--idle { color: var(--ss-w-faint); font-weight: 600; }
|
|
4801
4770
|
.ss-w-drow--live { background: color-mix(in srgb, var(--ss-w-neg) 5%, transparent); border-radius: 8px; padding: 7px 6px; margin: 0 -6px; border-bottom: none; }
|
|
4802
4771
|
.ss-w-drow-crest { width: 16px; height: 16px; flex: none; border-radius: 50%; object-fit: contain; background: var(--ss-w-bubble); }
|