@sportsdataverse/sdvtables 0.0.0-stage → 0.2.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.
@@ -0,0 +1,181 @@
1
+ import { applyHover, renderInteractive, handleClick, handleInput, handleHover, handleKeydown, captureFocus, tableRenderOptions, renderToolbar, pagerLabel } from './chunk-3TJR7TKS.js';
2
+ import { loadTeamNames, assemble, renderParts, tableHTML } from './chunk-R375P3MN.js';
3
+ import { TableSpecError, THEME_NAMES } from './chunk-UTOVFIFT.js';
4
+ import { loadLeague, loadGsis } from '@sportsdataverse/sdvplot';
5
+
6
+ // src/html/hydrate.ts
7
+ var live = /* @__PURE__ */ new WeakMap();
8
+ var behind = /* @__PURE__ */ new WeakSet();
9
+ var edge = (button, atEdge) => {
10
+ if (atEdge) button?.setAttribute("aria-disabled", "true");
11
+ else button?.removeAttribute("aria-disabled");
12
+ };
13
+ function hydrate(el, table) {
14
+ const body = el.querySelector("[data-sdv-body]");
15
+ if (!body)
16
+ throw new TableSpecError(
17
+ "hydrate: no [data-sdv-body] in element; render it with renderHTML(table) first"
18
+ );
19
+ live.get(el)?.();
20
+ let hidden = table.state.hidden;
21
+ let drawn = behind.has(body) ? null : table.rows;
22
+ const render = () => {
23
+ cancel = void 0;
24
+ const root = el.getRootNode();
25
+ const restore = captureFocus(el);
26
+ const p = renderParts(table.spec, table.rows, { css: "none", ...tableRenderOptions(table) });
27
+ body.innerHTML = tableHTML(p);
28
+ drawn = table.rows;
29
+ const toolbar = el.querySelector(".sdvt-toolbar");
30
+ if (toolbar && table.state.hidden !== hidden) toolbar.outerHTML = renderToolbar(table, p.labels);
31
+ hidden = table.state.hidden;
32
+ const label = el.querySelector("[data-sdv-page-label]");
33
+ const page = pagerLabel(table);
34
+ if (label && label.textContent !== page) label.textContent = page;
35
+ edge(el.querySelector('[data-sdv-page="prev"]'), table.state.page === 0);
36
+ edge(el.querySelector('[data-sdv-page="next"]'), table.state.page >= table.pageCount - 1);
37
+ for (const input of Array.from(
38
+ el.querySelectorAll("[data-sdv-filter],[data-sdv-global-filter]")
39
+ )) {
40
+ if (input === root.activeElement) continue;
41
+ const key = input.getAttribute("data-sdv-filter");
42
+ const f = key === null ? table.state.globalFilter : table.state.filters[key];
43
+ const want = typeof f === "string" ? f : "";
44
+ if (input.value !== want) input.value = want;
45
+ }
46
+ applyHover(el, table, table.getHover());
47
+ restore();
48
+ };
49
+ let cancel;
50
+ const schedule = () => {
51
+ if (cancel) return;
52
+ if (typeof requestAnimationFrame === "function") {
53
+ const id = requestAnimationFrame(render);
54
+ cancel = () => cancelAnimationFrame(id);
55
+ } else {
56
+ const id = setTimeout(render, 0);
57
+ cancel = () => clearTimeout(id);
58
+ }
59
+ };
60
+ const same = (r, i) => {
61
+ const was = drawn?.[i];
62
+ return r === was || table.rowId(r) === table.rowId(was);
63
+ };
64
+ const flush = () => {
65
+ const rows = table.rows;
66
+ if (!cancel || rows.length === drawn?.length && rows.every(same)) return false;
67
+ cancel();
68
+ render();
69
+ return true;
70
+ };
71
+ const onClick = (e) => {
72
+ flush();
73
+ handleClick(table, e.target);
74
+ };
75
+ const onInput = (e) => handleInput(table, e.target);
76
+ const onHover = (e) => {
77
+ flush();
78
+ handleHover(table, e.target);
79
+ };
80
+ const onKeydown = (e) => {
81
+ const k = e;
82
+ if (flush() && (k.key === " " || k.key === "Enter") && k.target.matches(".sdvt-row[data-row]"))
83
+ k.preventDefault();
84
+ handleKeydown(table, el, k);
85
+ };
86
+ el.addEventListener("click", onClick);
87
+ el.addEventListener("input", onInput);
88
+ el.addEventListener("mouseover", onHover);
89
+ el.addEventListener("mouseleave", onHover);
90
+ el.addEventListener("keydown", onKeydown);
91
+ const unsubscribe = table.subscribe((e) => {
92
+ if (e.type !== "hover") schedule();
93
+ else if (cancel === void 0) applyHover(el, table, e.id);
94
+ });
95
+ applyHover(el, table, table.getHover());
96
+ if (behind.delete(body)) schedule();
97
+ const teardown = () => {
98
+ if (live.get(el) === replace) live.delete(el);
99
+ unsubscribe();
100
+ if (cancel) {
101
+ cancel();
102
+ behind.add(body);
103
+ }
104
+ cancel = void 0;
105
+ el.removeEventListener("click", onClick);
106
+ el.removeEventListener("input", onInput);
107
+ el.removeEventListener("mouseover", onHover);
108
+ el.removeEventListener("mouseleave", onHover);
109
+ el.removeEventListener("keydown", onKeydown);
110
+ };
111
+ const replace = () => {
112
+ if (cancel) {
113
+ cancel();
114
+ render();
115
+ }
116
+ teardown();
117
+ };
118
+ live.set(el, replace);
119
+ return teardown;
120
+ }
121
+
122
+ // src/html/index.ts
123
+ function leaguesOf(spec) {
124
+ const out = /* @__PURE__ */ new Set();
125
+ for (const c of spec.columns)
126
+ if ("league" in c && c.kind !== "headshot" && c.league !== void 0) out.add(c.league);
127
+ if (spec.theme.name === "sdvTeam" && spec.theme.options?.league)
128
+ out.add(spec.theme.options.league);
129
+ return [...out];
130
+ }
131
+ async function prepare(spec) {
132
+ const gsis = spec.columns.some((c) => c.kind === "headshot" && c.idSystem === "gsis");
133
+ await Promise.all([...leaguesOf(spec).map((l) => loadLeague(l)), ...gsis ? [loadGsis()] : []]);
134
+ await loadTeamNames(leaguesOf(spec));
135
+ }
136
+ function isTable(x) {
137
+ return typeof x.subscribe === "function";
138
+ }
139
+ function renderHTML(a, b, c = {}) {
140
+ if (isTable(a)) return renderInteractive(a, b ?? {});
141
+ return assemble(renderParts(a, b ?? [], c));
142
+ }
143
+ async function renderHTMLAsync(spec, rows, opts = {}) {
144
+ await prepare(spec);
145
+ return renderHTML(spec, rows, opts);
146
+ }
147
+ function toElement(spec, rows, opts = {}) {
148
+ if (typeof document === "undefined")
149
+ throw new TableSpecError("toElement needs a DOM (browser, happy-dom or jsdom); use renderHTML in Node");
150
+ const t = document.createElement("template");
151
+ t.innerHTML = renderHTML(spec, rows, opts).trim();
152
+ const el = t.content.querySelector("div.sdvt");
153
+ if (!(el instanceof HTMLElement)) throw new TableSpecError("renderHTML produced no wrapper");
154
+ const link = t.content.querySelector("link");
155
+ const href = link?.getAttribute("href");
156
+ if (link && href && !Array.from(document.head.querySelectorAll("link")).some((l) => l.getAttribute("href") === href))
157
+ document.head.append(link);
158
+ return el;
159
+ }
160
+ function themePreview(spec, rows, themes, o = {}) {
161
+ const n = o.n ?? 5;
162
+ if (!Number.isInteger(n) || n < 1) throw new TableSpecError(`n must be a positive whole number, got ${n}`);
163
+ const out = {};
164
+ for (const name of themes ?? THEME_NAMES)
165
+ out[name] = renderHTML(
166
+ {
167
+ ...spec,
168
+ theme: {
169
+ name,
170
+ density: o.density ?? "compact",
171
+ ...name === "sdvTeam" ? { options: { league: "nfl" } } : {}
172
+ }
173
+ },
174
+ rows.slice(0, n)
175
+ );
176
+ return out;
177
+ }
178
+
179
+ export { hydrate, leaguesOf, prepare, renderHTML, renderHTMLAsync, themePreview, toElement };
180
+ //# sourceMappingURL=chunk-VLF2S6VW.js.map
181
+ //# sourceMappingURL=chunk-VLF2S6VW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/html/hydrate.ts","../src/html/index.ts"],"names":[],"mappings":"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","file":"chunk-VLF2S6VW.js","sourcesContent":["import type { Table } from \"../engine.js\";\nimport { TableSpecError } from \"../errors.js\";\nimport {\n applyHover,\n captureFocus,\n handleClick,\n handleHover,\n handleInput,\n handleKeydown,\n} from \"./controls.js\";\nimport { pagerLabel, renderToolbar, tableRenderOptions } from \"./interactive.js\";\nimport { renderParts, tableHTML } from \"./parts.js\";\n\n/** I2: each element's live binding, so hydrating it again replaces the old one (see `replace`). */\nconst live = new WeakMap<Element, () => void>();\n/** Bodies a teardown left behind the engine (it dropped a render still owed); the next hydrate of one redraws it. */\n// ponytail: exists for React StrictMode's double effect (mount, cleanup, mount); weak, so it never holds a body alive\nconst behind = new WeakSet<Element>();\n\nconst edge = (button: Element | null, atEdge: boolean): void => {\n if (atEdge) button?.setAttribute(\"aria-disabled\", \"true\");\n else button?.removeAttribute(\"aria-disabled\");\n};\n\n/**\n * Progressive enhancement for markup produced by `renderHTML(table)`: one delegated click + input listener drive the\n * engine; each change re-renders ONLY the table block (`[data-sdv-body]`) and the pager, once, on the next animation\n * frame, however many changes land in it (the engine state itself moves synchronously). One exception: a click,\n * pointer move or key on the table while a change to its rows (a sort, filter, page turn or `setRows`) waits for that\n * frame draws it at once, so the event never reads an old row as the one now at its index; a click or key on a row\n * this replaced acts on nothing, and Enter or Space on it is consumed, so the page does not scroll. A change that keeps\n * the rows (a selection, the cursor, a keyed row re-sent as a new object) waits for its frame. Toolbar inputs are\n * rebuilt only when a column is hidden or shown; otherwise their values are synced from the engine state, so an\n * external filter change shows in the box. A row hover (the pointer's, or a linked figure's through `linkSelection`)\n * re-renders nothing: the row gets the `sdvt-hover` class, which survives the next re-render; a hover the engine\n * already holds (`getHover`) shows on attach. A focused control, or a focusable element inside a rendered cell, that\n * the re-render replaced gets focus back (shadow-root mounts included). Attaching does NOT reconcile the SSR markup\n * against the engine state: render the markup from the same table state you hydrate. Hydrating an element again\n * replaces its previous binding (HMR, client-side navigation, an effect without cleanup) after drawing any change that\n * binding still owed, so every control still acts once. The returned teardown is idempotent and drops a render still\n * owed; hydrating that element again redraws it (on the next frame, or at once for an event that comes first), so React\n * StrictMode's mount, cleanup, mount leaves no stale rows. Until the teardown runs, the table's subscriber keeps `el`\n * alive as long as the table lives.\n */\nexport function hydrate<Row>(el: Element, table: Table<Row>): () => void {\n const body = el.querySelector(\"[data-sdv-body]\");\n if (!body)\n throw new TableSpecError(\n \"hydrate: no [data-sdv-body] in element; render it with renderHTML(table) first\",\n );\n live.get(el)?.();\n\n let hidden = table.state.hidden; // the engine replaces this array only when a column is hidden or shown\n // the rows the body shows: the SSR markup's until the first render; unknown (null) on a body a teardown left behind,\n // so an event before its redraw frame redraws first, even when the engine now holds no row\n let drawn: readonly Row[] | null = behind.has(body) ? null : table.rows;\n const render = (): void => {\n cancel = undefined;\n const root = el.getRootNode() as Document | ShadowRoot;\n const restore = captureFocus(el);\n const p = renderParts(table.spec, table.rows, { css: \"none\", ...tableRenderOptions(table) });\n body.innerHTML = tableHTML(p);\n drawn = table.rows; // after the write: a render that throws leaves the rows the body still shows on record\n // M8: a hidden column's filter input leaves the toolbar (and comes back), as in renderHTML(table) and <SdvTable/>\n const toolbar = el.querySelector(\".sdvt-toolbar\");\n if (toolbar && table.state.hidden !== hidden) toolbar.outerHTML = renderToolbar(table, p.labels);\n hidden = table.state.hidden;\n const label = el.querySelector(\"[data-sdv-page-label]\");\n const page = pagerLabel(table);\n if (label && label.textContent !== page) label.textContent = page; // M6: an unchanged live region is not re-announced\n edge(el.querySelector('[data-sdv-page=\"prev\"]'), table.state.page === 0);\n edge(el.querySelector('[data-sdv-page=\"next\"]'), table.state.page >= table.pageCount - 1);\n // I1: an external setFilter / setGlobalFilter / clear moves the engine state, not the box; a focused input wins\n for (const input of Array.from(\n el.querySelectorAll<HTMLInputElement>(\"[data-sdv-filter],[data-sdv-global-filter]\"),\n )) {\n if (input === root.activeElement) continue;\n const key = input.getAttribute(\"data-sdv-filter\");\n const f = key === null ? table.state.globalFilter : table.state.filters[key];\n const want = typeof f === \"string\" ? f : \"\";\n if (input.value !== want) input.value = want;\n }\n applyHover(el, table, table.getHover()); // the body was rebuilt: a linked figure's hover survives it\n restore();\n };\n // M6: one render per animation frame however many changes land in it (typing in an unpaged 1000-row table\n // re-rendered ~660 KB per keystroke); a timer where there is no requestAnimationFrame\n let cancel: (() => void) | undefined;\n const schedule = (): void => {\n if (cancel) return;\n if (typeof requestAnimationFrame === \"function\") {\n const id = requestAnimationFrame(render);\n cancel = () => cancelAnimationFrame(id);\n } else {\n const id = setTimeout(render, 0);\n cancel = () => clearTimeout(id);\n }\n };\n // a row event while a render that changes the rows is pending would read the old body's data-row against the new\n // rows (LV's row as BUF): render first, so the target is a current row, or a replaced one that names no row. A\n // pending render that keeps the rows (a selection, the cursor) leaves data-row right, and the target in place.\n // A keyed row re-sent as a new object (a live feed) keeps its id, so the old body's data-row still names it: it moved\n // only when its object AND its id differ. Never ids saved at render: without a rowKey those are positions, so the\n // old row 3 would read as whatever is at index 3 now; the engine's id of an old object is \"\" (gone) or its new index\n const same = (r: Row, i: number): boolean => {\n const was = drawn?.[i] as Row; // the lengths match\n return r === was || table.rowId(r) === table.rowId(was);\n };\n const flush = (): boolean => {\n const rows = table.rows;\n if (!cancel || (rows.length === drawn?.length && rows.every(same))) return false;\n cancel();\n render();\n return true;\n };\n const onClick = (e: Event): void => {\n flush();\n handleClick(table, e.target);\n };\n const onInput = (e: Event): void => handleInput(table, e.target);\n const onHover = (e: Event): void => {\n flush();\n handleHover(table, e.target); // J31 (A7)\n };\n const onKeydown = (e: Event): void => {\n const k = e as KeyboardEvent;\n // the flush replaced the focused body row: Enter and Space act on nothing, and Space must not scroll the page. A\n // row only: a sort button rebuilt under focus gets it back, and the browser's activation of it still sorts\n if (\n flush() &&\n (k.key === \" \" || k.key === \"Enter\") &&\n (k.target as Element).matches(\".sdvt-row[data-row]\")\n )\n k.preventDefault();\n handleKeydown(table, el, k); // Task 10\n };\n\n el.addEventListener(\"click\", onClick);\n el.addEventListener(\"input\", onInput);\n el.addEventListener(\"mouseover\", onHover);\n el.addEventListener(\"mouseleave\", onHover);\n el.addEventListener(\"keydown\", onKeydown);\n // J31: a hover changes no state, and re-rendering on it would replace the row under the pointer: a class toggle\n const unsubscribe = table.subscribe((e) => {\n if (e.type !== \"hover\") schedule();\n // a pending render means the DOM still shows the old rows, which data-row would misread: render applies it\n else if (cancel === undefined) applyHover(el, table, e.id);\n });\n applyHover(el, table, table.getHover()); // a hover set before this attached (a linked figure's) shows now\n if (behind.delete(body)) schedule(); // a teardown dropped a render this body still owes (StrictMode's cleanup)\n const teardown = (): void => {\n if (live.get(el) === replace) live.delete(el);\n unsubscribe();\n if (cancel) {\n cancel();\n behind.add(body); // the body stays behind the engine until the next hydrate of it redraws\n }\n cancel = undefined;\n el.removeEventListener(\"click\", onClick);\n el.removeEventListener(\"input\", onInput);\n el.removeEventListener(\"mouseover\", onHover);\n el.removeEventListener(\"mouseleave\", onHover);\n el.removeEventListener(\"keydown\", onKeydown);\n };\n // M3: hydrating `el` again first draws a change this binding still owes, so the next one starts from a current body\n // (its `drawn` is the engine's rows); a plain teardown drops it, since the element may no longer be ours to write,\n // and leaves the body `behind` for whichever hydrate comes next\n const replace = (): void => {\n if (cancel) {\n cancel();\n render();\n }\n teardown();\n };\n live.set(el, replace);\n return teardown;\n}\n","// src/html/index.ts — the public renderer entry; the body lives in parts.ts (Phase 5 Task 2).\nimport { loadGsis, loadLeague } from \"@sportsdataverse/sdvplot\";\nimport type { League } from \"@sportsdataverse/sdvplot\";\nimport type { Table } from \"../engine.js\";\nimport { TableSpecError } from \"../errors.js\";\nimport type { Density, TableSpec } from \"../spec.js\";\nimport { loadTeamNames } from \"../team-name.js\";\nimport { THEME_NAMES } from \"../themes/index.js\";\nimport { renderInteractive } from \"./interactive.js\";\nimport { type RenderOptions, assemble, labelOf, renderParts } from \"./parts.js\";\n\nexport type { ColumnScale, RenderContext } from \"./cells.js\";\nexport { fontsLink } from \"./fonts.js\";\nexport { ICON_NAMES } from \"./icons.js\";\nexport type { IconName } from \"../spec.js\";\nexport type { GoogleFont } from \"../themes/tokens.js\";\nexport type { RenderOptions, RenderedParts } from \"./parts.js\";\nexport {\n assemble,\n attrsText,\n fontsLinkFor,\n labelOf,\n renderParts,\n sortAria,\n styleSheet,\n tableHTML,\n themeKey,\n} from \"./parts.js\";\n\n/** Leagues whose shards the spec needs (team-aware columns + the sdvTeam theme). Headshot columns need no shard: ESPN ids are pure URLs. */\nexport function leaguesOf<Row>(spec: TableSpec<Row>): League[] {\n const out = new Set<League>();\n for (const c of spec.columns)\n if (\"league\" in c && c.kind !== \"headshot\" && c.league !== undefined) out.add(c.league);\n if (spec.theme.name === \"sdvTeam\" && spec.theme.options?.league)\n out.add(spec.theme.options.league as League);\n return [...out];\n}\n/** The documented preparation step: every league shard the spec names, plus the gsis map when a headshot column uses gsis ids (J27). Task 7 adds team names. */\nexport async function prepare<Row>(spec: TableSpec<Row>): Promise<void> {\n const gsis = spec.columns.some((c) => c.kind === \"headshot\" && c.idSystem === \"gsis\");\n await Promise.all([...leaguesOf(spec).map((l) => loadLeague(l)), ...(gsis ? [loadGsis()] : [])]);\n await loadTeamNames(leaguesOf(spec));\n}\n\nfunction isTable<Row>(x: TableSpec<Row> | Table<Row>): x is Table<Row> {\n return typeof (x as Table<Row>).subscribe === \"function\";\n}\n/**\n * Static markup from a spec, or the interactive document (toolbar, aria-sort headers, pager) from a `createTable`\n * instance.\n *\n * A spec renders as one HTML string, synchronously: the theme as a scoped `<style>` (with `css: \"none\"` the host\n * page emits `styleSheet(spec)` once instead), then the table. Cells that read league data (logos, team names) need\n * `await prepare(spec)` first, or use `renderHTMLAsync`. Throws `TableSpecError` for a column the rows lack or an\n * unknown theme.\n *\n * @example\n * ```ts\n * import { defineTable } from \"@sportsdataverse/sdvtables\";\n * import { renderHTML } from \"@sportsdataverse/sdvtables/html\";\n *\n * const rows = [\n * { team: \"BUF\", pf: 525, pa: 368 },\n * { team: \"DEN\", pf: 425, pa: 311 },\n * { team: \"LAC\", pf: 402, pa: 301 },\n * ];\n * const spec = defineTable<(typeof rows)[number]>()\n * .columns((c) => [c.text(\"team\"), c.int(\"pf\", { label: \"Points for\" }), c.int(\"pa\", { label: \"Points against\" })])\n * .theme(\"athletic\")\n * .build();\n * renderHTML(spec, rows);\n * ```\n */\nexport function renderHTML<Row>(table: Table<Row>, opts?: RenderOptions): string;\nexport function renderHTML<Row>(spec: TableSpec<Row>, rows: readonly Row[], opts?: RenderOptions): string;\nexport function renderHTML<Row>(\n a: TableSpec<Row> | Table<Row>,\n b?: readonly Row[] | RenderOptions,\n c: RenderOptions = {},\n): string {\n if (isTable(a)) return renderInteractive(a, (b as RenderOptions | undefined) ?? {});\n return assemble(renderParts(a, (b as readonly Row[] | undefined) ?? [], c));\n}\nexport { createTable } from \"../engine.js\";\nexport { handleClick, handleHover, handleInput, handleKeydown, keyAction, rowIdAt } from \"./controls.js\";\nexport type { KeyAction, KeyContext } from \"./controls.js\";\nexport { hydrate } from \"./hydrate.js\";\nexport {\n pagerLabel,\n renderInteractive,\n renderPager,\n renderToolbar,\n tableRenderOptions,\n} from \"./interactive.js\";\nexport async function renderHTMLAsync<Row>(\n spec: TableSpec<Row>,\n rows: readonly Row[],\n opts: RenderOptions = {},\n): Promise<string> {\n await prepare(spec);\n return renderHTML(spec, rows, opts);\n}\nexport function toElement<Row>(\n spec: TableSpec<Row>,\n rows: readonly Row[],\n opts: RenderOptions = {},\n): HTMLElement {\n if (typeof document === \"undefined\")\n throw new TableSpecError(\"toElement needs a DOM (browser, happy-dom or jsdom); use renderHTML in Node\");\n const t = document.createElement(\"template\");\n t.innerHTML = renderHTML(spec, rows, opts).trim();\n const el = t.content.querySelector(\"div.sdvt\");\n if (!(el instanceof HTMLElement)) throw new TableSpecError(\"renderHTML produced no wrapper\");\n // the fonts <link> goes to <head> once per href (A100), so the theme fonts load without a duplicate per table\n const link = t.content.querySelector(\"link\");\n const href = link?.getAttribute(\"href\");\n if (\n link &&\n href &&\n !Array.from(document.head.querySelectorAll(\"link\")).some((l) => l.getAttribute(\"href\") === href)\n )\n document.head.append(link);\n return el;\n}\n/**\n * gt_theme_preview (_themes.py:2058-2064): one HTML string per theme, first n rows, compact; sdvTeam shown with\n * league nfl and no team.\n *\n * @example\n * ```ts\n * import { defineTable } from \"@sportsdataverse/sdvtables\";\n * import { themePreview } from \"@sportsdataverse/sdvtables/html\";\n *\n * const rows = [\n * { team: \"KC\", wins: 15, losses: 2 },\n * { team: \"BUF\", wins: 13, losses: 4 },\n * ];\n * const spec = defineTable<(typeof rows)[number]>()\n * .columns((c) => [c.text(\"team\"), c.int(\"wins\"), c.int(\"losses\")])\n * .build();\n * Object.values(themePreview(spec, rows, [\"sdv\", \"athletic\", \"midnight\"])).join(\"\\n\");\n * ```\n */\nexport function themePreview<Row>(\n spec: TableSpec<Row>,\n rows: readonly Row[],\n themes?: readonly string[],\n o: { n?: number; density?: Density } = {},\n): Record<string, string> {\n const n = o.n ?? 5;\n if (!Number.isInteger(n) || n < 1) throw new TableSpecError(`n must be a positive whole number, got ${n}`);\n const out: Record<string, string> = {};\n for (const name of themes ?? THEME_NAMES)\n out[name] = renderHTML(\n {\n ...spec,\n theme: {\n name,\n density: o.density ?? \"compact\",\n ...(name === \"sdvTeam\" ? { options: { league: \"nfl\" } } : {}),\n },\n },\n rows.slice(0, n),\n );\n return out;\n}\nexport { labelOf as columnLabel };\n"]}
@@ -0,0 +1,189 @@
1
+ import { scaleNoteOf } from './chunk-R375P3MN.js';
2
+ import { isBlank, TableSpecError, toNumber, ordinal, comparatorFor, withMissingLast, formatNumber, scaledNumber } from './chunk-UTOVFIFT.js';
3
+ import { hex6, solid, onColor, logoUrlSync, headshotUrl, warn, InputError, checkAlpha, resolveSync } from '@sportsdataverse/sdvplot';
4
+
5
+ var cell = (v) => isBlank(v) ? "" : String(v).trim();
6
+ var value = (row, key) => row == null ? void 0 : row[key];
7
+ var opaque = (t) => t.bg === "transparent" ? "#ffffff" : hex6(t.bg);
8
+ var alphaHex = (a) => Math.round(checkAlpha(a) * 255).toString(16).padStart(2, "0");
9
+ var tint = (color, alpha, bg) => {
10
+ const fill = solid(`${hex6(color, { dropAlpha: true })}${alphaHex(alpha)}`, bg);
11
+ return { fill, ink: onColor(fill) };
12
+ };
13
+ function teamIds(rows, key, league, o) {
14
+ const raw = [...new Set(rows.map((r) => cell(value(r, key))).filter((v) => v !== ""))];
15
+ const ids = resolveSync(raw, league, {
16
+ ...o.season === void 0 ? {} : { season: o.season },
17
+ ...o.idSystem === void 0 ? {} : { idSystem: o.idSystem },
18
+ strict: o.strict ?? false
19
+ });
20
+ const out = /* @__PURE__ */ new Map();
21
+ raw.forEach((v, i) => {
22
+ const id = ids[i];
23
+ if (id !== void 0) out.set(v, id);
24
+ });
25
+ return out;
26
+ }
27
+ function palette(teamColorPalette, league, ids, which, bg) {
28
+ const colors = teamColorPalette(league, ids, {
29
+ idSystem: "team_id",
30
+ background: bg,
31
+ ...which === void 0 ? {} : { which }
32
+ });
33
+ return new Map(ids.map((id, i) => [id, colors[i] ?? "#808080"]));
34
+ }
35
+ function teamColorOf(col, rows, bg, teamColorPalette) {
36
+ const ids = teamIds(rows, col.key, col.league, col);
37
+ const colors = palette(teamColorPalette, col.league, [...new Set(ids.values())], col.which, bg);
38
+ return (raw) => {
39
+ const id = ids.get(cell(raw));
40
+ return (id === void 0 ? void 0 : colors.get(id)) ?? col.naColor;
41
+ };
42
+ }
43
+ function teamRowFills(rows, tr, bg, teamColorPalette) {
44
+ const alpha = tr.alpha ?? 0.4;
45
+ const ids = teamIds(rows, tr.key, tr.league, tr);
46
+ const colors = palette(teamColorPalette, tr.league, [...new Set(ids.values())], tr.which, bg);
47
+ const out = /* @__PURE__ */ new Map();
48
+ for (const [v, id] of ids) {
49
+ const color = colors.get(id);
50
+ if (color !== void 0) out.set(v, tint(color, alpha, bg));
51
+ }
52
+ return out;
53
+ }
54
+ function checkHeight(h, kind) {
55
+ if (!(Number.isFinite(h) && h >= 1))
56
+ throw new TableSpecError(`${kind} height must be a number of pixels >= 1, got ${String(h)}`);
57
+ return h;
58
+ }
59
+ function logoMarks(col, rows) {
60
+ const marks = /* @__PURE__ */ new Map();
61
+ for (const [v, id] of teamIds(rows, col.key, col.league, col)) {
62
+ const url = logoUrlSync(id, col.league, {
63
+ ...col.season === void 0 ? {} : { season: col.season },
64
+ ...col.variant === void 0 ? {} : { variant: col.variant },
65
+ markType: col.kind,
66
+ idSystem: "team_id"
67
+ });
68
+ if (url !== void 0) marks.set(v, { id, url });
69
+ }
70
+ return marks;
71
+ }
72
+ function headshotUrls(col, rows, who) {
73
+ const urls = /* @__PURE__ */ new Map();
74
+ for (const v of new Set(rows.map((r) => cell(value(r, col.key))))) {
75
+ if (v === "") continue;
76
+ const url = headshotUrl(v, col.league, { idSystem: col.idSystem });
77
+ if (url === void 0)
78
+ warn(
79
+ `sdvtables:${who.ns}:headshot:${col.league}:${v}`,
80
+ `${who.fn}: no ${col.league} headshot for player ${JSON.stringify(v)}: the cell keeps the id`
81
+ );
82
+ else urls.set(v, url);
83
+ }
84
+ return urls;
85
+ }
86
+ function scaleNotes(spec) {
87
+ const scaled = /* @__PURE__ */ new Map();
88
+ const labelSuffix = /* @__PURE__ */ new Map();
89
+ for (const d of spec.decorations) {
90
+ if (d.type !== "scaleNote") continue;
91
+ const { suffix } = scaleNoteOf(d);
92
+ for (const k of d.columns) {
93
+ scaled.set(k, { divisor: d.divisor, decimals: d.decimals });
94
+ if (d.where !== "sourceNote") labelSuffix.set(k, ` ${suffix}`);
95
+ }
96
+ }
97
+ return { scaled, labelSuffix };
98
+ }
99
+ var numberFormat = (f) => (v) => {
100
+ const n = toNumber(v);
101
+ return n === null ? "" : f(n);
102
+ };
103
+ function formatterFor(col, scaled) {
104
+ switch (col.kind) {
105
+ case "num": {
106
+ const sc = scaled.get(col.key);
107
+ if (sc) return numberFormat((n) => scaledNumber(n, sc));
108
+ return numberFormat(
109
+ (n) => formatNumber(n, {
110
+ ...col.digits === void 0 ? {} : { digits: col.digits },
111
+ big: col.big ?? false,
112
+ prefix: col.prefix ?? "",
113
+ suffix: col.suffix ?? "",
114
+ forceSign: col.forceSign ?? false
115
+ })
116
+ );
117
+ }
118
+ case "int": {
119
+ const sc = scaled.get(col.key);
120
+ return numberFormat((n) => sc ? scaledNumber(n, sc) : formatNumber(n, { digits: 0, big: true }));
121
+ }
122
+ case "pct":
123
+ return numberFormat((n) => `${formatNumber(col.scale ? n * 100 : n, { digits: col.digits })}%`);
124
+ case "rank":
125
+ return numberFormat(ordinal);
126
+ default:
127
+ return void 0;
128
+ }
129
+ }
130
+ function rankParts(v) {
131
+ const n = toNumber(v);
132
+ if (n === null) return null;
133
+ const num = String(Math.trunc(n));
134
+ return { num, suffix: ordinal(n).slice(num.length) };
135
+ }
136
+ function missingLast(col, rows) {
137
+ const compare = comparatorFor(col, rows);
138
+ return (dir) => withMissingLast(compare, dir);
139
+ }
140
+ function pxWidth(col, who) {
141
+ if (col.width === void 0) return void 0;
142
+ const m = /^\s*(\d+(?:\.\d+)?)px\s*$/.exec(col.width);
143
+ if (m) return Number(m[1]);
144
+ warn(
145
+ `sdvtables:${who.ns}:width:${col.key}`,
146
+ `${who.fn}: column "${col.key}" width ${JSON.stringify(col.width)} is not in px; ${who.grid} widths are px numbers, so ${who.short}'s default is kept`
147
+ );
148
+ return void 0;
149
+ }
150
+ function unmapped(kind, who) {
151
+ warn(
152
+ `sdvtables:${who.ns}:kind:${kind}`,
153
+ `${who.fn}: ${who.grid} cells for "${kind}" columns are not mapped yet; they show the plain value`
154
+ );
155
+ }
156
+ function rowIds(rows, key, who) {
157
+ const keyOf = (row, i) => {
158
+ const v = value(row, key);
159
+ if (isBlank(v))
160
+ throw new InputError(
161
+ `${who.fn}: row ${i} has no value for rowKey "${String(key)}" (${String(v)}); ${who.grid} needs an id for every row`
162
+ );
163
+ return String(v);
164
+ };
165
+ const index = /* @__PURE__ */ new Map();
166
+ const seen = /* @__PURE__ */ new Map();
167
+ rows.forEach((r, i) => {
168
+ const id = key === void 0 ? String(index.has(r) ? index.get(r) : i) : keyOf(r, i);
169
+ const j = seen.get(id);
170
+ if (j !== void 0)
171
+ throw new InputError(
172
+ key === void 0 ? `${who.fn}: rows ${j} and ${i} are the same object, so ${who.grid} would give them one id and drop one; copy the row or set the spec's rowKey` : `${who.fn}: rows ${j} and ${i} have the same rowKey "${String(key)}" value "${id}"; ${who.grid} needs a unique id per row`
173
+ );
174
+ seen.set(id, i);
175
+ if (!index.has(r)) index.set(r, id);
176
+ });
177
+ return (row) => {
178
+ const known = index.get(row);
179
+ if (known !== void 0) return known;
180
+ if (key !== void 0) return keyOf(row, "(not in rows)");
181
+ throw new TableSpecError(
182
+ `${who.fn}: a row the ${who.built} were not built from has no id; set the spec's rowKey, or build the ${who.built} from the rows you pass the grid`
183
+ );
184
+ };
185
+ }
186
+
187
+ export { cell, checkHeight, formatterFor, headshotUrls, logoMarks, missingLast, opaque, pxWidth, rankParts, rowIds, scaleNotes, teamColorOf, teamRowFills, tint, unmapped, value };
188
+ //# sourceMappingURL=chunk-WBTFIGYK.js.map
189
+ //# sourceMappingURL=chunk-WBTFIGYK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_grid.ts"],"names":[],"mappings":"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","file":"chunk-WBTFIGYK.js","sourcesContent":["/**\n * What the data grid adapters (`./mui` for MUI X Data Grid, `./aggrid` for AG Grid) share: reading cells, team ids and\n * colours, row ids and their checks, number formats (with a scaleNote and the rank suffix), the missing-last sort and the\n * image lookups with their warnings. Internal, not a subpath. It imports neither grid: each adapter turns these values\n * into its own grid's shapes (MUI's `sx` classes and React cells, AG Grid's inline styles and DOM cell renderers).\n */\nimport {\n type IdSystem,\n InputError,\n type League,\n type SeasonInput,\n type Which,\n checkAlpha,\n headshotUrl,\n hex6,\n logoUrlSync,\n onColor,\n resolveSync,\n solid,\n warn,\n} from \"@sportsdataverse/sdvplot\";\nimport { type Comparator, type SortDir, comparatorFor, withMissingLast } from \"./engine.js\";\nimport { TableSpecError } from \"./errors.js\";\nimport { formatNumber, isBlank, ordinal, scaledNumber, toNumber } from \"./format.js\";\nimport { scaleNoteOf } from \"./html/decorations.js\";\nimport type { ColumnSpec, TableSpec } from \"./spec.js\";\nimport type { ThemeTokens } from \"./themes/tokens.js\";\n\n/** Which adapter is speaking, for its warnings and errors. */\nexport interface GridName {\n /** The adapter's function, which starts each message: \"dataGridProps\". */\n readonly fn: string;\n /** The warning key's namespace: \"mui\". */\n readonly ns: string;\n /** The grid, in messages: \"MUI X Data Grid\". */\n readonly grid: string;\n /** The grid, in \"<short>'s default is kept\": \"MUI\". */\n readonly short: string;\n /** What the adapter builds, in messages: \"props\". */\n readonly built: string;\n}\n/** sdvplot's team palette for a background (each adapter passes its own subpath's `teamColorPalette`). */\nexport type PaletteFn = (\n league: League,\n teams: readonly unknown[],\n o: { idSystem?: IdSystem; background?: string; which?: Which },\n) => string[];\n/** A colour laid over the background, opaque, and its readable text colour. */\nexport interface Fill {\n readonly fill: string;\n readonly ink: string;\n}\n\n/** A cell's text: trimmed, \"\" for a blank value. Team and image lookups key on it, so 12 and \"12\" are one team. */\nexport const cell = (v: unknown): string => (isBlank(v) ? \"\" : String(v).trim());\nexport const value = (row: unknown, key: string): unknown =>\n row == null ? undefined : (row as Record<string, unknown>)[key];\n/** The theme's background as an opaque hex (white for a transparent theme). */\nexport const opaque = (t: ThemeTokens): string => (t.bg === \"transparent\" ? \"#ffffff\" : hex6(t.bg));\nconst alphaHex = (a: number): string =>\n Math.round(checkAlpha(a) * 255)\n .toString(16)\n .padStart(2, \"0\");\n/** `color` over `bg` at `alpha`, opaque, with its readable text colour. */\nexport const tint = (color: string, alpha: number, bg: string): Fill => {\n const fill = solid(`${hex6(color, { dropAlpha: true })}${alphaHex(alpha)}`, bg);\n return { fill, ink: onColor(fill) };\n};\n\n/** Each distinct cell value of `key` (trimmed text) -> its team id, by ONE resolveSync over the rows (one warning). */\nexport function teamIds(\n rows: readonly unknown[],\n key: string,\n league: League,\n o: { season?: SeasonInput | undefined; idSystem?: IdSystem | undefined; strict?: boolean | undefined },\n): Map<string, string> {\n const raw = [...new Set(rows.map((r) => cell(value(r, key))).filter((v) => v !== \"\"))];\n const ids = resolveSync(raw, league, {\n ...(o.season === undefined ? {} : { season: o.season }),\n ...(o.idSystem === undefined ? {} : { idSystem: o.idSystem }),\n strict: o.strict ?? false,\n });\n const out = new Map<string, string>();\n raw.forEach((v, i) => {\n const id = ids[i];\n if (id !== undefined) out.set(v, id);\n });\n return out;\n}\n/** Each team id -> its palette colour for `bg` (sdvplot's `teamColorPalette`, not a fork of it). */\nexport function palette(\n teamColorPalette: PaletteFn,\n league: League,\n ids: readonly string[],\n which: Which | undefined,\n bg: string,\n): Map<string, string> {\n const colors = teamColorPalette(league, ids, {\n idSystem: \"team_id\",\n background: bg,\n ...(which === undefined ? {} : { which }),\n });\n return new Map(ids.map((id, i) => [id, colors[i] ?? \"#808080\"]));\n}\n/** A `teamColorBg` / `teamColorBar` column: each cell value's colour, its team's palette colour else `naColor`. */\nexport function teamColorOf<Row>(\n col: ColumnSpec<Row> & { kind: \"teamColorBg\" | \"teamColorBar\" },\n rows: readonly Row[],\n bg: string,\n teamColorPalette: PaletteFn,\n): (raw: unknown) => string {\n const ids = teamIds(rows, col.key, col.league, col);\n const colors = palette(teamColorPalette, col.league, [...new Set(ids.values())], col.which, bg);\n return (raw) => {\n const id = ids.get(cell(raw));\n return (id === undefined ? undefined : colors.get(id)) ?? col.naColor;\n };\n}\n/** `teamRows`: each team value (trimmed text) -> its fill and ink; a team that is not found has none. */\nexport function teamRowFills(\n rows: readonly unknown[],\n tr: {\n key: string;\n league: League;\n which?: Which | undefined;\n alpha?: number | undefined;\n season?: SeasonInput | undefined;\n idSystem?: IdSystem | undefined;\n },\n bg: string,\n teamColorPalette: PaletteFn,\n): Map<string, Fill> {\n const alpha = tr.alpha ?? 0.4;\n const ids = teamIds(rows, tr.key, tr.league, tr);\n const colors = palette(teamColorPalette, tr.league, [...new Set(ids.values())], tr.which, bg);\n const out = new Map<string, Fill>();\n for (const [v, id] of ids) {\n const color = colors.get(id);\n if (color !== undefined) out.set(v, tint(color, alpha, bg));\n }\n return out;\n}\n\nexport function checkHeight(h: number, kind: string): number {\n if (!(Number.isFinite(h) && h >= 1))\n throw new TableSpecError(`${kind} height must be a number of pixels >= 1, got ${String(h)}`);\n return h;\n}\n/** A logo or wordmark column: each cell value (trimmed text) -> its team id and image URL; no URL, no entry. */\nexport function logoMarks<Row>(\n col: ColumnSpec<Row> & { kind: \"logo\" | \"wordmark\" },\n rows: readonly Row[],\n): Map<string, { id: string; url: string }> {\n const marks = new Map<string, { id: string; url: string }>();\n for (const [v, id] of teamIds(rows, col.key, col.league, col)) {\n const url = logoUrlSync(id, col.league, {\n ...(col.season === undefined ? {} : { season: col.season }),\n ...(col.variant === undefined ? {} : { variant: col.variant }),\n markType: col.kind,\n idSystem: \"team_id\",\n }); // undefined: no mark archived, and sdvplot warned once\n if (url !== undefined) marks.set(v, { id, url });\n }\n return marks;\n}\n/** A headshot column: each player id (trimmed text) -> its headshot URL; a player with none warns once. */\nexport function headshotUrls<Row>(\n col: ColumnSpec<Row> & { kind: \"headshot\" },\n rows: readonly Row[],\n who: GridName,\n): Map<string, string> {\n const urls = new Map<string, string>();\n for (const v of new Set(rows.map((r) => cell(value(r, col.key))))) {\n if (v === \"\") continue;\n const url = headshotUrl(v, col.league, { idSystem: col.idSystem });\n if (url === undefined)\n warn(\n `sdvtables:${who.ns}:headshot:${col.league}:${v}`,\n `${who.fn}: no ${col.league} headshot for player ${JSON.stringify(v)}: the cell keeps the id`,\n );\n else urls.set(v, url);\n }\n return urls;\n}\n\n/** scaleNote, as the HTML table applies it: num/int cells divided (with its decimals), the label suffixed; last wins. */\nexport function scaleNotes<Row>(spec: TableSpec<Row>): {\n scaled: Map<string, { divisor: number; decimals: number }>;\n labelSuffix: Map<string, string>;\n} {\n const scaled = new Map<string, { divisor: number; decimals: number }>();\n const labelSuffix = new Map<string, string>();\n for (const d of spec.decorations) {\n if (d.type !== \"scaleNote\") continue;\n const { suffix } = scaleNoteOf(d);\n for (const k of d.columns as readonly string[]) {\n scaled.set(k, { divisor: d.divisor, decimals: d.decimals });\n if (d.where !== \"sourceNote\") labelSuffix.set(k, ` ${suffix}`);\n }\n }\n return { scaled, labelSuffix };\n}\nconst numberFormat =\n (f: (n: number) => string) =>\n (v: unknown): string => {\n const n = toNumber(v);\n return n === null ? \"\" : f(n);\n };\n/** A number column's cell text, as the HTML table writes it (\"\" for a blank value); undefined for other kinds. */\nexport function formatterFor<Row>(\n col: ColumnSpec<Row>,\n scaled: ReadonlyMap<string, { divisor: number; decimals: number }>,\n): ((v: unknown) => string) | undefined {\n switch (col.kind) {\n case \"num\": {\n const sc = scaled.get(col.key);\n if (sc) return numberFormat((n) => scaledNumber(n, sc));\n return numberFormat((n) =>\n formatNumber(n, {\n ...(col.digits === undefined ? {} : { digits: col.digits }),\n big: col.big ?? false,\n prefix: col.prefix ?? \"\",\n suffix: col.suffix ?? \"\",\n forceSign: col.forceSign ?? false,\n }),\n );\n }\n case \"int\": {\n const sc = scaled.get(col.key);\n return numberFormat((n) => (sc ? scaledNumber(n, sc) : formatNumber(n, { digits: 0, big: true })));\n }\n case \"pct\":\n return numberFormat((n) => `${formatNumber(col.scale ? n * 100 : n, { digits: col.digits })}%`);\n case \"rank\":\n return numberFormat(ordinal);\n default:\n return undefined;\n }\n}\n/** A rank's number and its suffix, for the HTML table's superscript (`1<sup>st</sup>`); null for a blank value. */\nexport function rankParts(v: unknown): { num: string; suffix: string } | null {\n const n = toNumber(v);\n if (n === null) return null;\n const num = String(Math.trunc(n));\n return { num, suffix: ordinal(n).slice(num.length) };\n}\n/** sdvtables' own sort for a column (its `compare`, else by kind), missing values last in the given direction. */\nexport function missingLast<Row>(col: ColumnSpec<Row>, rows: readonly Row[]): (dir: SortDir) => Comparator {\n const compare = comparatorFor(col, rows);\n return (dir) => withMissingLast(compare, dir);\n}\n/** A column's px `width` as a number; any other CSS width is left to the grid, with one warning. */\nexport function pxWidth<Row>(col: ColumnSpec<Row>, who: GridName): number | undefined {\n if (col.width === undefined) return undefined;\n const m = /^\\s*(\\d+(?:\\.\\d+)?)px\\s*$/.exec(col.width);\n if (m) return Number(m[1]);\n warn(\n `sdvtables:${who.ns}:width:${col.key}`,\n `${who.fn}: column \"${col.key}\" width ${JSON.stringify(col.width)} is not in px; ${who.grid} widths are px numbers, so ${who.short}'s default is kept`,\n );\n return undefined;\n}\n/** A kind the grid draws as its plain value: one warning per kind. */\nexport function unmapped(kind: string, who: GridName): void {\n warn(\n `sdvtables:${who.ns}:kind:${kind}`,\n `${who.fn}: ${who.grid} cells for \"${kind}\" columns are not mapped yet; they show the plain value`,\n );\n}\n\n/**\n * Every row's id, checked once: `String(row[rowKey])`, else the row's index in `rows`. First wins for a row object; a\n * repeated id (the same object with no rowKey, or two rows with one rowKey value) or a blank rowKey value throws, since\n * the grid would drop a row. The returned function also ids a row the grid was not built from, by its rowKey.\n */\nexport function rowIds<Row>(\n rows: readonly Row[],\n key: (keyof Row & string) | undefined,\n who: GridName,\n): (row: Row) => string {\n const keyOf = (row: Row, i: number | string): string => {\n const v = value(row, key as string);\n if (isBlank(v))\n throw new InputError(\n `${who.fn}: row ${i} has no value for rowKey \"${String(key)}\" (${String(v)}); ${who.grid} needs an id for every row`,\n );\n return String(v);\n };\n const index = new Map<Row, string>();\n const seen = new Map<string, number>();\n rows.forEach((r, i) => {\n const id = key === undefined ? String(index.has(r) ? index.get(r) : i) : keyOf(r, i);\n const j = seen.get(id);\n if (j !== undefined)\n throw new InputError(\n key === undefined\n ? `${who.fn}: rows ${j} and ${i} are the same object, so ${who.grid} would give them one id and drop one; copy the row or set the spec's rowKey`\n : `${who.fn}: rows ${j} and ${i} have the same rowKey \"${String(key)}\" value \"${id}\"; ${who.grid} needs a unique id per row`,\n );\n seen.set(id, i);\n if (!index.has(r)) index.set(r, id);\n });\n return (row) => {\n const known = index.get(row);\n if (known !== undefined) return known;\n if (key !== undefined) return keyOf(row, \"(not in rows)\");\n throw new TableSpecError(\n `${who.fn}: a row the ${who.built} were not built from has no id; set the spec's rowKey, or build the ${who.built} from the rows you pass the grid`,\n );\n };\n}\n"]}