@sensiblestats/widget-react 0.56.0 → 0.58.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
@@ -1687,18 +1687,19 @@ function computeGameDay(timezone) {
1687
1687
  return new Intl.DateTimeFormat("en-CA", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
1688
1688
  }
1689
1689
  function useGames(conversation, { activeTab, operatorTimezone }) {
1690
- const [enabled, setEnabled] = useState4(true);
1690
+ const [enabled, setEnabled] = useState4(false);
1691
1691
  const [games, setGames] = useState4([]);
1692
1692
  const [loading, setLoading] = useState4(false);
1693
1693
  const [refreshCount, setRefreshCount] = useState4(0);
1694
1694
  const inFlightRef = useRef5(false);
1695
- const enabledRef = useRef5(true);
1695
+ const hardOffRef = useRef5(false);
1696
1696
  const conversationRef = useRef5(conversation);
1697
1697
  conversationRef.current = conversation;
1698
1698
  const tzRef = useRef5(operatorTimezone ?? DEFAULT_TZ);
1699
1699
  tzRef.current = operatorTimezone ?? DEFAULT_TZ;
1700
+ const ready = conversation != null;
1700
1701
  useEffect5(() => {
1701
- if (!enabledRef.current) return;
1702
+ if (hardOffRef.current) return;
1702
1703
  let active = true;
1703
1704
  const doFetch = async () => {
1704
1705
  const conv = conversationRef.current;
@@ -1710,10 +1711,12 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
1710
1711
  const result = await conv.getGames(gameDay);
1711
1712
  if (!active) return;
1712
1713
  if (result === null) {
1713
- enabledRef.current = false;
1714
+ hardOffRef.current = true;
1714
1715
  setEnabled(false);
1716
+ setGames([]);
1715
1717
  } else {
1716
1718
  setGames(result.games);
1719
+ setEnabled(result.games.length > 0);
1717
1720
  }
1718
1721
  } catch {
1719
1722
  } finally {
@@ -1725,7 +1728,7 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
1725
1728
  let intervalId;
1726
1729
  if (activeTab === "games") {
1727
1730
  intervalId = window.setInterval(() => {
1728
- if (!enabledRef.current) {
1731
+ if (hardOffRef.current) {
1729
1732
  window.clearInterval(intervalId);
1730
1733
  return;
1731
1734
  }
@@ -1734,9 +1737,10 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
1734
1737
  }
1735
1738
  return () => {
1736
1739
  active = false;
1740
+ inFlightRef.current = false;
1737
1741
  if (intervalId !== void 0) window.clearInterval(intervalId);
1738
1742
  };
1739
- }, [activeTab, refreshCount]);
1743
+ }, [activeTab, refreshCount, ready]);
1740
1744
  const refresh = useCallback4(() => {
1741
1745
  setRefreshCount((c) => c + 1);
1742
1746
  }, []);
@@ -4933,6 +4937,47 @@ function toRows(positions) {
4933
4937
  });
4934
4938
  return [...byRow.keys()].sort((a, b) => b - a).map((k) => byRow.get(k));
4935
4939
  }
4940
+ function formationLabel(rows) {
4941
+ if (rows.length < 2) return "";
4942
+ const gkFirst = [...rows].reverse();
4943
+ return gkFirst.slice(1).map((r) => r.length).join("-");
4944
+ }
4945
+ var FORMATION_POSITIONS = {
4946
+ "4-4-2": [["GK"], ["LB", "CB", "CB", "RB"], ["LM", "CM", "CM", "RM"], ["ST", "ST"]],
4947
+ "4-3-3": [["GK"], ["LB", "CB", "CB", "RB"], ["CM", "CM", "CM"], ["LW", "ST", "RW"]],
4948
+ "4-2-3-1": [["GK"], ["LB", "CB", "CB", "RB"], ["CDM", "CDM"], ["LW", "CAM", "RW"], ["ST"]],
4949
+ "4-1-4-1": [["GK"], ["LB", "CB", "CB", "RB"], ["CDM"], ["LM", "CM", "CM", "RM"], ["ST"]],
4950
+ "4-5-1": [["GK"], ["LB", "CB", "CB", "RB"], ["LM", "CM", "CM", "CM", "RM"], ["ST"]],
4951
+ "4-4-1-1": [["GK"], ["LB", "CB", "CB", "RB"], ["LM", "CM", "CM", "RM"], ["CAM"], ["ST"]],
4952
+ "4-1-2-1-2": [["GK"], ["LB", "CB", "CB", "RB"], ["CDM"], ["LM", "RM"], ["CAM"], ["ST", "ST"]],
4953
+ "3-5-2": [["GK"], ["CB", "CB", "CB"], ["LWB", "CM", "CM", "CM", "RWB"], ["ST", "ST"]],
4954
+ "3-4-3": [["GK"], ["CB", "CB", "CB"], ["LM", "CM", "CM", "RM"], ["LW", "ST", "RW"]],
4955
+ "5-3-2": [["GK"], ["LWB", "CB", "CB", "CB", "RWB"], ["CM", "CM", "CM"], ["ST", "ST"]],
4956
+ "5-4-1": [["GK"], ["LWB", "CB", "CB", "CB", "RWB"], ["LM", "CM", "CM", "RM"], ["ST"]]
4957
+ };
4958
+ function lineFallback(lineIdx, totalLines) {
4959
+ if (lineIdx === 0) return "GK";
4960
+ if (lineIdx === totalLines - 1) return "FWD";
4961
+ if (lineIdx === 1) return "DEF";
4962
+ return "MID";
4963
+ }
4964
+ function positionsBySlot(formation, rowsAttackFirst) {
4965
+ const gkFirst = [...rowsAttackFirst].reverse();
4966
+ const template = FORMATION_POSITIONS[formation];
4967
+ const map = /* @__PURE__ */ new Map();
4968
+ gkFirst.forEach((line, li) => {
4969
+ line.forEach((entry, ci) => {
4970
+ if (!entry.slot) return;
4971
+ const label = template?.[li]?.[ci] ?? lineFallback(li, gkFirst.length);
4972
+ map.set(entry.slot, label);
4973
+ });
4974
+ });
4975
+ return map;
4976
+ }
4977
+ function surname(name) {
4978
+ const parts = name.trim().split(/\s+/).filter(Boolean);
4979
+ return parts.length ? parts[parts.length - 1] : name;
4980
+ }
4936
4981
  function GuessTheXiGame({
4937
4982
  config,
4938
4983
  result,
@@ -4946,9 +4991,11 @@ function GuessTheXiGame({
4946
4991
  const solved = clientState?.status === "solved";
4947
4992
  const guessedTeamIds = new Set(clientState?.guessHistory.map((g) => g.teamId) ?? []);
4948
4993
  const rows = config ? toRows(config.positions) : [];
4949
- const revealBySlot = /* @__PURE__ */ new Map();
4994
+ const formation = config?.hasRealFormation ? formationLabel(rows) : "";
4995
+ const posBySlot = config?.hasRealFormation ? positionsBySlot(formation, rows) : /* @__PURE__ */ new Map();
4996
+ const nameBySlot = /* @__PURE__ */ new Map();
4950
4997
  (clientState?.earnedReveals ?? []).forEach((r) => {
4951
- if (r.slot) revealBySlot.set(r.slot, r);
4998
+ if (r.slot && r.type !== "position") nameBySlot.set(r.slot, r.value);
4952
4999
  });
4953
5000
  const filtered = config && !isOver ? config.teamPool.filter(
4954
5001
  (t) => !guessedTeamIds.has(t.teamId) && t.teamName.toLowerCase().includes(query.toLowerCase())
@@ -4961,13 +5008,16 @@ function GuessTheXiGame({
4961
5008
  onSubmit(JSON.stringify({ guesses: newGuesses }));
4962
5009
  }
4963
5010
  const wrongGuesses = (clientState?.guessHistory ?? []).filter((g) => !g.wasCorrect);
4964
- const revealCount = clientState?.earnedReveals.length ?? 0;
5011
+ const revealCount = nameBySlot.size;
4965
5012
  return /* @__PURE__ */ jsxs53("div", { className: "ss-w-gxi", "data-testid": "game-screen-guess-the-xi", children: [
4966
5013
  !config && /* @__PURE__ */ jsx59("div", { className: "ss-w-gscreen-body", children: "Loading\u2026" }),
4967
5014
  config && /* @__PURE__ */ jsxs53(Fragment14, { children: [
4968
5015
  /* @__PURE__ */ jsxs53("div", { className: "ss-w-gxi-head", children: [
4969
5016
  /* @__PURE__ */ jsxs53("div", { children: [
4970
- /* @__PURE__ */ jsx59("div", { className: "ss-w-gxi-title", children: "Guess the XI" }),
5017
+ /* @__PURE__ */ jsxs53("div", { className: "ss-w-gxi-title", children: [
5018
+ "Guess the XI",
5019
+ formation && /* @__PURE__ */ jsx59("span", { className: "ss-w-gxi-form", "aria-label": `Formation ${formation}`, children: formation })
5020
+ ] }),
4971
5021
  /* @__PURE__ */ jsxs53("div", { className: "ss-w-gxi-sub", children: [
4972
5022
  "Name the team \xB7 flags are each player's nationality \xB7 ",
4973
5023
  config.poolLabel
@@ -4980,18 +5030,20 @@ function GuessTheXiGame({
4980
5030
  ] }),
4981
5031
  /* @__PURE__ */ jsxs53("div", { className: "ss-w-gscroll", children: [
4982
5032
  rows.length > 0 && /* @__PURE__ */ jsx59("div", { className: "ss-w-gxi-pitch", children: rows.map((row, r) => /* @__PURE__ */ jsx59("div", { className: "ss-w-gxi-prow", children: row.map((pos, c) => {
4983
- const rev = pos.slot ? revealBySlot.get(pos.slot) : void 0;
5033
+ const posLabel = pos.slot ? posBySlot.get(pos.slot) ?? "" : "";
5034
+ const name = pos.slot ? nameBySlot.get(pos.slot) : void 0;
4984
5035
  return /* @__PURE__ */ jsxs53("div", { className: "ss-w-gxi-slot", children: [
4985
5036
  /* @__PURE__ */ jsx59(
4986
5037
  "div",
4987
5038
  {
4988
- className: `ss-w-gxi-flag${rev ? " ss-w-gxi-flag--revealed" : ""}`,
5039
+ className: `ss-w-gxi-flag${name ? " ss-w-gxi-flag--revealed" : ""}`,
4989
5040
  title: pos.nationality,
4990
- "aria-label": rev ? `${pos.nationality} \u2014 revealed: ${rev.value}` : pos.nationality,
5041
+ "aria-label": `${posLabel ? posLabel + " \u2014 " : ""}${pos.nationality}${name ? " \u2014 " + name : ""}`,
4991
5042
  children: nationalityFlag(pos.nationality)
4992
5043
  }
4993
5044
  ),
4994
- rev && /* @__PURE__ */ jsx59("span", { className: "ss-w-gxi-flag-lab", children: rev.value })
5045
+ posLabel && /* @__PURE__ */ jsx59("span", { className: "ss-w-gxi-pos", children: posLabel }),
5046
+ name && /* @__PURE__ */ jsx59("span", { className: "ss-w-gxi-flag-lab", children: surname(name) })
4995
5047
  ] }, `${pos.slot}-${c}`);
4996
5048
  }) }, r)) }),
4997
5049
  revealCount > 0 && !isOver && /* @__PURE__ */ jsxs53("div", { className: "ss-w-gxi-revnote", children: [
@@ -6935,6 +6987,10 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
6935
6987
  .ss-w-gscreen { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 0; }
6936
6988
  .ss-w-gscreen > .ss-w-gxi, .ss-w-gscreen > .ss-w-gt, .ss-w-gscreen > .ss-w-cpath,
6937
6989
  .ss-w-gscreen > .ss-w-btg, .ss-w-gscreen > .ss-w-hl { flex: 1; min-height: 0; }
6990
+ /* Daily Quiz has no inner .ss-w-gscroll region \u2014 its whole body (score, questions, submit) is the
6991
+ scroller, so it takes the fill AND the overflow here. Without this it was clipped by the
6992
+ overflow:hidden frame and the lower questions + Submit could not be reached. */
6993
+ .ss-w-gscreen > .ss-w-dq { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
6938
6994
  /* Scrollable board + guess-history region. */
6939
6995
  .ss-w-gscroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 10px; padding-bottom: 2px; }
6940
6996
  /* Bottom-pinned bar (search input / action). */
@@ -7152,7 +7208,9 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
7152
7208
  .ss-w-gxi { display: flex; flex-direction: column; gap: 12px; }
7153
7209
  /* Header */
7154
7210
  .ss-w-gxi-head { display: flex; align-items: flex-start; gap: 8px; }
7155
- .ss-w-gxi-title { font-size: 18px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; color: var(--ss-w-ink); line-height: 1; }
7211
+ .ss-w-gxi-title { font-size: 18px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; color: var(--ss-w-ink); line-height: 1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
7212
+ /* Formation pill next to the title, e.g. "4-2-3-1". */
7213
+ .ss-w-gxi-form { font-size: 12px; font-weight: 800; letter-spacing: .06em; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 26%, var(--ss-w-line)); border-radius: 999px; padding: 2px 9px; font-variant-numeric: tabular-nums; }
7156
7214
  .ss-w-gxi-sub { font-size: 11px; color: var(--ss-w-faint); margin-top: 4px; }
7157
7215
  .ss-w-gxi-left { margin-left: auto; flex: none; font-size: 11px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 11%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 22%, var(--ss-w-line)); border-radius: 999px; padding: 4px 10px; white-space: nowrap; }
7158
7216
  /* Pitch: a flag per slot laid out in formation rows on a green field. A real min-height plus
@@ -7171,6 +7229,10 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
7171
7229
  .ss-w-gxi-flag { width: 32px; height: 32px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 17px; line-height: 1; background: rgba(9, 12, 10, .55); border: 1px solid rgba(255,255,255,.16); box-shadow: 0 1px 3px rgba(0,0,0,.3); overflow: hidden; }
7172
7230
  /* A slot whose player has been revealed by a wrong guess. */
7173
7231
  .ss-w-gxi-flag--revealed { border-color: var(--ss-w-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ss-w-accent) 45%, transparent); }
7232
+ /* Position label shown under every flag (GK, CB, CM, ST\u2026) \u2014 reads the formation and marks the
7233
+ keeper's end. Muted so it sits behind the flag, not competing with it. */
7234
+ .ss-w-gxi-pos { font-size: 8.5px; line-height: 1.05; font-weight: 800; letter-spacing: .04em; text-align: center; color: rgba(255,255,255,.82); text-shadow: 0 1px 2px rgba(0,0,0,.5); }
7235
+ /* Player surname, shown under the position once a wrong guess reveals it \u2014 in the accent colour. */
7174
7236
  .ss-w-gxi-flag-lab { font-size: 8px; line-height: 1.05; font-weight: 700; text-align: center; color: var(--ss-w-accent); max-width: 46px; word-break: break-word; }
7175
7237
  .ss-w-gxi-revnote { font-size: 11px; color: var(--ss-w-faint); }
7176
7238
  /* Wrong-guess trail */