@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.
- package/LICENSE +21 -0
- package/NOTICE.md +94 -0
- package/README.md +908 -2
- package/dist/_web-D7Aa5Rd8.d.ts +69 -0
- package/dist/aaf-GJ3SH34T.js +13 -0
- package/dist/aaf-HCYN5T6Y.js +37 -0
- package/dist/aaf-ZAE5KS2Y.js +13 -0
- package/dist/ahl-E66MDVCU.js +66 -0
- package/dist/ahl-NPQQWZEV.js +249 -0
- package/dist/ahl-QRDU73XX.js +309 -0
- package/dist/axis-CraWQRUJ.d.ts +49 -0
- package/dist/bins.d.ts +174 -0
- package/dist/bins.js +4 -0
- package/dist/bins.js.map +1 -0
- package/dist/cfb-F42SVS4X.js +5000 -0
- package/dist/cfb-P3SHEYRZ.js +692 -0
- package/dist/cfb-WXRZANLP.js +10226 -0
- package/dist/chartjs-surface.d.ts +42 -0
- package/dist/chartjs-surface.js +65 -0
- package/dist/chartjs-surface.js.map +1 -0
- package/dist/chartjs.d.ts +132 -0
- package/dist/chartjs.js +339 -0
- package/dist/chartjs.js.map +1 -0
- package/dist/chunk-4C3GX6QO.js +92 -0
- package/dist/chunk-4C3GX6QO.js.map +1 -0
- package/dist/chunk-C2CDY5OG.js +137 -0
- package/dist/chunk-C2CDY5OG.js.map +1 -0
- package/dist/chunk-E7G7VSLV.js +43 -0
- package/dist/chunk-E7G7VSLV.js.map +1 -0
- package/dist/chunk-J6R2LEJ7.js +58 -0
- package/dist/chunk-J6R2LEJ7.js.map +1 -0
- package/dist/chunk-JJFTD4VB.js +175 -0
- package/dist/chunk-JJFTD4VB.js.map +1 -0
- package/dist/chunk-KKH6ABMA.js +223 -0
- package/dist/chunk-KKH6ABMA.js.map +1 -0
- package/dist/chunk-NLPH2VZT.js +127 -0
- package/dist/chunk-NLPH2VZT.js.map +1 -0
- package/dist/chunk-OKEE4AH6.js +30 -0
- package/dist/chunk-OKEE4AH6.js.map +1 -0
- package/dist/chunk-QY65DUEF.js +306 -0
- package/dist/chunk-QY65DUEF.js.map +1 -0
- package/dist/chunk-RMT2GMFH.js +97 -0
- package/dist/chunk-RMT2GMFH.js.map +1 -0
- package/dist/chunk-SEBO35DV.js +76 -0
- package/dist/chunk-SEBO35DV.js.map +1 -0
- package/dist/chunk-TAXO3VXH.js +556 -0
- package/dist/chunk-TAXO3VXH.js.map +1 -0
- package/dist/chunk-TDNBAEQB.js +105 -0
- package/dist/chunk-TDNBAEQB.js.map +1 -0
- package/dist/cricket-KPCYG2HL.js +493 -0
- package/dist/cricket-NDXLE25B.js +116 -0
- package/dist/cricket-YUUF7P6M.js +160 -0
- package/dist/d3.d.ts +234 -0
- package/dist/d3.js +198 -0
- package/dist/d3.js.map +1 -0
- package/dist/echarts.d.ts +121 -0
- package/dist/echarts.js +228 -0
- package/dist/echarts.js.map +1 -0
- package/dist/echl-3CR4RGFC.js +241 -0
- package/dist/echl-NPC32C23.js +107 -0
- package/dist/echl-PI33DGJC.js +64 -0
- package/dist/export.d.ts +130 -0
- package/dist/export.js +258 -0
- package/dist/export.js.map +1 -0
- package/dist/headshots-B2J_LOO8.d.ts +40 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/interact.d.ts +567 -0
- package/dist/interact.js +809 -0
- package/dist/interact.js.map +1 -0
- package/dist/marks-DH-tQIP-.d.ts +75 -0
- package/dist/mbb-3TRLVW3H.js +3619 -0
- package/dist/mbb-DQUHCHZP.js +5111 -0
- package/dist/mbb-PRAIO2P5.js +371 -0
- package/dist/milb-6IQ6NCEW.js +291 -0
- package/dist/milb-EYY5MD4T.js +1752 -0
- package/dist/milb-EYZ4SZLI.js +797 -0
- package/dist/mlb-4PBUJSVQ.js +35 -0
- package/dist/mlb-FHPY3GQJ.js +701 -0
- package/dist/mlb-REVGL3N2.js +665 -0
- package/dist/nba-ATT775GS.js +485 -0
- package/dist/nba-LKM4UHVU.js +482 -0
- package/dist/nba-YX7PXM3R.js +35 -0
- package/dist/nbagl-3WGZCZ4S.js +37 -0
- package/dist/nbagl-LP4XJRAL.js +229 -0
- package/dist/nbagl-VTTOU7LN.js +499 -0
- package/dist/ncaa_baseball-UV75LNVR.js +2670 -0
- package/dist/ncaa_baseball-VD5J5C72.js +419 -0
- package/dist/ncaa_baseball-YRBS32D6.js +4592 -0
- package/dist/ncaa_mhockey-IMYHS7QS.js +1033 -0
- package/dist/ncaa_mhockey-TXTKWHE3.js +115 -0
- package/dist/ncaa_mhockey-U3MFW5VT.js +663 -0
- package/dist/ncaa_softball-6WR6KS64.js +420 -0
- package/dist/ncaa_softball-RZCTIE56.js +2654 -0
- package/dist/ncaa_softball-VOD4PVGY.js +4599 -0
- package/dist/ncaa_whockey-6XXGUTBK.js +607 -0
- package/dist/ncaa_whockey-AKKYKY67.js +53 -0
- package/dist/ncaa_whockey-KBEKXFBB.js +293 -0
- package/dist/nfl-BDB2HYAR.js +625 -0
- package/dist/nfl-EHYHDLJD.js +37 -0
- package/dist/nfl-VF4NJCHY.js +514 -0
- package/dist/nfl_gsis-UY64JO37.js +4 -0
- package/dist/nhl-6WPHME7H.js +511 -0
- package/dist/nhl-EMIJFZHY.js +884 -0
- package/dist/nhl-ISFUKRFU.js +37 -0
- package/dist/ohl-2FEM554G.js +113 -0
- package/dist/ohl-C5Q4W6JB.js +32 -0
- package/dist/ohl-TQ4J46VC.js +66 -0
- package/dist/phf-H7POU2KW.js +32 -0
- package/dist/phf-K5IFMZUM.js +13 -0
- package/dist/phf-TWJI3BOW.js +20 -0
- package/dist/plot.d.ts +449 -0
- package/dist/plot.js +711 -0
- package/dist/plot.js.map +1 -0
- package/dist/plotly.d.ts +129 -0
- package/dist/plotly.js +360 -0
- package/dist/plotly.js.map +1 -0
- package/dist/pwhl-OX3BO4JI.js +53 -0
- package/dist/pwhl-W44OC3C6.js +17 -0
- package/dist/pwhl-ZJGG3H7W.js +23 -0
- package/dist/qmjhl-5HISEHLL.js +35 -0
- package/dist/qmjhl-DA3UHKE5.js +125 -0
- package/dist/qmjhl-YG4BOQPF.js +614 -0
- package/dist/react.d.ts +101 -0
- package/dist/react.js +164 -0
- package/dist/react.js.map +1 -0
- package/dist/resolve-CQW-jzvr.d.ts +28 -0
- package/dist/selection-D4T3PkRt.d.ts +140 -0
- package/dist/shots.d.ts +364 -0
- package/dist/shots.js +85 -0
- package/dist/shots.js.map +1 -0
- package/dist/soccer-7BH3ZLVL.js +7898 -0
- package/dist/soccer-MRU25OGE.js +2636 -0
- package/dist/soccer-PF4IPPVA.js +12342 -0
- package/dist/surfaces-ChaD9nei.d.ts +38 -0
- package/dist/testing.d.ts +75 -0
- package/dist/testing.js +240 -0
- package/dist/testing.js.map +1 -0
- package/dist/tiers-B6pUBPcE.d.ts +93 -0
- package/dist/types-C7n6gJF3.d.ts +62 -0
- package/dist/ufl-7VLZTLTT.js +143 -0
- package/dist/ufl-CCMBLPSW.js +100 -0
- package/dist/ufl-F2BJRXME.js +16 -0
- package/dist/usfl-4CR5RMZJ.js +32 -0
- package/dist/usfl-LD7HP2YK.js +14 -0
- package/dist/usfl-VU4YSJQD.js +19 -0
- package/dist/ushl-CPG2LB4L.js +73 -0
- package/dist/ushl-PQL6HJAD.js +88 -0
- package/dist/ushl-ZT2ATPHK.js +22 -0
- package/dist/vega.d.ts +85 -0
- package/dist/vega.js +362 -0
- package/dist/vega.js.map +1 -0
- package/dist/wbb-3KHCDYTK.js +5087 -0
- package/dist/wbb-BMX53Q3Q.js +369 -0
- package/dist/wbb-QNRN2CJJ.js +3620 -0
- package/dist/whl-4ATYZWUB.js +97 -0
- package/dist/whl-RVFPL6MH.js +79 -0
- package/dist/whl-Y7ASKYMM.js +28 -0
- package/dist/wnba-FWX5SFVI.js +232 -0
- package/dist/wnba-GX6HZR4B.js +220 -0
- package/dist/wnba-NTMWIPP6.js +20 -0
- package/dist/xfl-43R2JMJK.js +30 -0
- package/dist/xfl-H3BXQPZZ.js +16 -0
- package/dist/xfl-XON7A7FI.js +69 -0
- package/package.json +154 -3
package/dist/react.d.ts
ADDED
|
@@ -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 };
|