@sensiblestats/widget-react 0.46.0 → 0.47.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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.47.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Shows a half-time label instead of a frozen 45', ages the live minute against when the reading was
8
+ actually taken so it no longer sits up to two minutes behind, and stops asserting a clock for a
9
+ single match whose sync window has stalled.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+ - @sensiblestats/widget-sdk@0.35.0
15
+
3
16
  ## 0.46.0
4
17
 
5
18
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -128,6 +128,7 @@ var STRINGS = {
128
128
  accordionSuspended: "Suspended",
129
129
  tabToday: "Today",
130
130
  sectionLive: "Live",
131
+ halfTime: "HT",
131
132
  staleStates: "Live scores can't be updated \u2014 showing the last known state."
132
133
  },
133
134
  tr: {
@@ -205,6 +206,7 @@ var STRINGS = {
205
206
  accordionSuspended: "Ask\u0131da",
206
207
  tabToday: "Bug\xFCn",
207
208
  sectionLive: "Canl\u0131",
209
+ halfTime: "\u0130Y",
208
210
  staleStates: "Canl\u0131 skorlar g\xFCncellenemiyor \u2014 son bilinen durum g\xF6steriliyor."
209
211
  }
210
212
  };
@@ -283,6 +285,7 @@ function normalizeUi(cfg) {
283
285
  tabOdds: strings.tabOdds,
284
286
  tabToday: strings.tabToday,
285
287
  sectionLive: strings.sectionLive,
288
+ halfTime: strings.halfTime,
286
289
  staleStates: strings.staleStates,
287
290
  pinLabel: strings.pinLabel,
288
291
  unpinLabel: strings.unpinLabel,
@@ -1542,8 +1545,40 @@ function dueMatchIds(groups, states, nowMs) {
1542
1545
  return [...due];
1543
1546
  }
1544
1547
 
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
+
1545
1579
  // src/useMatchDiscovery.ts
1546
1580
  var POLL_MS = 6e4;
1581
+ var CLOCK_MS = 1e4;
1547
1582
  var ROLLOVER_CHECK_MS = 6e4;
1548
1583
  var STALE_AFTER_FAILURES = 2;
1549
1584
  function localDateOf(now) {
@@ -1556,6 +1591,7 @@ function useMatchDiscovery(opts) {
1556
1591
  const [groups, setGroups] = (0, import_react4.useState)([]);
1557
1592
  const [states, setStates] = (0, import_react4.useState)({});
1558
1593
  const [failures, setFailures] = (0, import_react4.useState)(0);
1594
+ const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
1559
1595
  const [expanded, setExpanded] = (0, import_react4.useState)({});
1560
1596
  const [touched, setTouched] = (0, import_react4.useState)({});
1561
1597
  const groupsRef = (0, import_react4.useRef)(groups);
@@ -1633,16 +1669,21 @@ function useMatchDiscovery(opts) {
1633
1669
  }, POLL_MS);
1634
1670
  return () => clearInterval(id);
1635
1671
  }, [opts.isOpen, tick, groups]);
1672
+ (0, import_react4.useEffect)(() => {
1673
+ if (!opts.isOpen) return;
1674
+ const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
1675
+ return () => clearInterval(id);
1676
+ }, [opts.isOpen]);
1636
1677
  const stale = failures >= STALE_AFTER_FAILURES;
1637
1678
  const liveCount = (0, import_react4.useMemo)(
1638
- () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
1639
- [states, stale]
1679
+ () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live" && !isReadingStale(s, nowMs)).length,
1680
+ [states, stale, nowMs]
1640
1681
  );
1641
1682
  const toggleGroup = (0, import_react4.useCallback)((competitionId) => {
1642
1683
  setTouched((prev) => ({ ...prev, [competitionId]: true }));
1643
1684
  setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
1644
1685
  }, []);
1645
- return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
1686
+ return { groups, states, nowMs, liveCount, stale, expanded, toggleGroup, _tick: tick };
1646
1687
  }
1647
1688
 
1648
1689
  // src/eventLogContext.ts
@@ -3000,7 +3041,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
3000
3041
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
3001
3042
  }
3002
3043
  function useLiveClock(minute, clockRunning, receivedAtMs) {
3003
- const [displayMinute, setDisplayMinute] = (0, import_react19.useState)(
3044
+ const [displayMinute2, setDisplayMinute] = (0, import_react19.useState)(
3004
3045
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
3005
3046
  );
3006
3047
  (0, import_react19.useEffect)(() => {
@@ -3011,7 +3052,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
3011
3052
  }, TICK_MS);
3012
3053
  return () => window.clearInterval(id);
3013
3054
  }, [minute, clockRunning, receivedAtMs]);
3014
- return displayMinute;
3055
+ return displayMinute2;
3015
3056
  }
3016
3057
 
3017
3058
  // src/components/dashboard/LiveScoreCard.tsx
@@ -3043,7 +3084,7 @@ function LiveScoreCard({
3043
3084
  if (!seedRef.current || seedRef.current.key !== seedKey) {
3044
3085
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
3045
3086
  }
3046
- const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3087
+ const displayMinute2 = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3047
3088
  const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
3048
3089
  const scorers = scorersLine(goalEvents);
3049
3090
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
@@ -3053,7 +3094,7 @@ function LiveScoreCard({
3053
3094
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
3054
3095
  data.statusLabel
3055
3096
  ] }),
3056
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
3097
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute2}${minuteSuffix}` }),
3057
3098
  data.competition ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
3058
3099
  ] }),
3059
3100
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-live-main", children: [
@@ -3735,12 +3776,14 @@ function Side({ name, crest }) {
3735
3776
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-nm", children: name })
3736
3777
  ] });
3737
3778
  }
3738
- function FixtureRow({ match, state, stale, ui, monitor }) {
3739
- const isLive = state?.phase === "live" && !stale;
3779
+ function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
3780
+ const readingStale = isReadingStale(state, nowMs);
3781
+ const isLive = state?.phase === "live" && !stale && !readingStale;
3740
3782
  const hasScore = (state?.phase === "live" || state?.phase === "fullTime") && typeof state.homeScore === "number" && typeof state.awayScore === "number";
3741
- const when = stale && state?.phase === "live" ? "\u2014" : isLive ? state?.minuteLabel ?? "\u2014" : kickoffLabel(match.kickoffUnix);
3742
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: isLive ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
3743
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: isLive ? "ss-w-drow-when ss-w-drow-when--live" : "ss-w-drow-when", children: when }),
3783
+ const ticked = displayMinute(state, nowMs);
3784
+ const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : kickoffLabel(match.kickoffUnix);
3785
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: isLive || state?.isHalfTime ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
3786
+ /* @__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 }),
3744
3787
  /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "ss-w-drow-fx", children: [
3745
3788
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Side, { name: match.homeTeamName, crest: match.homeTeamImagePath }),
3746
3789
  /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-dash", children: "\u2013" }),
@@ -3753,7 +3796,7 @@ function FixtureRow({ match, state, stale, ui, monitor }) {
3753
3796
 
3754
3797
  // src/components/CompetitionGroup.tsx
3755
3798
  var import_jsx_runtime50 = require("react/jsx-runtime");
3756
- function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
3799
+ function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, monitor, disambiguate = false }) {
3757
3800
  const own = group.matches.filter((m) => states[m.matchId]?.phase !== "live" || stale);
3758
3801
  const hasLive = own.length !== group.matches.length;
3759
3802
  return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "ss-w-grp", children: [
@@ -3770,14 +3813,14 @@ function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, d
3770
3813
  ] }),
3771
3814
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "ss-w-grp-count", children: own.length })
3772
3815
  ] }),
3773
- 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)) })
3816
+ 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, nowMs, ui, monitor }, m.matchId)) })
3774
3817
  ] });
3775
3818
  }
3776
3819
 
3777
3820
  // src/components/TodayPanel.tsx
3778
3821
  var import_jsx_runtime51 = require("react/jsx-runtime");
3779
3822
  function TodayPanel({ discovery, ui, monitor }) {
3780
- const { groups, states, stale, expanded, toggleGroup } = discovery;
3823
+ const { groups, states, nowMs, stale, expanded, toggleGroup } = discovery;
3781
3824
  const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => states[m.matchId]?.phase === "live").map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
3782
3825
  const nameCounts = groups.reduce((acc, g) => {
3783
3826
  acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
@@ -3796,6 +3839,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3796
3839
  match,
3797
3840
  state: states[match.matchId],
3798
3841
  stale,
3842
+ nowMs,
3799
3843
  ui,
3800
3844
  monitor
3801
3845
  },
@@ -3811,6 +3855,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3811
3855
  disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
3812
3856
  states,
3813
3857
  stale,
3858
+ nowMs,
3814
3859
  ui,
3815
3860
  monitor
3816
3861
  },