@sensiblestats/widget-react 0.46.0 → 0.47.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/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,43 @@ 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
+ function isTreatedLive(state, pollStale, nowMs) {
1542
+ return state?.phase === "live" && !pollStale && !isReadingStale(state, nowMs);
1543
+ }
1544
+
1508
1545
  // src/useMatchDiscovery.ts
1509
1546
  var POLL_MS = 6e4;
1547
+ var CLOCK_MS = 1e4;
1510
1548
  var ROLLOVER_CHECK_MS = 6e4;
1511
1549
  var STALE_AFTER_FAILURES = 2;
1512
1550
  function localDateOf(now) {
@@ -1519,6 +1557,7 @@ function useMatchDiscovery(opts) {
1519
1557
  const [groups, setGroups] = useState2([]);
1520
1558
  const [states, setStates] = useState2({});
1521
1559
  const [failures, setFailures] = useState2(0);
1560
+ const [nowMs, setNowMs] = useState2(() => Date.now());
1522
1561
  const [expanded, setExpanded] = useState2({});
1523
1562
  const [touched, setTouched] = useState2({});
1524
1563
  const groupsRef = useRef4(groups);
@@ -1596,16 +1635,21 @@ function useMatchDiscovery(opts) {
1596
1635
  }, POLL_MS);
1597
1636
  return () => clearInterval(id);
1598
1637
  }, [opts.isOpen, tick, groups]);
1638
+ useEffect4(() => {
1639
+ if (!opts.isOpen) return;
1640
+ const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
1641
+ return () => clearInterval(id);
1642
+ }, [opts.isOpen]);
1599
1643
  const stale = failures >= STALE_AFTER_FAILURES;
1600
1644
  const liveCount = useMemo2(
1601
- () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
1602
- [states, stale]
1645
+ () => stale ? 0 : Object.values(states).filter((s) => s.phase === "live" && !isReadingStale(s, nowMs)).length,
1646
+ [states, stale, nowMs]
1603
1647
  );
1604
1648
  const toggleGroup = useCallback3((competitionId) => {
1605
1649
  setTouched((prev) => ({ ...prev, [competitionId]: true }));
1606
1650
  setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
1607
1651
  }, []);
1608
- return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
1652
+ return { groups, states, nowMs, liveCount, stale, expanded, toggleGroup, _tick: tick };
1609
1653
  }
1610
1654
 
1611
1655
  // src/eventLogContext.ts
@@ -2963,7 +3007,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
2963
3007
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
2964
3008
  }
2965
3009
  function useLiveClock(minute, clockRunning, receivedAtMs) {
2966
- const [displayMinute, setDisplayMinute] = useState15(
3010
+ const [displayMinute2, setDisplayMinute] = useState15(
2967
3011
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
2968
3012
  );
2969
3013
  useEffect9(() => {
@@ -2974,7 +3018,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
2974
3018
  }, TICK_MS);
2975
3019
  return () => window.clearInterval(id);
2976
3020
  }, [minute, clockRunning, receivedAtMs]);
2977
- return displayMinute;
3021
+ return displayMinute2;
2978
3022
  }
2979
3023
 
2980
3024
  // src/components/dashboard/LiveScoreCard.tsx
@@ -3006,7 +3050,7 @@ function LiveScoreCard({
3006
3050
  if (!seedRef.current || seedRef.current.key !== seedKey) {
3007
3051
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
3008
3052
  }
3009
- const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3053
+ const displayMinute2 = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
3010
3054
  const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
3011
3055
  const scorers = scorersLine(goalEvents);
3012
3056
  return /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
@@ -3016,7 +3060,7 @@ function LiveScoreCard({
3016
3060
  /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
3017
3061
  data.statusLabel
3018
3062
  ] }),
3019
- /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
3063
+ /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute2}${minuteSuffix}` }),
3020
3064
  data.competition ? /* @__PURE__ */ jsx36("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
3021
3065
  ] }),
3022
3066
  /* @__PURE__ */ jsxs32("div", { className: "ss-w-fandash-live-main", children: [
@@ -3698,12 +3742,14 @@ function Side({ name, crest }) {
3698
3742
  /* @__PURE__ */ jsx49("span", { className: "ss-w-drow-nm", children: name })
3699
3743
  ] });
3700
3744
  }
3701
- function FixtureRow({ match, state, stale, ui, monitor }) {
3702
- const isLive = state?.phase === "live" && !stale;
3745
+ function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
3746
+ const readingStale = isReadingStale(state, nowMs);
3747
+ const isLive = state?.phase === "live" && !stale && !readingStale;
3703
3748
  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 }),
3749
+ const ticked = displayMinute(state, nowMs);
3750
+ const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : kickoffLabel(match.kickoffUnix);
3751
+ return /* @__PURE__ */ jsxs44("div", { className: isLive || state?.isHalfTime ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
3752
+ /* @__PURE__ */ jsx49("span", { className: isLive || state?.isHalfTime ? "ss-w-drow-when ss-w-drow-when--live" : "ss-w-drow-when", children: when }),
3707
3753
  /* @__PURE__ */ jsxs44("span", { className: "ss-w-drow-fx", children: [
3708
3754
  /* @__PURE__ */ jsx49(Side, { name: match.homeTeamName, crest: match.homeTeamImagePath }),
3709
3755
  /* @__PURE__ */ jsx49("span", { className: "ss-w-drow-dash", children: "\u2013" }),
@@ -3716,8 +3762,8 @@ function FixtureRow({ match, state, stale, ui, monitor }) {
3716
3762
 
3717
3763
  // src/components/CompetitionGroup.tsx
3718
3764
  import { jsx as jsx50, jsxs as jsxs45 } from "react/jsx-runtime";
3719
- function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
3720
- const own = group.matches.filter((m) => states[m.matchId]?.phase !== "live" || stale);
3765
+ function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, monitor, disambiguate = false }) {
3766
+ const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale, nowMs));
3721
3767
  const hasLive = own.length !== group.matches.length;
3722
3768
  return /* @__PURE__ */ jsxs45("div", { className: "ss-w-grp", children: [
3723
3769
  /* @__PURE__ */ jsxs45("button", { type: "button", className: "ss-w-grp-head", "aria-expanded": open, onClick: onToggle, children: [
@@ -3733,15 +3779,15 @@ function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, d
3733
3779
  ] }),
3734
3780
  /* @__PURE__ */ jsx50("span", { className: "ss-w-grp-count", children: own.length })
3735
3781
  ] }),
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)) })
3782
+ 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
3783
  ] });
3738
3784
  }
3739
3785
 
3740
3786
  // src/components/TodayPanel.tsx
3741
3787
  import { Fragment as Fragment7, jsx as jsx51, jsxs as jsxs46 } from "react/jsx-runtime";
3742
3788
  function TodayPanel({ discovery, ui, monitor }) {
3743
- const { groups, states, stale, expanded, toggleGroup } = discovery;
3744
- 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);
3789
+ const { groups, states, nowMs, stale, expanded, toggleGroup } = discovery;
3790
+ const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale, nowMs)).map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
3745
3791
  const nameCounts = groups.reduce((acc, g) => {
3746
3792
  acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
3747
3793
  return acc;
@@ -3759,6 +3805,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3759
3805
  match,
3760
3806
  state: states[match.matchId],
3761
3807
  stale,
3808
+ nowMs,
3762
3809
  ui,
3763
3810
  monitor
3764
3811
  },
@@ -3774,6 +3821,7 @@ function TodayPanel({ discovery, ui, monitor }) {
3774
3821
  disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
3775
3822
  states,
3776
3823
  stale,
3824
+ nowMs,
3777
3825
  ui,
3778
3826
  monitor
3779
3827
  },