@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/dist/index.js CHANGED
@@ -91,6 +91,7 @@ var STRINGS = {
91
91
  accordionSuspended: "Suspended",
92
92
  tabToday: "Today",
93
93
  sectionLive: "Live",
94
+ halfTime: "HT",
94
95
  staleStates: "Live scores can't be updated \u2014 showing the last known state."
95
96
  },
96
97
  tr: {
@@ -168,6 +169,7 @@ var STRINGS = {
168
169
  accordionSuspended: "Ask\u0131da",
169
170
  tabToday: "Bug\xFCn",
170
171
  sectionLive: "Canl\u0131",
172
+ halfTime: "\u0130Y",
171
173
  staleStates: "Canl\u0131 skorlar g\xFCncellenemiyor \u2014 son bilinen durum g\xF6steriliyor."
172
174
  }
173
175
  };
@@ -246,6 +248,7 @@ function normalizeUi(cfg) {
246
248
  tabOdds: strings.tabOdds,
247
249
  tabToday: strings.tabToday,
248
250
  sectionLive: strings.sectionLive,
251
+ halfTime: strings.halfTime,
249
252
  staleStates: strings.staleStates,
250
253
  pinLabel: strings.pinLabel,
251
254
  unpinLabel: strings.unpinLabel,
@@ -1505,8 +1508,40 @@ function dueMatchIds(groups, states, nowMs) {
1505
1508
  return [...due];
1506
1509
  }
1507
1510
 
1511
+ // src/liveClock.ts
1512
+ var STALE_READING_MS = 5 * 6e4;
1513
+ function parseMinute(label) {
1514
+ if (!label) return null;
1515
+ const plus = /^(\d+)\+(\d+)'$/.exec(label);
1516
+ if (plus) return Number(plus[1]) + Number(plus[2]);
1517
+ const plain = /^(\d+)'$/.exec(label);
1518
+ return plain ? Number(plain[1]) : null;
1519
+ }
1520
+ function readingAgeMs(state, nowMs) {
1521
+ if (!state?.stateAsOfUtc) return null;
1522
+ const t = Date.parse(state.stateAsOfUtc);
1523
+ return Number.isNaN(t) ? null : Math.max(0, nowMs - t);
1524
+ }
1525
+ function isReadingStale(state, nowMs) {
1526
+ if (state?.isHalfTime) return false;
1527
+ const age = readingAgeMs(state, nowMs);
1528
+ return age !== null && age > STALE_READING_MS;
1529
+ }
1530
+ function displayMinute(state, nowMs) {
1531
+ if (!state || state.phase !== "live") return null;
1532
+ if (state.isHalfTime) return null;
1533
+ if (isReadingStale(state, nowMs)) return null;
1534
+ const base = parseMinute(state.minuteLabel);
1535
+ if (base === null) return null;
1536
+ const age = readingAgeMs(state, nowMs);
1537
+ if (age === null) return state.minuteLabel ?? null;
1538
+ const advanced = base + Math.floor(age / 6e4);
1539
+ return `${advanced}'`;
1540
+ }
1541
+
1508
1542
  // src/useMatchDiscovery.ts
1509
1543
  var POLL_MS = 6e4;
1544
+ var CLOCK_MS = 1e4;
1510
1545
  var ROLLOVER_CHECK_MS = 6e4;
1511
1546
  var STALE_AFTER_FAILURES = 2;
1512
1547
  function localDateOf(now) {
@@ -1519,6 +1554,7 @@ function useMatchDiscovery(opts) {
1519
1554
  const [groups, setGroups] = useState2([]);
1520
1555
  const [states, setStates] = useState2({});
1521
1556
  const [failures, setFailures] = useState2(0);
1557
+ const [nowMs, setNowMs] = useState2(() => Date.now());
1522
1558
  const [expanded, setExpanded] = useState2({});
1523
1559
  const [touched, setTouched] = useState2({});
1524
1560
  const groupsRef = useRef4(groups);
@@ -1596,16 +1632,21 @@ function useMatchDiscovery(opts) {
1596
1632
  }, POLL_MS);
1597
1633
  return () => clearInterval(id);
1598
1634
  }, [opts.isOpen, tick, groups]);
1635
+ useEffect4(() => {
1636
+ if (!opts.isOpen) return;
1637
+ const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
1638
+ return () => clearInterval(id);
1639
+ }, [opts.isOpen]);
1599
1640
  const stale = failures >= STALE_AFTER_FAILURES;
1600
1641
  const liveCount = useMemo2(
1601
- () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
1602
- [states, stale]
1642
+ () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live" && !isReadingStale(s, nowMs)).length,
1643
+ [states, stale, nowMs]
1603
1644
  );
1604
1645
  const toggleGroup = useCallback3((competitionId) => {
1605
1646
  setTouched((prev) => ({ ...prev, [competitionId]: true }));
1606
1647
  setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
1607
1648
  }, []);
1608
- return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
1649
+ return { groups, states, nowMs, liveCount, stale, expanded, toggleGroup, _tick: tick };
1609
1650
  }
1610
1651
 
1611
1652
  // src/eventLogContext.ts
@@ -2963,7 +3004,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
2963
3004
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
2964
3005
  }
2965
3006
  function useLiveClock(minute, clockRunning, receivedAtMs) {
2966
- const [displayMinute, setDisplayMinute] = useState15(
3007
+ const [displayMinute2, setDisplayMinute] = useState15(
2967
3008
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
2968
3009
  );
2969
3010
  useEffect9(() => {
@@ -2974,7 +3015,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
2974
3015
  }, TICK_MS);
2975
3016
  return () => window.clearInterval(id);
2976
3017
  }, [minute, clockRunning, receivedAtMs]);
2977
- return displayMinute;
3018
+ return displayMinute2;
2978
3019
  }
2979
3020
 
2980
3021
  // src/components/dashboard/LiveScoreCard.tsx
@@ -3006,7 +3047,7 @@ function LiveScoreCard({
3006
3047
  if (!seedRef.current || seedRef.current.key !== seedKey) {
3007
3048
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
3008
3049
  }
3009
- const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3050
+ const displayMinute2 = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3010
3051
  const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
3011
3052
  const scorers = scorersLine(goalEvents);
3012
3053
  return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
@@ -3016,7 +3057,7 @@ function LiveScoreCard({
3016
3057
  /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
3017
3058
  data.statusLabel
3018
3059
  ] }),
3019
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
3060
+ /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute2}${minuteSuffix}` }),
3020
3061
  data.competition ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
3021
3062
  ] }),
3022
3063
  /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-main", children: [
@@ -3698,12 +3739,14 @@ function Side({ name, crest }) {
3698
3739
  /* @__PURE__ */ jsx49("span", { className: "ss-w-drow-nm", children: name })
3699
3740
  ] });
3700
3741
  }
3701
- function FixtureRow({ match, state, stale, ui, monitor }) {
3702
- const isLive = state?.phase === "live" && !stale;
3742
+ function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
3743
+ const readingStale = isReadingStale(state, nowMs);
3744
+ const isLive = state?.phase === "live" && !stale && !readingStale;
3703
3745
  const hasScore = (state?.phase === "live" || state?.phase === "fullTime") && typeof state.homeScore === "number" && typeof state.awayScore === "number";
3704
- const when = stale && state?.phase === "live" ? "\u2014" : isLive ? state?.minuteLabel ?? "\u2014" : kickoffLabel(match.kickoffUnix);
3705
- return /* @__PURE__ */ jsxs44("div", { className: isLive ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
3706
- /* @__PURE__ */ jsx49("span", { className: isLive ? "ss-w-drow-when ss-w-drow-when--live" : "ss-w-drow-when", children: when }),
3746
+ const ticked = displayMinute(state, nowMs);
3747
+ const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : kickoffLabel(match.kickoffUnix);
3748
+ return /* @__PURE__ */ jsxs44("div", { className: isLive || state?.isHalfTime ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
3749
+ /* @__PURE__ */ jsx49("span", { className: isLive || state?.isHalfTime ? "ss-w-drow-when ss-w-drow-when--live" : "ss-w-drow-when", children: when }),
3707
3750
  /* @__PURE__ */ jsxs44("span", { className: "ss-w-drow-fx", children: [
3708
3751
  /* @__PURE__ */ jsx49(Side, { name: match.homeTeamName, crest: match.homeTeamImagePath }),
3709
3752
  /* @__PURE__ */ jsx49("span", { className: "ss-w-drow-dash", children: "\u2013" }),
@@ -3716,7 +3759,7 @@ function FixtureRow({ match, state, stale, ui, monitor }) {
3716
3759
 
3717
3760
  // src/components/CompetitionGroup.tsx
3718
3761
  import { jsx as jsx50, jsxs as jsxs45 } from "react/jsx-runtime";
3719
- function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
3762
+ function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, monitor, disambiguate = false }) {
3720
3763
  const own = group.matches.filter((m) => states[m.matchId]?.phase !== "live" || stale);
3721
3764
  const hasLive = own.length !== group.matches.length;
3722
3765
  return /* @__PURE__ */ jsxs45("div", { className: "ss-w-grp", children: [
@@ -3733,14 +3776,14 @@ function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, d
3733
3776
  ] }),
3734
3777
  /* @__PURE__ */ jsx50("span", { className: "ss-w-grp-count", children: own.length })
3735
3778
  ] }),
3736
- open && /* @__PURE__ */ jsx50("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ jsx50(FixtureRow, { match: m, state: states[m.matchId], stale, ui, monitor }, m.matchId)) })
3779
+ open && /* @__PURE__ */ jsx50("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ jsx50(FixtureRow, { match: m, state: states[m.matchId], stale, nowMs, ui, monitor }, m.matchId)) })
3737
3780
  ] });
3738
3781
  }
3739
3782
 
3740
3783
  // src/components/TodayPanel.tsx
3741
3784
  import { Fragment as Fragment7, jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
3742
3785
  function TodayPanel({ discovery, ui, monitor }) {
3743
- const { groups, states, stale, expanded, toggleGroup } = discovery;
3786
+ const { groups, states, nowMs, stale, expanded, toggleGroup } = discovery;
3744
3787
  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);
3745
3788
  const nameCounts = groups.reduce((acc, g) => {
3746
3789
  acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
@@ -3759,6 +3802,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3759
3802
  match,
3760
3803
  state: states[match.matchId],
3761
3804
  stale,
3805
+ nowMs,
3762
3806
  ui,
3763
3807
  monitor
3764
3808
  },
@@ -3774,6 +3818,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3774
3818
  disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
3775
3819
  states,
3776
3820
  stale,
3821
+ nowMs,
3777
3822
  ui,
3778
3823
  monitor
3779
3824
  },