@sportsdataverse/sdvtables 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 +4 -0
- package/README.md +561 -2
- package/dist/chunk-4VKYHOHL.js +2081 -0
- package/dist/chunk-4VKYHOHL.js.map +1 -0
- package/dist/chunk-U6W6MWKW.js +179 -0
- package/dist/chunk-U6W6MWKW.js.map +1 -0
- package/dist/chunk-UCRLJGDL.js +1628 -0
- package/dist/chunk-UCRLJGDL.js.map +1 -0
- package/dist/engine-CkfxYXAL.d.ts +190 -0
- package/dist/export.d.ts +88 -0
- package/dist/export.js +309 -0
- package/dist/export.js.map +1 -0
- package/dist/html.d.ts +330 -0
- package/dist/html.js +5 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +72 -0
- package/dist/react.js +183 -0
- package/dist/react.js.map +1 -0
- package/dist/spec-BEhLDtj_.d.ts +430 -0
- package/dist/tokens-CpYFsFtf.d.ts +75 -0
- package/package.json +86 -3
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { applyHover, loadTeamNames, renderInteractive, assemble, renderParts, handleClick, handleInput, handleHover, handleKeydown, captureFocus, tableRenderOptions, tableHTML, renderToolbar, pagerLabel } from './chunk-UCRLJGDL.js';
|
|
2
|
+
import { TableSpecError, THEME_NAMES } from './chunk-4VKYHOHL.js';
|
|
3
|
+
import { loadLeague, loadGsis } from '@sportsdataverse/sdvplot';
|
|
4
|
+
|
|
5
|
+
// src/html/hydrate.ts
|
|
6
|
+
var live = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
var behind = /* @__PURE__ */ new WeakSet();
|
|
8
|
+
var edge = (button, atEdge) => {
|
|
9
|
+
if (atEdge) button?.setAttribute("aria-disabled", "true");
|
|
10
|
+
else button?.removeAttribute("aria-disabled");
|
|
11
|
+
};
|
|
12
|
+
function hydrate(el, table) {
|
|
13
|
+
const body = el.querySelector("[data-sdv-body]");
|
|
14
|
+
if (!body)
|
|
15
|
+
throw new TableSpecError(
|
|
16
|
+
"hydrate: no [data-sdv-body] in element; render it with renderHTML(table) first"
|
|
17
|
+
);
|
|
18
|
+
live.get(el)?.();
|
|
19
|
+
let hidden = table.state.hidden;
|
|
20
|
+
let drawn = behind.has(body) ? null : table.rows;
|
|
21
|
+
const render = () => {
|
|
22
|
+
cancel = void 0;
|
|
23
|
+
const root = el.getRootNode();
|
|
24
|
+
const restore = captureFocus(el);
|
|
25
|
+
const p = renderParts(table.spec, table.rows, { css: "none", ...tableRenderOptions(table) });
|
|
26
|
+
body.innerHTML = tableHTML(p);
|
|
27
|
+
drawn = table.rows;
|
|
28
|
+
const toolbar = el.querySelector(".sdvt-toolbar");
|
|
29
|
+
if (toolbar && table.state.hidden !== hidden) toolbar.outerHTML = renderToolbar(table, p.labels);
|
|
30
|
+
hidden = table.state.hidden;
|
|
31
|
+
const label = el.querySelector("[data-sdv-page-label]");
|
|
32
|
+
const page = pagerLabel(table);
|
|
33
|
+
if (label && label.textContent !== page) label.textContent = page;
|
|
34
|
+
edge(el.querySelector('[data-sdv-page="prev"]'), table.state.page === 0);
|
|
35
|
+
edge(el.querySelector('[data-sdv-page="next"]'), table.state.page >= table.pageCount - 1);
|
|
36
|
+
for (const input of Array.from(
|
|
37
|
+
el.querySelectorAll("[data-sdv-filter],[data-sdv-global-filter]")
|
|
38
|
+
)) {
|
|
39
|
+
if (input === root.activeElement) continue;
|
|
40
|
+
const key = input.getAttribute("data-sdv-filter");
|
|
41
|
+
const f = key === null ? table.state.globalFilter : table.state.filters[key];
|
|
42
|
+
const want = typeof f === "string" ? f : "";
|
|
43
|
+
if (input.value !== want) input.value = want;
|
|
44
|
+
}
|
|
45
|
+
applyHover(el, table, table.getHover());
|
|
46
|
+
restore();
|
|
47
|
+
};
|
|
48
|
+
let cancel;
|
|
49
|
+
const schedule = () => {
|
|
50
|
+
if (cancel) return;
|
|
51
|
+
if (typeof requestAnimationFrame === "function") {
|
|
52
|
+
const id = requestAnimationFrame(render);
|
|
53
|
+
cancel = () => cancelAnimationFrame(id);
|
|
54
|
+
} else {
|
|
55
|
+
const id = setTimeout(render, 0);
|
|
56
|
+
cancel = () => clearTimeout(id);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const same = (r, i) => {
|
|
60
|
+
const was = drawn?.[i];
|
|
61
|
+
return r === was || table.rowId(r) === table.rowId(was);
|
|
62
|
+
};
|
|
63
|
+
const flush = () => {
|
|
64
|
+
const rows = table.rows;
|
|
65
|
+
if (!cancel || rows.length === drawn?.length && rows.every(same)) return false;
|
|
66
|
+
cancel();
|
|
67
|
+
render();
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
70
|
+
const onClick = (e) => {
|
|
71
|
+
flush();
|
|
72
|
+
handleClick(table, e.target);
|
|
73
|
+
};
|
|
74
|
+
const onInput = (e) => handleInput(table, e.target);
|
|
75
|
+
const onHover = (e) => {
|
|
76
|
+
flush();
|
|
77
|
+
handleHover(table, e.target);
|
|
78
|
+
};
|
|
79
|
+
const onKeydown = (e) => {
|
|
80
|
+
const k = e;
|
|
81
|
+
if (flush() && (k.key === " " || k.key === "Enter") && k.target.matches(".sdvt-row[data-row]"))
|
|
82
|
+
k.preventDefault();
|
|
83
|
+
handleKeydown(table, el, k);
|
|
84
|
+
};
|
|
85
|
+
el.addEventListener("click", onClick);
|
|
86
|
+
el.addEventListener("input", onInput);
|
|
87
|
+
el.addEventListener("mouseover", onHover);
|
|
88
|
+
el.addEventListener("mouseleave", onHover);
|
|
89
|
+
el.addEventListener("keydown", onKeydown);
|
|
90
|
+
const unsubscribe = table.subscribe((e) => {
|
|
91
|
+
if (e.type !== "hover") schedule();
|
|
92
|
+
else if (cancel === void 0) applyHover(el, table, e.id);
|
|
93
|
+
});
|
|
94
|
+
applyHover(el, table, table.getHover());
|
|
95
|
+
if (behind.delete(body)) schedule();
|
|
96
|
+
const teardown = () => {
|
|
97
|
+
if (live.get(el) === replace) live.delete(el);
|
|
98
|
+
unsubscribe();
|
|
99
|
+
if (cancel) {
|
|
100
|
+
cancel();
|
|
101
|
+
behind.add(body);
|
|
102
|
+
}
|
|
103
|
+
cancel = void 0;
|
|
104
|
+
el.removeEventListener("click", onClick);
|
|
105
|
+
el.removeEventListener("input", onInput);
|
|
106
|
+
el.removeEventListener("mouseover", onHover);
|
|
107
|
+
el.removeEventListener("mouseleave", onHover);
|
|
108
|
+
el.removeEventListener("keydown", onKeydown);
|
|
109
|
+
};
|
|
110
|
+
const replace = () => {
|
|
111
|
+
if (cancel) {
|
|
112
|
+
cancel();
|
|
113
|
+
render();
|
|
114
|
+
}
|
|
115
|
+
teardown();
|
|
116
|
+
};
|
|
117
|
+
live.set(el, replace);
|
|
118
|
+
return teardown;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/html/index.ts
|
|
122
|
+
function leaguesOf(spec) {
|
|
123
|
+
const out = /* @__PURE__ */ new Set();
|
|
124
|
+
for (const c of spec.columns) if ("league" in c && c.kind !== "headshot") out.add(c.league);
|
|
125
|
+
if (spec.theme.name === "sdvTeam" && spec.theme.options?.league)
|
|
126
|
+
out.add(spec.theme.options.league);
|
|
127
|
+
return [...out];
|
|
128
|
+
}
|
|
129
|
+
async function prepare(spec) {
|
|
130
|
+
const gsis = spec.columns.some((c) => c.kind === "headshot" && c.idSystem === "gsis");
|
|
131
|
+
await Promise.all([...leaguesOf(spec).map((l) => loadLeague(l)), ...gsis ? [loadGsis()] : []]);
|
|
132
|
+
await loadTeamNames(leaguesOf(spec));
|
|
133
|
+
}
|
|
134
|
+
function isTable(x) {
|
|
135
|
+
return typeof x.subscribe === "function";
|
|
136
|
+
}
|
|
137
|
+
function renderHTML(a, b, c = {}) {
|
|
138
|
+
if (isTable(a)) return renderInteractive(a, b ?? {});
|
|
139
|
+
return assemble(renderParts(a, b ?? [], c));
|
|
140
|
+
}
|
|
141
|
+
async function renderHTMLAsync(spec, rows, opts = {}) {
|
|
142
|
+
await prepare(spec);
|
|
143
|
+
return renderHTML(spec, rows, opts);
|
|
144
|
+
}
|
|
145
|
+
function toElement(spec, rows, opts = {}) {
|
|
146
|
+
if (typeof document === "undefined")
|
|
147
|
+
throw new TableSpecError("toElement needs a DOM (browser, happy-dom or jsdom); use renderHTML in Node");
|
|
148
|
+
const t = document.createElement("template");
|
|
149
|
+
t.innerHTML = renderHTML(spec, rows, opts).trim();
|
|
150
|
+
const el = t.content.querySelector("div.sdvt");
|
|
151
|
+
if (!(el instanceof HTMLElement)) throw new TableSpecError("renderHTML produced no wrapper");
|
|
152
|
+
const link = t.content.querySelector("link");
|
|
153
|
+
const href = link?.getAttribute("href");
|
|
154
|
+
if (link && href && !Array.from(document.head.querySelectorAll("link")).some((l) => l.getAttribute("href") === href))
|
|
155
|
+
document.head.append(link);
|
|
156
|
+
return el;
|
|
157
|
+
}
|
|
158
|
+
function themePreview(spec, rows, themes, o = {}) {
|
|
159
|
+
const n = o.n ?? 5;
|
|
160
|
+
if (!Number.isInteger(n) || n < 1) throw new TableSpecError(`n must be a positive whole number, got ${n}`);
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const name of themes ?? THEME_NAMES)
|
|
163
|
+
out[name] = renderHTML(
|
|
164
|
+
{
|
|
165
|
+
...spec,
|
|
166
|
+
theme: {
|
|
167
|
+
name,
|
|
168
|
+
density: o.density ?? "compact",
|
|
169
|
+
...name === "sdvTeam" ? { options: { league: "nfl" } } : {}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
rows.slice(0, n)
|
|
173
|
+
);
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { hydrate, leaguesOf, prepare, renderHTML, renderHTMLAsync, themePreview, toElement };
|
|
178
|
+
//# sourceMappingURL=chunk-U6W6MWKW.js.map
|
|
179
|
+
//# sourceMappingURL=chunk-U6W6MWKW.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-U6W6MWKW.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 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) if (\"league\" in c && c.kind !== \"headshot\") 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"]}
|