@sportsdataverse/sdvplot 0.0.0-stage → 0.1.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.
Files changed (166) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +94 -0
  3. package/README.md +908 -2
  4. package/dist/_web-D7Aa5Rd8.d.ts +69 -0
  5. package/dist/aaf-GJ3SH34T.js +13 -0
  6. package/dist/aaf-HCYN5T6Y.js +37 -0
  7. package/dist/aaf-ZAE5KS2Y.js +13 -0
  8. package/dist/ahl-E66MDVCU.js +66 -0
  9. package/dist/ahl-NPQQWZEV.js +249 -0
  10. package/dist/ahl-QRDU73XX.js +309 -0
  11. package/dist/axis-CraWQRUJ.d.ts +49 -0
  12. package/dist/bins.d.ts +174 -0
  13. package/dist/bins.js +4 -0
  14. package/dist/bins.js.map +1 -0
  15. package/dist/cfb-F42SVS4X.js +5000 -0
  16. package/dist/cfb-P3SHEYRZ.js +692 -0
  17. package/dist/cfb-WXRZANLP.js +10226 -0
  18. package/dist/chartjs-surface.d.ts +42 -0
  19. package/dist/chartjs-surface.js +65 -0
  20. package/dist/chartjs-surface.js.map +1 -0
  21. package/dist/chartjs.d.ts +132 -0
  22. package/dist/chartjs.js +339 -0
  23. package/dist/chartjs.js.map +1 -0
  24. package/dist/chunk-4C3GX6QO.js +92 -0
  25. package/dist/chunk-4C3GX6QO.js.map +1 -0
  26. package/dist/chunk-C2CDY5OG.js +137 -0
  27. package/dist/chunk-C2CDY5OG.js.map +1 -0
  28. package/dist/chunk-E7G7VSLV.js +43 -0
  29. package/dist/chunk-E7G7VSLV.js.map +1 -0
  30. package/dist/chunk-J6R2LEJ7.js +58 -0
  31. package/dist/chunk-J6R2LEJ7.js.map +1 -0
  32. package/dist/chunk-JJFTD4VB.js +175 -0
  33. package/dist/chunk-JJFTD4VB.js.map +1 -0
  34. package/dist/chunk-KKH6ABMA.js +223 -0
  35. package/dist/chunk-KKH6ABMA.js.map +1 -0
  36. package/dist/chunk-NLPH2VZT.js +127 -0
  37. package/dist/chunk-NLPH2VZT.js.map +1 -0
  38. package/dist/chunk-OKEE4AH6.js +30 -0
  39. package/dist/chunk-OKEE4AH6.js.map +1 -0
  40. package/dist/chunk-QY65DUEF.js +306 -0
  41. package/dist/chunk-QY65DUEF.js.map +1 -0
  42. package/dist/chunk-RMT2GMFH.js +97 -0
  43. package/dist/chunk-RMT2GMFH.js.map +1 -0
  44. package/dist/chunk-SEBO35DV.js +76 -0
  45. package/dist/chunk-SEBO35DV.js.map +1 -0
  46. package/dist/chunk-TAXO3VXH.js +556 -0
  47. package/dist/chunk-TAXO3VXH.js.map +1 -0
  48. package/dist/chunk-TDNBAEQB.js +105 -0
  49. package/dist/chunk-TDNBAEQB.js.map +1 -0
  50. package/dist/cricket-KPCYG2HL.js +493 -0
  51. package/dist/cricket-NDXLE25B.js +116 -0
  52. package/dist/cricket-YUUF7P6M.js +160 -0
  53. package/dist/d3.d.ts +234 -0
  54. package/dist/d3.js +198 -0
  55. package/dist/d3.js.map +1 -0
  56. package/dist/echarts.d.ts +121 -0
  57. package/dist/echarts.js +228 -0
  58. package/dist/echarts.js.map +1 -0
  59. package/dist/echl-3CR4RGFC.js +241 -0
  60. package/dist/echl-NPC32C23.js +107 -0
  61. package/dist/echl-PI33DGJC.js +64 -0
  62. package/dist/export.d.ts +130 -0
  63. package/dist/export.js +258 -0
  64. package/dist/export.js.map +1 -0
  65. package/dist/headshots-B2J_LOO8.d.ts +40 -0
  66. package/dist/index.d.ts +182 -0
  67. package/dist/index.js +10 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/interact.d.ts +567 -0
  70. package/dist/interact.js +809 -0
  71. package/dist/interact.js.map +1 -0
  72. package/dist/marks-DH-tQIP-.d.ts +75 -0
  73. package/dist/mbb-3TRLVW3H.js +3619 -0
  74. package/dist/mbb-DQUHCHZP.js +5111 -0
  75. package/dist/mbb-PRAIO2P5.js +371 -0
  76. package/dist/milb-6IQ6NCEW.js +291 -0
  77. package/dist/milb-EYY5MD4T.js +1752 -0
  78. package/dist/milb-EYZ4SZLI.js +797 -0
  79. package/dist/mlb-4PBUJSVQ.js +35 -0
  80. package/dist/mlb-FHPY3GQJ.js +701 -0
  81. package/dist/mlb-REVGL3N2.js +665 -0
  82. package/dist/nba-ATT775GS.js +485 -0
  83. package/dist/nba-LKM4UHVU.js +482 -0
  84. package/dist/nba-YX7PXM3R.js +35 -0
  85. package/dist/nbagl-3WGZCZ4S.js +37 -0
  86. package/dist/nbagl-LP4XJRAL.js +229 -0
  87. package/dist/nbagl-VTTOU7LN.js +499 -0
  88. package/dist/ncaa_baseball-UV75LNVR.js +2670 -0
  89. package/dist/ncaa_baseball-VD5J5C72.js +419 -0
  90. package/dist/ncaa_baseball-YRBS32D6.js +4592 -0
  91. package/dist/ncaa_mhockey-IMYHS7QS.js +1033 -0
  92. package/dist/ncaa_mhockey-TXTKWHE3.js +115 -0
  93. package/dist/ncaa_mhockey-U3MFW5VT.js +663 -0
  94. package/dist/ncaa_softball-6WR6KS64.js +420 -0
  95. package/dist/ncaa_softball-RZCTIE56.js +2654 -0
  96. package/dist/ncaa_softball-VOD4PVGY.js +4599 -0
  97. package/dist/ncaa_whockey-6XXGUTBK.js +607 -0
  98. package/dist/ncaa_whockey-AKKYKY67.js +53 -0
  99. package/dist/ncaa_whockey-KBEKXFBB.js +293 -0
  100. package/dist/nfl-BDB2HYAR.js +625 -0
  101. package/dist/nfl-EHYHDLJD.js +37 -0
  102. package/dist/nfl-VF4NJCHY.js +514 -0
  103. package/dist/nfl_gsis-UY64JO37.js +4 -0
  104. package/dist/nhl-6WPHME7H.js +511 -0
  105. package/dist/nhl-EMIJFZHY.js +884 -0
  106. package/dist/nhl-ISFUKRFU.js +37 -0
  107. package/dist/ohl-2FEM554G.js +113 -0
  108. package/dist/ohl-C5Q4W6JB.js +32 -0
  109. package/dist/ohl-TQ4J46VC.js +66 -0
  110. package/dist/phf-H7POU2KW.js +32 -0
  111. package/dist/phf-K5IFMZUM.js +13 -0
  112. package/dist/phf-TWJI3BOW.js +20 -0
  113. package/dist/plot.d.ts +449 -0
  114. package/dist/plot.js +711 -0
  115. package/dist/plot.js.map +1 -0
  116. package/dist/plotly.d.ts +129 -0
  117. package/dist/plotly.js +360 -0
  118. package/dist/plotly.js.map +1 -0
  119. package/dist/pwhl-OX3BO4JI.js +53 -0
  120. package/dist/pwhl-W44OC3C6.js +17 -0
  121. package/dist/pwhl-ZJGG3H7W.js +23 -0
  122. package/dist/qmjhl-5HISEHLL.js +35 -0
  123. package/dist/qmjhl-DA3UHKE5.js +125 -0
  124. package/dist/qmjhl-YG4BOQPF.js +614 -0
  125. package/dist/react.d.ts +101 -0
  126. package/dist/react.js +164 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/resolve-CQW-jzvr.d.ts +28 -0
  129. package/dist/selection-D4T3PkRt.d.ts +140 -0
  130. package/dist/shots.d.ts +364 -0
  131. package/dist/shots.js +85 -0
  132. package/dist/shots.js.map +1 -0
  133. package/dist/soccer-7BH3ZLVL.js +7898 -0
  134. package/dist/soccer-MRU25OGE.js +2636 -0
  135. package/dist/soccer-PF4IPPVA.js +12342 -0
  136. package/dist/surfaces-ChaD9nei.d.ts +38 -0
  137. package/dist/testing.d.ts +75 -0
  138. package/dist/testing.js +240 -0
  139. package/dist/testing.js.map +1 -0
  140. package/dist/tiers-B6pUBPcE.d.ts +93 -0
  141. package/dist/types-C7n6gJF3.d.ts +62 -0
  142. package/dist/ufl-7VLZTLTT.js +143 -0
  143. package/dist/ufl-CCMBLPSW.js +100 -0
  144. package/dist/ufl-F2BJRXME.js +16 -0
  145. package/dist/usfl-4CR5RMZJ.js +32 -0
  146. package/dist/usfl-LD7HP2YK.js +14 -0
  147. package/dist/usfl-VU4YSJQD.js +19 -0
  148. package/dist/ushl-CPG2LB4L.js +73 -0
  149. package/dist/ushl-PQL6HJAD.js +88 -0
  150. package/dist/ushl-ZT2ATPHK.js +22 -0
  151. package/dist/vega.d.ts +85 -0
  152. package/dist/vega.js +362 -0
  153. package/dist/vega.js.map +1 -0
  154. package/dist/wbb-3KHCDYTK.js +5087 -0
  155. package/dist/wbb-BMX53Q3Q.js +369 -0
  156. package/dist/wbb-QNRN2CJJ.js +3620 -0
  157. package/dist/whl-4ATYZWUB.js +97 -0
  158. package/dist/whl-RVFPL6MH.js +79 -0
  159. package/dist/whl-Y7ASKYMM.js +28 -0
  160. package/dist/wnba-FWX5SFVI.js +232 -0
  161. package/dist/wnba-GX6HZR4B.js +220 -0
  162. package/dist/wnba-NTMWIPP6.js +20 -0
  163. package/dist/xfl-43R2JMJK.js +30 -0
  164. package/dist/xfl-H3BXQPZZ.js +16 -0
  165. package/dist/xfl-XON7A7FI.js +69 -0
  166. package/package.json +154 -3
@@ -0,0 +1,101 @@
1
+ import { ImgHTMLAttributes, ReactElement } from 'react';
2
+ import { L as LogoUrlOptions, C as ColorOptions } from './marks-DH-tQIP-.js';
3
+ import { E as EspnHeadshotLeague } from './headshots-B2J_LOO8.js';
4
+ import { V as Value, R as ResolveOptions } from './resolve-CQW-jzvr.js';
5
+ import { S as SelectionStore, a as SelectionState } from './selection-D4T3PkRt.js';
6
+ import { H as HeadshotIdSystem, L as League, V as Variant, M as MarkType, T as TeamId } from './types-C7n6gJF3.js';
7
+
8
+ type ImgProps = Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "height" | "width">;
9
+ interface TeamLogoProps extends ImgProps {
10
+ team: Value;
11
+ league: League;
12
+ season?: LogoUrlOptions["season"];
13
+ variant?: Variant;
14
+ markType?: MarkType;
15
+ /** Rendered `height` in px; width is left unset so the browser keeps the aspect. */
16
+ size?: number;
17
+ /** Defaults to the team's name. */
18
+ alt?: string;
19
+ }
20
+ /**
21
+ * Team logo `<img>`. Loads the league index on first use; renders nothing until resolved and for an unknown team.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * import { TeamLogo } from "@sportsdataverse/sdvplot/react";
26
+ *
27
+ * <TeamLogo team="KC" league="nfl" size={64} />;
28
+ * ```
29
+ */
30
+ declare function TeamLogo({ team, league, season, variant, markType, size, alt, ...imgProps }: TeamLogoProps): ReactElement | null;
31
+ /** `TeamLogo` with `markType="wordmark"`. */
32
+ declare function Wordmark(props: Omit<TeamLogoProps, "markType">): ReactElement | null;
33
+ interface HeadshotProps extends ImgProps {
34
+ playerId: Value;
35
+ league: EspnHeadshotLeague;
36
+ idSystem?: HeadshotIdSystem;
37
+ /** Height in px (default 60); width follows the 600:436 headshot aspect. */
38
+ height?: number;
39
+ /** Default `name`, then "Player headshot". `""` marks it decorative (the initials box is then `aria-hidden`). */
40
+ alt?: string;
41
+ /**
42
+ * `"initials"`: when there is no headshot URL or the image fails to load, show up to three initials of `name` in a
43
+ * box of the headshot's size (blazing-the-nets `main` `components/Headshot.tsx:15-29`). Default `"none"`: a failed
44
+ * load leaves the `<img>`, and an id with no URL renders nothing.
45
+ */
46
+ fallback?: "none" | "initials";
47
+ /** The player's name, for the initials and the default alt text. */
48
+ name?: string;
49
+ }
50
+ /**
51
+ * Player headshot `<img>`: sync for ESPN ids; gsis ids render once the gsis map has loaded. Renders nothing for an
52
+ * unknown/malformed id, or a league/idSystem `headshotUrl` rejects, unless `fallback="initials"` and a `name` are
53
+ * given, which show the player's initials instead.
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * import { Headshot } from "@sportsdataverse/sdvplot/react";
58
+ *
59
+ * <Headshot playerId="3918298" league="nfl" height={80} alt="Josh Allen" />;
60
+ * ```
61
+ */
62
+ declare function Headshot({ playerId, league, idSystem, height, alt, fallback, name, ...imgProps }: HeadshotProps): ReactElement | null;
63
+ /** `{team: "#hex"}` for a league once loaded (`undefined` before); reloads when `league`/`which` change. */
64
+ declare function useTeamColors(league: League, { which }?: {
65
+ which?: ColorOptions["which"];
66
+ }): Record<string, string> | undefined;
67
+ /** Sync resolver `(value, opts?) => TeamId | undefined` once the league is loaded (`undefined` before); reloads when `league` changes. */
68
+ declare function useResolve(league: League): ((value: Value, opts?: ResolveOptions) => TeamId | undefined) | undefined;
69
+ /**
70
+ * The state of a `createSelection` store (J31), re-rendering on every change and never on a no-op patch. On the
71
+ * server, and in the first client render, it reads the store's current state, so server and client markup agree.
72
+ * Redrawing from it is the component's choice: filter rows with `state.predicate`, emphasise `state.selected`.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * import { createSelection } from "@sportsdataverse/sdvplot";
77
+ * import { useSelection } from "@sportsdataverse/sdvplot/react";
78
+ * import type { ReactElement } from "react";
79
+ *
80
+ * const store = createSelection();
81
+ * store.set({ selected: ["KC", "BUF"] });
82
+ * function Picked(): ReactElement {
83
+ * const { selected } = useSelection(store); // re-renders on every change of the store
84
+ * return (
85
+ * <table>
86
+ * <tbody>
87
+ * {[...selected].map((id) => (
88
+ * <tr key={id}>
89
+ * <td>{id}</td>
90
+ * </tr>
91
+ * ))}
92
+ * </tbody>
93
+ * </table>
94
+ * );
95
+ * }
96
+ * <Picked />;
97
+ * ```
98
+ */
99
+ declare function useSelection<Row>(store: SelectionStore<Row>): SelectionState<Row>;
100
+
101
+ export { Headshot, type HeadshotProps, TeamLogo, type TeamLogoProps, Wordmark, useResolve, useSelection, useTeamColors };
package/dist/react.js ADDED
@@ -0,0 +1,164 @@
1
+ "use client";
2
+ import { logoUrlSync } from './chunk-KKH6ABMA.js';
3
+ import { loadLeague, resolveSync, getLeagueSync, loadGsis, headshotUrl, palette, HEADSHOT_ASPECT } from './chunk-TAXO3VXH.js';
4
+ import './chunk-E7G7VSLV.js';
5
+ import { useState, useEffect, useRef, useSyncExternalStore, useLayoutEffect } from 'react';
6
+ import { jsx } from 'react/jsx-runtime';
7
+
8
+ function TeamLogo({
9
+ team,
10
+ league,
11
+ season,
12
+ variant,
13
+ markType,
14
+ size,
15
+ alt,
16
+ ...imgProps
17
+ }) {
18
+ const [mark, setMark] = useState();
19
+ useEffect(() => {
20
+ let live = true;
21
+ (async () => {
22
+ await loadLeague(league);
23
+ if (!live) return;
24
+ const o = {};
25
+ if (season !== void 0) o.season = season;
26
+ if (variant !== void 0) o.variant = variant;
27
+ if (markType !== void 0) o.markType = markType;
28
+ const src = logoUrlSync(team, league, o);
29
+ const id = resolveSync(team, league, { season });
30
+ const name = getLeagueSync(league).teams.find((t) => t.team_id === id)?.name ?? "";
31
+ setMark(src === void 0 ? void 0 : { src, name });
32
+ })().catch(() => {
33
+ if (live) setMark(void 0);
34
+ });
35
+ return () => {
36
+ live = false;
37
+ };
38
+ }, [team, league, season, variant, markType]);
39
+ if (mark === void 0) return null;
40
+ return /* @__PURE__ */ jsx("img", { src: mark.src, alt: alt ?? mark.name, height: size, ...imgProps });
41
+ }
42
+ function Wordmark(props) {
43
+ return /* @__PURE__ */ jsx(TeamLogo, { ...props, markType: "wordmark" });
44
+ }
45
+ var initialsOf = (name) => name.split(" ").map((w) => Array.from(w)[0] ?? "").join("").slice(0, 3);
46
+ var useClientLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
47
+ function Headshot({
48
+ playerId,
49
+ league,
50
+ idSystem,
51
+ height = 60,
52
+ alt,
53
+ fallback = "none",
54
+ name,
55
+ ...imgProps
56
+ }) {
57
+ const [, setGsisReady] = useState(false);
58
+ const [failed, setFailed] = useState();
59
+ const img = useRef(null);
60
+ useEffect(() => {
61
+ if (idSystem !== "gsis") return;
62
+ let live = true;
63
+ loadGsis().then(
64
+ () => {
65
+ if (live) setGsisReady(true);
66
+ },
67
+ () => {
68
+ }
69
+ );
70
+ return () => {
71
+ live = false;
72
+ };
73
+ }, [idSystem]);
74
+ let src;
75
+ try {
76
+ src = headshotUrl(playerId, league, idSystem === void 0 ? {} : { idSystem });
77
+ } catch {
78
+ src = void 0;
79
+ }
80
+ const label = alt ?? (name || void 0) ?? "Player headshot";
81
+ const width = Math.round(height * HEADSHOT_ASPECT);
82
+ useClientLayoutEffect(() => {
83
+ if (img.current?.complete && img.current.naturalWidth === 0) setFailed(src);
84
+ }, [src]);
85
+ if (src === void 0 || fallback === "initials" && src === failed) {
86
+ if (fallback !== "initials" || name === void 0) return null;
87
+ return /* @__PURE__ */ jsx(
88
+ "span",
89
+ {
90
+ ...label === "" ? { "aria-hidden": true } : { role: "img", "aria-label": label },
91
+ className: imgProps.className,
92
+ style: {
93
+ display: "inline-flex",
94
+ alignItems: "center",
95
+ justifyContent: "center",
96
+ width,
97
+ height,
98
+ background: "var(--sdv-line, #e2e2e2)",
99
+ color: "var(--sdv-muted, #525252)",
100
+ ...imgProps.style
101
+ },
102
+ children: initialsOf(name)
103
+ }
104
+ );
105
+ }
106
+ const shown = src;
107
+ return (
108
+ // biome-ignore lint/a11y/useAltText: alt always defaults to a string; imgProps cannot carry it
109
+ /* @__PURE__ */ jsx(
110
+ "img",
111
+ {
112
+ ref: img,
113
+ src: shown,
114
+ alt: label,
115
+ height,
116
+ width,
117
+ ...imgProps,
118
+ onError: (e) => {
119
+ setFailed(shown);
120
+ imgProps.onError?.(e);
121
+ }
122
+ }
123
+ )
124
+ );
125
+ }
126
+ function useTeamColors(league, { which } = {}) {
127
+ const [colors, setColors] = useState();
128
+ useEffect(() => {
129
+ let live = true;
130
+ setColors(void 0);
131
+ palette(league, void 0, which === void 0 ? {} : { which }).then((c) => {
132
+ if (live) setColors(c);
133
+ }).catch(() => {
134
+ if (live) setColors(void 0);
135
+ });
136
+ return () => {
137
+ live = false;
138
+ };
139
+ }, [league, which]);
140
+ return colors;
141
+ }
142
+ function useResolve(league) {
143
+ const [ready, setReady] = useState();
144
+ useEffect(() => {
145
+ let live = true;
146
+ setReady(void 0);
147
+ loadLeague(league).then(() => {
148
+ if (live) setReady(league);
149
+ }).catch(() => {
150
+ if (live) setReady(void 0);
151
+ });
152
+ return () => {
153
+ live = false;
154
+ };
155
+ }, [league]);
156
+ return ready === void 0 ? void 0 : (value, opts) => resolveSync(value, ready, opts);
157
+ }
158
+ function useSelection(store) {
159
+ return useSyncExternalStore(store.subscribe, store.getState, store.getState);
160
+ }
161
+
162
+ export { Headshot, TeamLogo, Wordmark, useResolve, useSelection, useTeamColors };
163
+ //# sourceMappingURL=react.js.map
164
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/index.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["import {\n type ImgHTMLAttributes,\n type ReactElement,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { type ColorOptions, palette } from \"../colors.js\";\nimport { type EspnHeadshotLeague, HEADSHOT_ASPECT, headshotUrl, loadGsis } from \"../headshots.js\";\nimport { getLeagueSync, loadLeague } from \"../index-data.js\";\nimport { type LogoUrlOptions, logoUrlSync } from \"../marks.js\";\nimport { type ResolveOptions, type Value, resolveSync } from \"../resolve.js\";\nimport type { SelectionState, SelectionStore } from \"../selection.js\";\nimport type { HeadshotIdSystem, League, MarkType, TeamId, Variant } from \"../types.js\";\n\ntype ImgProps = Omit<ImgHTMLAttributes<HTMLImageElement>, \"src\" | \"alt\" | \"height\" | \"width\">;\n\nexport interface TeamLogoProps extends ImgProps {\n team: Value;\n league: League;\n season?: LogoUrlOptions[\"season\"];\n variant?: Variant;\n markType?: MarkType;\n /** Rendered `height` in px; width is left unset so the browser keeps the aspect. */\n size?: number;\n /** Defaults to the team's name. */\n alt?: string;\n}\n\n/**\n * Team logo `<img>`. Loads the league index on first use; renders nothing until resolved and for an unknown team.\n *\n * @example\n * ```tsx\n * import { TeamLogo } from \"@sportsdataverse/sdvplot/react\";\n *\n * <TeamLogo team=\"KC\" league=\"nfl\" size={64} />;\n * ```\n */\nexport function TeamLogo({\n team,\n league,\n season,\n variant,\n markType,\n size,\n alt,\n ...imgProps\n}: TeamLogoProps): ReactElement | null {\n const [mark, setMark] = useState<{ src: string; name: string } | undefined>();\n useEffect(() => {\n let live = true;\n // One async body: any throw (bad league, bad variant, lookup error) becomes a rejection the .catch swallows.\n (async () => {\n await loadLeague(league);\n if (!live) return;\n const o: LogoUrlOptions = {};\n if (season !== undefined) o.season = season;\n if (variant !== undefined) o.variant = variant;\n if (markType !== undefined) o.markType = markType;\n const src = logoUrlSync(team, league, o);\n const id = resolveSync(team, league, { season });\n const name = getLeagueSync(league).teams.find((t) => t.team_id === id)?.name ?? \"\";\n setMark(src === undefined ? undefined : { src, name });\n })().catch(() => {\n if (live) setMark(undefined);\n });\n return () => {\n live = false;\n };\n }, [team, league, season, variant, markType]);\n if (mark === undefined) return null;\n // biome-ignore lint/a11y/useAltText: alt is always set (prop or team name); imgProps cannot carry it\n return <img src={mark.src} alt={alt ?? mark.name} height={size} {...imgProps} />;\n}\n\n/** `TeamLogo` with `markType=\"wordmark\"`. */\nexport function Wordmark(props: Omit<TeamLogoProps, \"markType\">): ReactElement | null {\n return <TeamLogo {...props} markType=\"wordmark\" />;\n}\n\nexport interface HeadshotProps extends ImgProps {\n playerId: Value;\n league: EspnHeadshotLeague;\n idSystem?: HeadshotIdSystem;\n /** Height in px (default 60); width follows the 600:436 headshot aspect. */\n height?: number;\n /** Default `name`, then \"Player headshot\". `\"\"` marks it decorative (the initials box is then `aria-hidden`). */\n alt?: string;\n /**\n * `\"initials\"`: when there is no headshot URL or the image fails to load, show up to three initials of `name` in a\n * box of the headshot's size (blazing-the-nets `main` `components/Headshot.tsx:15-29`). Default `\"none\"`: a failed\n * load leaves the `<img>`, and an id with no URL renders nothing.\n */\n fallback?: \"none\" | \"initials\";\n /** The player's name, for the initials and the default alt text. */\n name?: string;\n}\n\n/**\n * The first character of every space-separated word, up to three, as blazing-the-nets `main` does\n * (`Headshot.tsx:16-20`): suffixes and particles are words (\"Brian Thomas Jr.\" is \"BTJ\"), \"De'Von\" is one word.\n * It differs from `main` only on an astral first character, which it keeps whole where `w[0]` leaves half of it.\n */\nconst initialsOf = (name: string): string =>\n name\n .split(\" \")\n .map((w) => Array.from(w)[0] ?? \"\")\n .join(\"\")\n .slice(0, 3);\n\n// useLayoutEffect warns during React 18 server rendering; on the server no effect runs anyway.\nconst useClientLayoutEffect = typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n\n/**\n * Player headshot `<img>`: sync for ESPN ids; gsis ids render once the gsis map has loaded. Renders nothing for an\n * unknown/malformed id, or a league/idSystem `headshotUrl` rejects, unless `fallback=\"initials\"` and a `name` are\n * given, which show the player's initials instead.\n *\n * @example\n * ```tsx\n * import { Headshot } from \"@sportsdataverse/sdvplot/react\";\n *\n * <Headshot playerId=\"3918298\" league=\"nfl\" height={80} alt=\"Josh Allen\" />;\n * ```\n */\nexport function Headshot({\n playerId,\n league,\n idSystem,\n height = 60,\n alt,\n fallback = \"none\",\n name,\n ...imgProps\n}: HeadshotProps): ReactElement | null {\n const [, setGsisReady] = useState(false); // re-render once loadGsis resolves\n const [failed, setFailed] = useState<string | undefined>(); // the src that failed to load\n const img = useRef<HTMLImageElement>(null);\n useEffect(() => {\n if (idSystem !== \"gsis\") return;\n let live = true;\n loadGsis().then(\n () => {\n if (live) setGsisReady(true);\n },\n () => {},\n );\n return () => {\n live = false;\n };\n }, [idSystem]);\n let src: string | undefined;\n try {\n src = headshotUrl(playerId, league, idSystem === undefined ? {} : { idSystem });\n } catch {\n src = undefined;\n }\n const label = alt ?? (name || undefined) ?? \"Player headshot\";\n const width = Math.round(height * HEADSHOT_ASPECT);\n // React replays no error that fired before hydration (or before mount): read the settled image instead. A\n // loading image is not complete yet, and a loaded one has a natural width.\n useClientLayoutEffect(() => {\n if (img.current?.complete && img.current.naturalWidth === 0) setFailed(src);\n }, [src]);\n if (src === undefined || (fallback === \"initials\" && src === failed)) {\n if (fallback !== \"initials\" || name === undefined) return null;\n return (\n <span\n {...(label === \"\" ? { \"aria-hidden\": true } : { role: \"img\", \"aria-label\": label })}\n className={imgProps.className}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width,\n height,\n background: \"var(--sdv-line, #e2e2e2)\",\n color: \"var(--sdv-muted, #525252)\",\n ...imgProps.style,\n }}\n >\n {initialsOf(name)}\n </span>\n );\n }\n const shown = src;\n return (\n // biome-ignore lint/a11y/useAltText: alt always defaults to a string; imgProps cannot carry it\n <img\n ref={img}\n src={shown}\n alt={label}\n height={height}\n width={width}\n {...imgProps}\n onError={(e) => {\n setFailed(shown);\n imgProps.onError?.(e);\n }}\n />\n );\n}\n\n/** `{team: \"#hex\"}` for a league once loaded (`undefined` before); reloads when `league`/`which` change. */\nexport function useTeamColors(\n league: League,\n { which }: { which?: ColorOptions[\"which\"] } = {},\n): Record<string, string> | undefined {\n const [colors, setColors] = useState<Record<string, string> | undefined>();\n useEffect(() => {\n let live = true;\n setColors(undefined);\n palette(league, undefined, which === undefined ? {} : { which })\n .then((c) => {\n if (live) setColors(c);\n })\n .catch(() => {\n if (live) setColors(undefined);\n });\n return () => {\n live = false;\n };\n }, [league, which]);\n return colors;\n}\n\n/** Sync resolver `(value, opts?) => TeamId | undefined` once the league is loaded (`undefined` before); reloads when `league` changes. */\nexport function useResolve(\n league: League,\n): ((value: Value, opts?: ResolveOptions) => TeamId | undefined) | undefined {\n const [ready, setReady] = useState<League | undefined>();\n useEffect(() => {\n let live = true;\n setReady(undefined);\n loadLeague(league)\n .then(() => {\n if (live) setReady(league);\n })\n .catch(() => {\n if (live) setReady(undefined);\n });\n return () => {\n live = false;\n };\n }, [league]);\n return ready === undefined ? undefined : (value, opts) => resolveSync(value, ready, opts);\n}\n\n/**\n * The state of a `createSelection` store (J31), re-rendering on every change and never on a no-op patch. On the\n * server, and in the first client render, it reads the store's current state, so server and client markup agree.\n * Redrawing from it is the component's choice: filter rows with `state.predicate`, emphasise `state.selected`.\n *\n * @example\n * ```tsx\n * import { createSelection } from \"@sportsdataverse/sdvplot\";\n * import { useSelection } from \"@sportsdataverse/sdvplot/react\";\n * import type { ReactElement } from \"react\";\n *\n * const store = createSelection();\n * store.set({ selected: [\"KC\", \"BUF\"] });\n * function Picked(): ReactElement {\n * const { selected } = useSelection(store); // re-renders on every change of the store\n * return (\n * <table>\n * <tbody>\n * {[...selected].map((id) => (\n * <tr key={id}>\n * <td>{id}</td>\n * </tr>\n * ))}\n * </tbody>\n * </table>\n * );\n * }\n * <Picked />;\n * ```\n */\nexport function useSelection<Row>(store: SelectionStore<Row>): SelectionState<Row> {\n // The store's methods are closures (no `this`), so they pass unbound; getState is referentially stable between\n // changes, which is all useSyncExternalStore needs.\n return useSyncExternalStore(store.subscribe, store.getState, store.getState);\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import { S as SeasonInput, I as IdSystem, T as TeamId, L as League } from './types-C7n6gJF3.js';
2
+
3
+ type Value = string | number | bigint | null | undefined;
4
+ type Resolved<T> = T extends readonly Value[] ? (TeamId | undefined)[] : TeamId | undefined;
5
+ interface ResolveOptions {
6
+ season?: SeasonInput | readonly SeasonInput[];
7
+ idSystem?: IdSystem;
8
+ strict?: boolean;
9
+ }
10
+ declare function resolveSync<T extends Value | readonly Value[]>(values: T, league: League, opts?: ResolveOptions): Resolved<T>;
11
+ /**
12
+ * Resolve team values (abbreviations, ids, names) to ESPN team ids, loading the league first. Unknown values give
13
+ * `undefined` and one warning per call; `strict: true` throws `UnresolvedTeamError`.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { resolve } from "@sportsdataverse/sdvplot";
18
+ *
19
+ * await resolve(["KC", "Kansas City Chiefs", "OAK"], "nfl", { season: 2019 });
20
+ * ```
21
+ */
22
+ declare function resolve<T extends Value | readonly Value[]>(values: T, league: League, opts?: ResolveOptions): Promise<Resolved<T>>;
23
+ /** Port of `suggest`: difflib.get_close_matches(cutoff 0.6): SequenceMatcher(a=candidate, b=query).ratio() ≥ 0.6, best first, ties by candidate key descending; never picks one. */
24
+ declare function suggest(value: Value, league: League, opts?: {
25
+ n?: number;
26
+ }): Promise<Array<[TeamId, string]>>;
27
+
28
+ export { type ResolveOptions as R, type Value as V, type Resolved as a, resolveSync as b, resolve as r, suggest as s };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * A row test, true to keep the row: the brush region (or any caller's) as a predicate. The one definition: sdvtables
3
+ * re-exports this type for `setExternalFilter`, so a linked table applies a store's predicate as is (not a
4
+ * `ValuePredicate`, which tests one column's cell). Both compare it by identity: the same function again is a no-op.
5
+ */
6
+ type RowFilter<Row> = (row: Row) => boolean;
7
+ /**
8
+ * A shared hover VALUE (J34): a number along one data field, such as the shot distance under the pointer, that every
9
+ * linked chart draws through its own scale (a rule, a band, a ring). Not an id: it never dims marks or filters a table.
10
+ * Unrelated to sdvtables' `TableCursor` (a grid's keyboard row).
11
+ */
12
+ interface Cursor {
13
+ /** The data field the value is in, e.g. `"shot_distance"`. Never `""`. */
14
+ readonly field: string;
15
+ /** The value along that field. Always finite. */
16
+ readonly value: number;
17
+ }
18
+ /**
19
+ * What a {@link SelectionStore} holds. Immutable: every change replaces it, and a snapshot is read-only at runtime too
20
+ * (its sets' `add`, `delete` and `clear`, and a write to it or its cursor, throw a `TypeError`): pass changes to
21
+ * `store.set`, or copy a set with `new Set(ids)`.
22
+ */
23
+ interface SelectionState<Row = unknown> {
24
+ /** Ids under the pointer right now (transient). */
25
+ readonly hover: ReadonlySet<string>;
26
+ /** Ids picked by a table-row click or a brush (sticky). */
27
+ readonly selected: ReadonlySet<string>;
28
+ /** The brush region as a row test; null when nothing is brushed. */
29
+ readonly predicate: RowFilter<Row> | null;
30
+ /** The shared hover value; null when the pointer is on no linked chart. */
31
+ readonly cursor: Cursor | null;
32
+ }
33
+ /**
34
+ * A partial update for {@link SelectionStore.set}: an absent field keeps its value. Each id goes through
35
+ * {@link toId}: a number is stringified (`12` and `"12"` are the same id) and null, undefined, NaN and `""` are
36
+ * dropped. A bare string throws `InputError` rather than being split into letters: pass `["KC"]`, not `"KC"`.
37
+ */
38
+ interface SelectionPatch<Row = unknown> {
39
+ /** The new hover ids (replaces the set; `[]` clears it). */
40
+ readonly hover?: Iterable<string>;
41
+ /** The new selected ids (replaces the set; `[]` clears it). */
42
+ readonly selected?: Iterable<string>;
43
+ /** The new brush region; `null` clears it. */
44
+ readonly predicate?: RowFilter<Row> | null;
45
+ /** The new hover value; `null` clears it. An equal cursor (same field, same value) is a no-op. */
46
+ readonly cursor?: Cursor | null;
47
+ }
48
+ /** The linked-interactivity hub that {@link createSelection} returns. */
49
+ interface SelectionStore<Row = unknown> {
50
+ /** Referentially stable until the next change: what `useSyncExternalStore` reads. */
51
+ getState(): SelectionState<Row>;
52
+ /** Apply a patch. ONE notification when anything changed, none when nothing did, so linked components cannot ping-pong. */
53
+ set(patch: SelectionPatch<Row>): void;
54
+ /** Clear hover, selected, predicate and cursor. */
55
+ clear(): void;
56
+ /**
57
+ * Call `fn` after every change; returns the unsubscribe function. A listener that throws stops neither the others
58
+ * nor `set`: its error is rethrown in a microtask, as an `EventTarget` listener's is, so it still reaches
59
+ * `window.onerror` (or Node's `uncaughtException`) but one broken view cannot freeze the rest. A listener removed
60
+ * during a notification is not called for it.
61
+ */
62
+ subscribe(fn: (state: SelectionState<Row>) => void): () => void;
63
+ }
64
+ /**
65
+ * The link id of a value: `String(v)`, or `""` (never matched) for null, undefined, NaN and `""`. A figure and a table
66
+ * link only when they hold the same ids, so an ESPN team id (`"12"`) never matches an abbreviation (`"KC"`).
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { toId } from "@sportsdataverse/sdvplot";
71
+ *
72
+ * [toId(12), toId("KC"), toId(null), toId(Number.NaN)];
73
+ * ```
74
+ */
75
+ declare function toId(v: unknown): string;
76
+ /**
77
+ * Whether two id sets hold the same ids, in any order.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { sameIds } from "@sportsdataverse/sdvplot";
82
+ *
83
+ * sameIds(new Set(["KC", "BUF"]), new Set(["BUF", "KC"]));
84
+ * ```
85
+ */
86
+ declare function sameIds(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean;
87
+ /**
88
+ * Whether two cursors are the same: the same field and an `===`-equal value, so `0` and `-0` are one hover value
89
+ * (NaN never gets into the store). `null` equals only `null`.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { sameCursor } from "@sportsdataverse/sdvplot";
94
+ *
95
+ * const rim = { field: "shot_distance", value: 0.5 }; // the centre of the 0-1 ft bin
96
+ * [
97
+ * sameCursor(rim, { field: "shot_distance", value: 0.5 }), // a new object, the same value: true
98
+ * sameCursor(rim, { field: "shot_distance", value: 1.5 }), // the next bin: false
99
+ * sameCursor(rim, null), // the pointer left: false
100
+ * sameCursor(null, null),
101
+ * ];
102
+ * ```
103
+ */
104
+ declare function sameCursor(a: Cursor | null, b: Cursor | null): boolean;
105
+ /**
106
+ * What a figure should emphasise. `null`: nothing is active, draw everything normally. A set, possibly EMPTY
107
+ * (a brush that encloses no point): emphasise those ids and dim the rest. Hover and selected are unioned. The
108
+ * cursor is ignored: a shared hover value never dims anything.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * import { createSelection, focusIds } from "@sportsdataverse/sdvplot";
113
+ *
114
+ * const store = createSelection();
115
+ * const idle = focusIds(store.getState()); // null: draw everything normally
116
+ * store.set({ selected: ["KC"], hover: ["BUF"] });
117
+ * ({ idle, focus: [...(focusIds(store.getState()) ?? [])] });
118
+ * ```
119
+ */
120
+ declare function focusIds<Row>(s: SelectionState<Row>): ReadonlySet<string> | null;
121
+ /**
122
+ * The linked-interactivity hub (J31): hover and selected id sets plus a row predicate. Pure data, zero deps, no DOM:
123
+ * creating or updating one in Node changes nothing a server renders. Ids are strings, normalised by {@link toId}
124
+ * (see {@link SelectionPatch}).
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * import { createSelection } from "@sportsdataverse/sdvplot";
129
+ *
130
+ * const store = createSelection();
131
+ * let updates = 0;
132
+ * store.subscribe(() => updates++);
133
+ * store.set({ selected: ["KC", "BUF"] });
134
+ * store.set({ selected: ["BUF", "KC"] }); // the same ids: silent, so linked views cannot ping-pong
135
+ * ({ updates, selected: [...store.getState().selected] });
136
+ * ```
137
+ */
138
+ declare function createSelection<Row = unknown>(): SelectionStore<Row>;
139
+
140
+ export { type Cursor as C, type RowFilter as R, type SelectionStore as S, type SelectionState as a, type SelectionPatch as b, createSelection as c, sameIds as d, focusIds as f, sameCursor as s, toId as t };