@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/dist/index.js ADDED
@@ -0,0 +1,93 @@
1
+ export { DENSITY, NUMERIC_KINDS, RANK_PALETTE, THEMES, THEME_NAMES, TOKEN_KEYS, TableBuilder, TableSpecError, applyFilters, applySort, columnFactory, comparatorFor, createTable, defineTable, findColumn, isMissing, matches, nextSortDir, paginate, resolveTheme, secondaryOn, selectRows, snakeAlign, withMissingLast } from './chunk-4VKYHOHL.js';
2
+
3
+ // src/aliases.ts
4
+ var T = (target, note) => note ? { target, status: "ported", note } : { target, status: "ported" };
5
+ var GT_ALIASES = {
6
+ gt_sdv_logos: T("c.logo"),
7
+ gt_sdv_wordmarks: T("c.wordmark"),
8
+ gt_sdv_headshots: T("c.headshot"),
9
+ gt_sdv_cols_label: {
10
+ target: "labelHtml",
11
+ status: "not-ported",
12
+ note: "labels are text in Phase 4; use .titleHeader() or subheader"
13
+ },
14
+ gt_merge_stack_team_color: T("c.mergeStackTeamColor"),
15
+ gt_theme_sdv: T("theme.sdv"),
16
+ gt_theme_sdv_team: T("theme.sdvTeam"),
17
+ gt_theme_almanac: T("theme.almanac"),
18
+ gt_theme_athletic: T("theme.athletic"),
19
+ gt_theme_booktabs: T("theme.booktabs"),
20
+ gt_theme_broadsheet: T("theme.broadsheet"),
21
+ gt_theme_brutalist: T("theme.brutalist"),
22
+ gt_theme_drench: T("theme.drench"),
23
+ gt_theme_gtutils: T("theme.gtutils"),
24
+ gt_theme_kenpom: T("theme.kenpom"),
25
+ gt_theme_midnight: T("theme.midnight"),
26
+ gt_theme_ncaa: T("theme.ncaa"),
27
+ gt_theme_pl: T("theme.pl"),
28
+ gt_theme_savant: T("theme.savant"),
29
+ gt_theme_scoreboard: T("theme.scoreboard"),
30
+ gt_theme_sofa: T("theme.sofa"),
31
+ gt_theme_swiss: T("theme.swiss"),
32
+ gt_theme_terminal: T("theme.terminal"),
33
+ gt_theme_tier: T("theme.tier"),
34
+ gt_theme_tufte: T("theme.tufte"),
35
+ gt_theme_preview: T("html.themePreview", "a Record<name, html>, not a grid"),
36
+ pal_midnight: T("PAL_MIDNIGHT"),
37
+ gt_538_caption: T("builder.caption538"),
38
+ gt_bold_rows: T("builder.boldRows"),
39
+ gt_border_bars_top: T("builder.borderBars"),
40
+ gt_border_bars_bottom: T("builder.borderBars"),
41
+ gt_border_grid: T("builder.borderGrid"),
42
+ gt_color_pills: T("c.colorPills"),
43
+ gt_color_ranks: T("c.colorRanks"),
44
+ gt_color_results: T("c.colorResults", "a column kind (spec \xA76.1) that fills its row"),
45
+ gt_column_subheaders: T("builder.columns", "the per-column subheader option of every c.* kind"),
46
+ gt_cutline: T("builder.cutline"),
47
+ gt_delta: T("c.delta"),
48
+ gt_fmt_rank: T("c.rank"),
49
+ gt_fmt_tally: T("c.tally"),
50
+ gt_group_stripes: T("builder.groupStripes"),
51
+ gt_highlight_cells: T("c.highlight", "data predicate, not a formula"),
52
+ gt_highlight_na: T("c.highlightNa"),
53
+ gt_indicator_boxes: T("c.indicatorBox", "value list, not a rule"),
54
+ gt_legend_continuous: T("builder.legendContinuous"),
55
+ gt_legend_discrete: T("builder.legendDiscrete"),
56
+ gt_color_legend: T("builder.legendContinuous", "deprecated gtUtils name (sdvplotR deprecated.R:20)"),
57
+ gt_centered_legend: T("builder.legendDiscrete", "deprecated gtUtils name (sdvplotR deprecated.R:27)"),
58
+ gt_marginalia: T("builder.marginalia"),
59
+ gt_outliers: T("builder.outliers"),
60
+ gt_percentile_bar: T("c.percentileBar"),
61
+ gt_row_accent: T("builder.rowAccent"),
62
+ gt_scale_note: T("builder.scaleNote"),
63
+ gt_set_font: T("builder.font"),
64
+ gt_significance: T("builder.significance"),
65
+ gt_snake: T("builder.snake"),
66
+ gt_snake_align: T("snakeAlign"),
67
+ gt_social_tag: T("builder.socialTag"),
68
+ gt_spotlight: T("builder.spotlight"),
69
+ gt_tiers: T("builder.tiers"),
70
+ gt_title_header: T("builder.titleHeader"),
71
+ gt_watermark: T("builder.watermark"),
72
+ gt_wrap_labels: T("builder.wrapLabels"),
73
+ gt_save_crop: { target: "tableToPNG", status: "phase-5" },
74
+ gt_save_batch: { target: "tableToPNG", status: "phase-5" },
75
+ gt_social_crop: { target: "socialCrop", status: "phase-5" },
76
+ gt_grid: { target: "gridTables", status: "phase-5" },
77
+ gt_stack_tables: { target: "stackTables", status: "phase-5" },
78
+ reactable_sdv_logos: T("c.logo"),
79
+ reactable_sdv_wordmarks: T("c.wordmark"),
80
+ reactable_sdv_headshots: T("c.headshot"),
81
+ reactable_sdv_cols_label: { target: "labelHtml", status: "not-ported", note: "as gt_sdv_cols_label" },
82
+ reactable_sdv_team_color_bar: T("c.teamColorBar"),
83
+ reactable_sdv_team_color_bg: T("c.teamColorBg")
84
+ };
85
+ var aliasFor = (name) => GT_ALIASES[name];
86
+ var PAL_MIDNIGHT = ["#5B8DEF", "#3FBF87", "#E8E9ED", "#9498A3", "#24272E"];
87
+
88
+ // src/index.ts
89
+ var VERSION = "0.1.0";
90
+
91
+ export { GT_ALIASES, PAL_MIDNIGHT, VERSION, aliasFor };
92
+ //# sourceMappingURL=index.js.map
93
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/aliases.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// src/aliases.ts — every sdvplotR gt_* / pal_* / reactable_sdv_* export (74, sdvplotR NAMESPACE) → its sdvtables target.\n// Target forms: \"c.<kind>\" (column factory), \"builder.<method>\" (TableBuilder), \"theme.<name>\" (THEME_NAMES),\n// \"html.<export>\" (the ./html subpath), a bare root export (\"snakeAlign\", \"PAL_MIDNIGHT\"), or a Phase 5 name.\n/** One sdvplotR/gtUtils name and where it lives in sdvtables. */\nexport interface Alias {\n readonly target: string;\n readonly status: \"ported\" | \"phase-5\" | \"not-ported\";\n readonly note?: string;\n}\nconst T = (target: string, note?: string): Alias =>\n note ? { target, status: \"ported\", note } : { target, status: \"ported\" };\n/** sdvplotR/gtUtils function name → sdvtables equivalent. */\nexport const GT_ALIASES: Readonly<Record<string, Alias>> = {\n gt_sdv_logos: T(\"c.logo\"),\n gt_sdv_wordmarks: T(\"c.wordmark\"),\n gt_sdv_headshots: T(\"c.headshot\"),\n gt_sdv_cols_label: {\n target: \"labelHtml\",\n status: \"not-ported\",\n note: \"labels are text in Phase 4; use .titleHeader() or subheader\",\n },\n gt_merge_stack_team_color: T(\"c.mergeStackTeamColor\"),\n gt_theme_sdv: T(\"theme.sdv\"),\n gt_theme_sdv_team: T(\"theme.sdvTeam\"),\n gt_theme_almanac: T(\"theme.almanac\"),\n gt_theme_athletic: T(\"theme.athletic\"),\n gt_theme_booktabs: T(\"theme.booktabs\"),\n gt_theme_broadsheet: T(\"theme.broadsheet\"),\n gt_theme_brutalist: T(\"theme.brutalist\"),\n gt_theme_drench: T(\"theme.drench\"),\n gt_theme_gtutils: T(\"theme.gtutils\"),\n gt_theme_kenpom: T(\"theme.kenpom\"),\n gt_theme_midnight: T(\"theme.midnight\"),\n gt_theme_ncaa: T(\"theme.ncaa\"),\n gt_theme_pl: T(\"theme.pl\"),\n gt_theme_savant: T(\"theme.savant\"),\n gt_theme_scoreboard: T(\"theme.scoreboard\"),\n gt_theme_sofa: T(\"theme.sofa\"),\n gt_theme_swiss: T(\"theme.swiss\"),\n gt_theme_terminal: T(\"theme.terminal\"),\n gt_theme_tier: T(\"theme.tier\"),\n gt_theme_tufte: T(\"theme.tufte\"),\n gt_theme_preview: T(\"html.themePreview\", \"a Record<name, html>, not a grid\"),\n pal_midnight: T(\"PAL_MIDNIGHT\"),\n gt_538_caption: T(\"builder.caption538\"),\n gt_bold_rows: T(\"builder.boldRows\"),\n gt_border_bars_top: T(\"builder.borderBars\"),\n gt_border_bars_bottom: T(\"builder.borderBars\"),\n gt_border_grid: T(\"builder.borderGrid\"),\n gt_color_pills: T(\"c.colorPills\"),\n gt_color_ranks: T(\"c.colorRanks\"),\n gt_color_results: T(\"c.colorResults\", \"a column kind (spec §6.1) that fills its row\"),\n gt_column_subheaders: T(\"builder.columns\", \"the per-column subheader option of every c.* kind\"),\n gt_cutline: T(\"builder.cutline\"),\n gt_delta: T(\"c.delta\"),\n gt_fmt_rank: T(\"c.rank\"),\n gt_fmt_tally: T(\"c.tally\"),\n gt_group_stripes: T(\"builder.groupStripes\"),\n gt_highlight_cells: T(\"c.highlight\", \"data predicate, not a formula\"),\n gt_highlight_na: T(\"c.highlightNa\"),\n gt_indicator_boxes: T(\"c.indicatorBox\", \"value list, not a rule\"),\n gt_legend_continuous: T(\"builder.legendContinuous\"),\n gt_legend_discrete: T(\"builder.legendDiscrete\"),\n gt_color_legend: T(\"builder.legendContinuous\", \"deprecated gtUtils name (sdvplotR deprecated.R:20)\"),\n gt_centered_legend: T(\"builder.legendDiscrete\", \"deprecated gtUtils name (sdvplotR deprecated.R:27)\"),\n gt_marginalia: T(\"builder.marginalia\"),\n gt_outliers: T(\"builder.outliers\"),\n gt_percentile_bar: T(\"c.percentileBar\"),\n gt_row_accent: T(\"builder.rowAccent\"),\n gt_scale_note: T(\"builder.scaleNote\"),\n gt_set_font: T(\"builder.font\"),\n gt_significance: T(\"builder.significance\"),\n gt_snake: T(\"builder.snake\"),\n gt_snake_align: T(\"snakeAlign\"),\n gt_social_tag: T(\"builder.socialTag\"),\n gt_spotlight: T(\"builder.spotlight\"),\n gt_tiers: T(\"builder.tiers\"),\n gt_title_header: T(\"builder.titleHeader\"),\n gt_watermark: T(\"builder.watermark\"),\n gt_wrap_labels: T(\"builder.wrapLabels\"),\n gt_save_crop: { target: \"tableToPNG\", status: \"phase-5\" },\n gt_save_batch: { target: \"tableToPNG\", status: \"phase-5\" },\n gt_social_crop: { target: \"socialCrop\", status: \"phase-5\" },\n gt_grid: { target: \"gridTables\", status: \"phase-5\" },\n gt_stack_tables: { target: \"stackTables\", status: \"phase-5\" },\n reactable_sdv_logos: T(\"c.logo\"),\n reactable_sdv_wordmarks: T(\"c.wordmark\"),\n reactable_sdv_headshots: T(\"c.headshot\"),\n reactable_sdv_cols_label: { target: \"labelHtml\", status: \"not-ported\", note: \"as gt_sdv_cols_label\" },\n reactable_sdv_team_color_bar: T(\"c.teamColorBar\"),\n reactable_sdv_team_color_bg: T(\"c.teamColorBg\"),\n};\n/** Look up the sdvtables equivalent of an sdvplotR/gtUtils function name. */\nexport const aliasFor = (name: string): Alias | undefined => GT_ALIASES[name];\n/** The five-colour midnight palette (same values as sdvplotR `pal_midnight()`). */\nexport const PAL_MIDNIGHT: readonly string[] = [\"#5B8DEF\", \"#3FBF87\", \"#E8E9ED\", \"#9498A3\", \"#24272E\"];\n","/** The package version, injected from package.json at build time. */\nexport const VERSION: string = __SDVTABLES_VERSION__;\n// The renderer, themePreview, styleSheet and prepare live under ./html.\nexport type * from \"./spec.js\";\nexport { TableSpecError } from \"./errors.js\";\nexport { type ColumnFactory, RANK_PALETTE, TableBuilder, columnFactory, defineTable } from \"./define.js\";\nexport { matches, selectRows } from \"./predicate.js\";\nexport { snakeAlign } from \"./snake.js\";\nexport { THEMES, THEME_NAMES, resolveTheme } from \"./themes/index.js\";\nexport type { GoogleFont, Theme, ThemeDef, ThemeTokens } from \"./themes/index.js\";\nexport { DENSITY, TOKEN_KEYS } from \"./themes/tokens.js\";\nexport { secondaryOn } from \"./themes/sdv.js\";\nexport { type Alias, GT_ALIASES, PAL_MIDNIGHT, aliasFor } from \"./aliases.js\";\nexport {\n NUMERIC_KINDS,\n applyFilters,\n applySort,\n comparatorFor,\n createTable,\n findColumn,\n isMissing,\n nextSortDir,\n paginate,\n withMissingLast,\n} from \"./engine.js\";\nexport type {\n Comparator,\n FilterValue,\n RowFilter,\n Sort,\n SortDir,\n Table,\n TableCursor,\n TableEvent,\n TableOptions,\n TableSnapshot,\n TableState,\n ValuePredicate,\n} from \"./engine.js\";\n"]}
@@ -0,0 +1,72 @@
1
+ import { ReactElement } from 'react';
2
+ import { S as Sort, a as Table, e as TableOptions, f as TableSnapshot } from './engine-CkfxYXAL.js';
3
+ import { T as TableSpec } from './spec-BEhLDtj_.js';
4
+ import '@sportsdataverse/sdvplot';
5
+
6
+ /**
7
+ * Subscribe a component to a `createTable` engine via useSyncExternalStore. The engine is created once per component:
8
+ * a new `rows` array is handed to `table.setRows` (sort, filters, page and selection kept), and only a structural
9
+ * `spec` change rebuilds it, so neither needs to be memoized. `options` are INITIAL state: a later change is ignored.
10
+ */
11
+ declare function useTable<Row>(spec: TableSpec<Row>, rows: readonly Row[], options?: TableOptions): {
12
+ table: Table<Row>;
13
+ snapshot: TableSnapshot<Row>;
14
+ };
15
+ /** `<SdvTable/>` building and owning its engine from `spec` and `rows`. */
16
+ interface SdvTableOwnProps<Row> {
17
+ /** the table to build; a later change with the same JSON (functions dropped) keeps the engine and its state */
18
+ spec: TableSpec<Row>;
19
+ /** the source rows; a new array (an equal copy included) keeps the user's sort, filters, page and selection */
20
+ rows: readonly Row[];
21
+ /** toolbar filters, sort headers and a pager driven by the engine; without it no engine is built */
22
+ interactive?: boolean;
23
+ /** INITIAL page size (beats `spec.interactive.pageSize`); like a React `default*` prop, a later change is ignored */
24
+ pageSize?: number;
25
+ /** INITIAL sort; like a React `default*` prop, a later change is ignored */
26
+ sort?: Sort;
27
+ /** `"none"` leaves the theme stylesheet out */
28
+ css?: "inline" | "none";
29
+ /** absent: pass a `table` to render an engine you own (`SdvTableLinkedProps`) */
30
+ table?: undefined;
31
+ }
32
+ /**
33
+ * J31 (A8): `<SdvTable/>` rendering an engine you own (from `createTable` or `useTable`, linked with
34
+ * `linkSelection`). Its own spec and rows are rendered; `spec` and `rows` passed beside it are accepted and IGNORED
35
+ * (change the rows with `table.setRows`). Initial state (`pageSize`, `sort`) belongs to that engine, so it is rejected.
36
+ */
37
+ interface SdvTableLinkedProps<Row> {
38
+ /** the engine to render; the component re-renders on its changes */
39
+ table: Table<Row>;
40
+ /** ignored: the engine's own spec is rendered */
41
+ spec?: TableSpec<Row>;
42
+ /** ignored: the engine's own rows are rendered */
43
+ rows?: readonly Row[];
44
+ /** toolbar filters, sort headers and a pager; without it, the engine's current page as a static table */
45
+ interactive?: boolean;
46
+ /** `"none"` leaves the theme stylesheet out */
47
+ css?: "inline" | "none";
48
+ /** rejected: set it on the engine (`createTable(spec, rows, { pageSize })`) */
49
+ pageSize?: undefined;
50
+ /** rejected: set it on the engine (`createTable(spec, rows, { sort })`) */
51
+ sort?: undefined;
52
+ }
53
+ /** Either an engine to render (`{ table }`) or the data to build one from (`{ spec, rows }`). */
54
+ type SdvTableProps<Row> = SdvTableOwnProps<Row> | SdvTableLinkedProps<Row>;
55
+ /**
56
+ * Same markup as `renderHTML` (test-enforced). Two forms: `{ spec, rows }` builds an engine (only when `interactive`;
57
+ * a static table needs none) that lives as long as the component; `{ table }` renders an engine you own, statically
58
+ * (its current page: filtered, sorted, sliced) or with controls. The table block is the string renderer's output;
59
+ * React owns only the toolbar inputs and the pager, so typing in a filter never re-creates the input. The inputs are
60
+ * controlled by the engine state, so a filter set from outside (another component, a linked selection) shows in its
61
+ * box. A control or focusable cell element that a re-render replaces gets focus back, as in `hydrate`. The
62
+ * Google-Fonts link is NOT rendered: put `fontsLinkFor(spec)` in the page head.
63
+ *
64
+ * Two cases remount the owned engine (`{ spec, rows }` form only; an engine passed as `table` is yours and is kept):
65
+ * toggling `interactive` remounts it, losing the user's sort, filters, page and selection, since a static render
66
+ * builds no engine. And the engine is keyed by the spec's JSON, which drops functions, so a deliberately changed
67
+ * formatter or comparator under an otherwise equal spec does NOT take effect: change the element's React `key` to
68
+ * rebuild the engine from the new spec.
69
+ */
70
+ declare function SdvTable<Row>(props: SdvTableProps<Row>): ReactElement;
71
+
72
+ export { SdvTable, type SdvTableLinkedProps, type SdvTableOwnProps, type SdvTableProps, useTable };
package/dist/react.js ADDED
@@ -0,0 +1,183 @@
1
+ "use client";
2
+ import { SR_ONLY, renderParts, tableHTML, applyHover, captureFocus, tableRenderOptions, pagerLabel, handleKeydown, handleHover, handleClick, filterInputs, handleInput } from './chunk-UCRLJGDL.js';
3
+ import { createTable } from './chunk-4VKYHOHL.js';
4
+ import { useSyncExternalStore, createElement, useRef, useCallback, useState, useEffect, useLayoutEffect, Fragment } from 'react';
5
+
6
+ function useOwnedTable(spec, rows, options) {
7
+ const key = JSON.stringify(spec);
8
+ const [held, setHeld] = useState(() => ({ key, table: createTable(spec, rows, options) }));
9
+ let table = held.table;
10
+ if (held.key !== key) {
11
+ table = createTable(spec, rows, options);
12
+ setHeld({ key, table });
13
+ }
14
+ useIsoLayoutEffect(() => {
15
+ if (table.allRows !== rows) table.setRows(rows);
16
+ }, [table, rows]);
17
+ return table;
18
+ }
19
+ function useTable(spec, rows, options = {}) {
20
+ const table = useOwnedTable(spec, rows, options);
21
+ const snapshot = useSyncExternalStore(table.subscribe, table.getSnapshot, table.getSnapshot);
22
+ return { table, snapshot };
23
+ }
24
+ function wrapperProps(p) {
25
+ return {
26
+ className: p.wrapperAttrs.class ?? "",
27
+ id: p.wrapperAttrs.id ?? "",
28
+ "data-sdvt-theme": p.wrapperAttrs["data-sdvt-theme"] ?? "",
29
+ "data-sdvt-density": p.wrapperAttrs["data-sdvt-density"] ?? ""
30
+ };
31
+ }
32
+ var raw = (html) => ({ __html: html });
33
+ var SR_STYLE = Object.fromEntries(
34
+ SR_ONLY.split(";").map((d) => {
35
+ const [k = "", v = ""] = d.split(":");
36
+ return [k.replace(/-(\w)/g, (_, ch) => ch.toUpperCase()), v];
37
+ })
38
+ );
39
+ var useIsoLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
40
+ function searchBox(key, id, label, cls, data, value, onChange, placeholder = label) {
41
+ return createElement(
42
+ Fragment,
43
+ { key },
44
+ createElement("label", { htmlFor: id, style: SR_STYLE }, label),
45
+ createElement("input", { type: "search", id, className: cls, ...data, placeholder, value, onChange })
46
+ );
47
+ }
48
+ function SdvTable(props) {
49
+ const { interactive = false, css } = props;
50
+ if (props.table !== void 0)
51
+ return createElement(TableView, { table: props.table, interactive, css });
52
+ if (interactive) return createElement(OwnedTable, props);
53
+ const p = renderParts(props.spec, props.rows, { fonts: false, ...css !== void 0 && { css } });
54
+ return staticBlock(p);
55
+ }
56
+ function staticBlock(p) {
57
+ const inner = `${p.sheet ? `<style>${p.sheet}</style>` : ""}${tableHTML(p)}`;
58
+ return createElement("div", { ...wrapperProps(p), dangerouslySetInnerHTML: raw(inner) });
59
+ }
60
+ function OwnedTable({ spec, rows, pageSize, sort, css }) {
61
+ const table = useOwnedTable(spec, rows, {
62
+ ...pageSize !== void 0 && { pageSize },
63
+ ...sort !== void 0 && { sort }
64
+ });
65
+ return createElement(TableView, { table, interactive: true, css });
66
+ }
67
+ function TableView({
68
+ table,
69
+ interactive,
70
+ css
71
+ }) {
72
+ const ref = useRef(null);
73
+ const restore = useRef(null);
74
+ const subscribe = useCallback(
75
+ (onChange) => table.subscribe((e) => {
76
+ if (e.type === "hover") {
77
+ if (ref.current) applyHover(ref.current, table, e.id);
78
+ } else if (restore.current === null && ref.current) restore.current = captureFocus(ref.current);
79
+ onChange();
80
+ }),
81
+ [table]
82
+ );
83
+ useSyncExternalStore(subscribe, table.getSnapshot, table.getSnapshot);
84
+ useIsoLayoutEffect(() => {
85
+ if (ref.current) applyHover(ref.current, table, table.getHover());
86
+ restore.current?.();
87
+ restore.current = null;
88
+ });
89
+ const base = { fonts: false, ...css !== void 0 && { css } };
90
+ if (!interactive)
91
+ return staticBlock(
92
+ renderParts(table.spec, table.rows, { ...base, ...tableRenderOptions(table), interactive: false })
93
+ );
94
+ const p = renderParts(table.spec, table.rows, { ...base, ...tableRenderOptions(table) });
95
+ const onInput = (e) => handleInput(table, e.target);
96
+ const atStart = table.state.page === 0;
97
+ const atEnd = table.state.page >= table.pageCount - 1;
98
+ const pager = table.state.pageSize === Number.POSITIVE_INFINITY ? null : createElement(
99
+ "nav",
100
+ { className: "sdvt-pager", "data-sdv-pager": "", "aria-label": "Pagination" },
101
+ createElement(
102
+ "button",
103
+ {
104
+ type: "button",
105
+ className: "sdvt-page",
106
+ "data-sdv-page": "prev",
107
+ "aria-label": "Previous page",
108
+ "aria-disabled": atStart ? "true" : void 0
109
+ },
110
+ "\u2039"
111
+ ),
112
+ createElement(
113
+ "span",
114
+ { className: "sdvt-page-label", "data-sdv-page-label": "", "aria-live": "polite" },
115
+ pagerLabel(table)
116
+ ),
117
+ createElement(
118
+ "button",
119
+ {
120
+ type: "button",
121
+ className: "sdvt-page",
122
+ "data-sdv-page": "next",
123
+ "aria-label": "Next page",
124
+ "aria-disabled": atEnd ? "true" : void 0
125
+ },
126
+ "\u203A"
127
+ )
128
+ );
129
+ return createElement(
130
+ "div",
131
+ {
132
+ ...wrapperProps(p),
133
+ ref,
134
+ onClick: (e) => handleClick(table, e.target),
135
+ onMouseOver: (e) => handleHover(table, e.target),
136
+ // J31 (A8): no attribute, so SSR is unchanged
137
+ onMouseLeave: (e) => handleHover(table, e.target),
138
+ // Task 10: hotkeys and the grid's keys, as in hydrate; an event prop writes no attribute
139
+ onKeyDown: (e) => {
140
+ if (ref.current) handleKeydown(table, ref.current, e.nativeEvent);
141
+ }
142
+ },
143
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: escaped renderer output (see `raw`)
144
+ p.sheet ? createElement("style", { dangerouslySetInnerHTML: raw(p.sheet) }) : null,
145
+ createElement(
146
+ "div",
147
+ { className: "sdvt-toolbar" },
148
+ searchBox(
149
+ "",
150
+ `${p.id}-search`,
151
+ "Search all columns",
152
+ "sdvt-global-filter",
153
+ { "data-sdv-global-filter": "" },
154
+ table.state.globalFilter,
155
+ onInput,
156
+ "Search"
157
+ ),
158
+ // keyed by column, so hiding a column never hands another column's input (and its focus) over
159
+ filterInputs(table, p.labels).map(
160
+ (f, j) => searchBox(
161
+ f.key,
162
+ `${p.id}-filter-${j}`,
163
+ `Filter ${f.label}`,
164
+ "sdvt-filter",
165
+ { "data-sdv-filter": f.key },
166
+ f.value,
167
+ onInput
168
+ )
169
+ )
170
+ ),
171
+ createElement("div", {
172
+ className: "sdvt-body",
173
+ "data-sdv-body": "",
174
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: escaped renderer output (see `raw`)
175
+ dangerouslySetInnerHTML: raw(tableHTML(p))
176
+ }),
177
+ pager
178
+ );
179
+ }
180
+
181
+ export { SdvTable, useTable };
182
+ //# sourceMappingURL=react.js.map
183
+ //# 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 CSSProperties,\n Fragment,\n type ReactElement,\n type KeyboardEvent as ReactKeyboardEvent,\n type SyntheticEvent,\n createElement,\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { type Sort, type Table, type TableOptions, type TableSnapshot, createTable } from \"../engine.js\";\nimport {\n applyHover,\n captureFocus,\n handleClick,\n handleHover,\n handleInput,\n handleKeydown,\n} from \"../html/controls.js\";\nimport { SR_ONLY, filterInputs, pagerLabel, tableRenderOptions } from \"../html/interactive.js\";\nimport { type RenderOptions, type RenderedParts, renderParts, tableHTML } from \"../html/parts.js\";\nimport type { TableSpec } from \"../spec.js\";\n\n/**\n * The engine behind `useTable` and an owned `<SdvTable/>`, created ONCE per component instance. A new `rows` array\n * (even an equal copy) goes to `table.setRows`, so the user's sort, filters, page and selection survive a parent\n * re-render; only a STRUCTURAL spec change (its JSON, functions dropped) builds a new engine from the current props.\n * Function-valued spec fields (formatters, comparators) are therefore read from the spec the engine was built with.\n */\nfunction useOwnedTable<Row>(spec: TableSpec<Row>, rows: readonly Row[], options: TableOptions): Table<Row> {\n // Not tableId(spec): an explicit spec.id short-circuits it, so a column change under a fixed id would keep a stale engine.\n // ponytail: one JSON.stringify per render, the same work tableId does for an id-less spec.\n const key = JSON.stringify(spec);\n const [held, setHeld] = useState(() => ({ key, table: createTable(spec, rows, options) }));\n let table = held.table;\n if (held.key !== key) {\n table = createTable(spec, rows, options); // React re-runs this render at once with the stored engine\n setHeld({ key, table });\n }\n // after the commit, never during render: setRows notifies subscribers, and React forbids updates from a render\n useIsoLayoutEffect(() => {\n if (table.allRows !== rows) table.setRows(rows);\n }, [table, rows]);\n return table;\n}\n\n/**\n * Subscribe a component to a `createTable` engine via useSyncExternalStore. The engine is created once per component:\n * a new `rows` array is handed to `table.setRows` (sort, filters, page and selection kept), and only a structural\n * `spec` change rebuilds it, so neither needs to be memoized. `options` are INITIAL state: a later change is ignored.\n */\nexport function useTable<Row>(\n spec: TableSpec<Row>,\n rows: readonly Row[],\n options: TableOptions = {},\n): { table: Table<Row>; snapshot: TableSnapshot<Row> } {\n const table = useOwnedTable(spec, rows, options);\n const snapshot = useSyncExternalStore(table.subscribe, table.getSnapshot, table.getSnapshot);\n return { table, snapshot };\n}\n\n/** `<SdvTable/>` building and owning its engine from `spec` and `rows`. */\nexport interface SdvTableOwnProps<Row> {\n /** the table to build; a later change with the same JSON (functions dropped) keeps the engine and its state */\n spec: TableSpec<Row>;\n /** the source rows; a new array (an equal copy included) keeps the user's sort, filters, page and selection */\n rows: readonly Row[];\n /** toolbar filters, sort headers and a pager driven by the engine; without it no engine is built */\n interactive?: boolean;\n /** INITIAL page size (beats `spec.interactive.pageSize`); like a React `default*` prop, a later change is ignored */\n pageSize?: number;\n /** INITIAL sort; like a React `default*` prop, a later change is ignored */\n sort?: Sort;\n /** `\"none\"` leaves the theme stylesheet out */\n css?: \"inline\" | \"none\";\n /** absent: pass a `table` to render an engine you own (`SdvTableLinkedProps`) */\n table?: undefined;\n}\n/**\n * J31 (A8): `<SdvTable/>` rendering an engine you own (from `createTable` or `useTable`, linked with\n * `linkSelection`). Its own spec and rows are rendered; `spec` and `rows` passed beside it are accepted and IGNORED\n * (change the rows with `table.setRows`). Initial state (`pageSize`, `sort`) belongs to that engine, so it is rejected.\n */\nexport interface SdvTableLinkedProps<Row> {\n /** the engine to render; the component re-renders on its changes */\n table: Table<Row>;\n /** ignored: the engine's own spec is rendered */\n spec?: TableSpec<Row>;\n /** ignored: the engine's own rows are rendered */\n rows?: readonly Row[];\n /** toolbar filters, sort headers and a pager; without it, the engine's current page as a static table */\n interactive?: boolean;\n /** `\"none\"` leaves the theme stylesheet out */\n css?: \"inline\" | \"none\";\n /** rejected: set it on the engine (`createTable(spec, rows, { pageSize })`) */\n pageSize?: undefined;\n /** rejected: set it on the engine (`createTable(spec, rows, { sort })`) */\n sort?: undefined;\n}\n/** Either an engine to render (`{ table }`) or the data to build one from (`{ spec, rows }`). */\nexport type SdvTableProps<Row> = SdvTableOwnProps<Row> | SdvTableLinkedProps<Row>;\n\n/** Wrapper attributes in the string renderer's order: class, id, data-sdvt-theme, data-sdvt-density. */\nfunction wrapperProps(p: RenderedParts): Record<string, string> {\n return {\n className: p.wrapperAttrs.class ?? \"\",\n id: p.wrapperAttrs.id ?? \"\",\n \"data-sdvt-theme\": p.wrapperAttrs[\"data-sdvt-theme\"] ?? \"\",\n \"data-sdvt-density\": p.wrapperAttrs[\"data-sdvt-density\"] ?? \"\",\n };\n}\n// Every string below comes from the escaping string renderer (parts.ts), never from raw data.\nconst raw = (html: string): { __html: string } => ({ __html: html });\n// the string renderer's visually hidden <label> style, as the camelCase object React writes back in the same order\nconst SR_STYLE = Object.fromEntries(\n SR_ONLY.split(\";\").map((d) => {\n const [k = \"\", v = \"\"] = d.split(\":\");\n return [k.replace(/-(\\w)/g, (_, ch: string) => ch.toUpperCase()), v];\n }),\n) as CSSProperties;\n// no layout effects during SSR (React 18 warns about them there)\nconst useIsoLayoutEffect = typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n\n/** A visually hidden <label> + search <input>, the same markup as the string renderer's toolbar inputs. */\nfunction searchBox(\n key: string,\n id: string,\n label: string,\n cls: string,\n data: Record<string, string>,\n value: string,\n onChange: (e: SyntheticEvent) => void,\n placeholder: string = label,\n): ReactElement {\n return createElement(\n Fragment,\n { key },\n createElement(\"label\", { htmlFor: id, style: SR_STYLE }, label),\n createElement(\"input\", { type: \"search\", id, className: cls, ...data, placeholder, value, onChange }),\n );\n}\n\n/**\n * Same markup as `renderHTML` (test-enforced). Two forms: `{ spec, rows }` builds an engine (only when `interactive`;\n * a static table needs none) that lives as long as the component; `{ table }` renders an engine you own, statically\n * (its current page: filtered, sorted, sliced) or with controls. The table block is the string renderer's output;\n * React owns only the toolbar inputs and the pager, so typing in a filter never re-creates the input. The inputs are\n * controlled by the engine state, so a filter set from outside (another component, a linked selection) shows in its\n * box. A control or focusable cell element that a re-render replaces gets focus back, as in `hydrate`. The\n * Google-Fonts link is NOT rendered: put `fontsLinkFor(spec)` in the page head.\n *\n * Two cases remount the owned engine (`{ spec, rows }` form only; an engine passed as `table` is yours and is kept):\n * toggling `interactive` remounts it, losing the user's sort, filters, page and selection, since a static render\n * builds no engine. And the engine is keyed by the spec's JSON, which drops functions, so a deliberately changed\n * formatter or comparator under an otherwise equal spec does NOT take effect: change the element's React `key` to\n * rebuild the engine from the new spec.\n */\nexport function SdvTable<Row>(props: SdvTableProps<Row>): ReactElement {\n const { interactive = false, css } = props;\n if (props.table !== undefined)\n return createElement(TableView<Row>, { table: props.table, interactive, css });\n if (interactive) return createElement(OwnedTable<Row>, props);\n const p = renderParts(props.spec, props.rows, { fonts: false, ...(css !== undefined && { css }) });\n return staticBlock(p);\n}\n\n/** The static table: wrapper, theme sheet and table block, as one string-renderer HTML blob. */\nfunction staticBlock(p: RenderedParts): ReactElement {\n const inner = `${p.sheet ? `<style>${p.sheet}</style>` : \"\"}${tableHTML(p)}`;\n return createElement(\"div\", { ...wrapperProps(p), dangerouslySetInnerHTML: raw(inner) });\n}\n\n/** I2: the owned engine exists only here, so `{ table }` and static renders never build one. */\nfunction OwnedTable<Row>({ spec, rows, pageSize, sort, css }: SdvTableOwnProps<Row>): ReactElement {\n const table = useOwnedTable(spec, rows, {\n ...(pageSize !== undefined && { pageSize }),\n ...(sort !== undefined && { sort }),\n });\n return createElement(TableView<Row>, { table, interactive: true, css });\n}\n\nfunction TableView<Row>({\n table,\n interactive,\n css,\n}: {\n table: Table<Row>;\n interactive: boolean;\n css: \"inline\" | \"none\" | undefined;\n}): ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const restore = useRef<(() => void) | null>(null);\n // A50: remember the focused control while the DOM still holds it (the engine notifies before React re-renders).\n // A hover is a class toggle on the rendered rows, never a re-render (that would replace the row under the pointer).\n const subscribe = useCallback(\n (onChange: () => void) =>\n table.subscribe((e) => {\n if (e.type === \"hover\") {\n if (ref.current) applyHover(ref.current, table, e.id);\n } else if (restore.current === null && ref.current) restore.current = captureFocus(ref.current);\n onChange();\n }),\n [table],\n );\n useSyncExternalStore(subscribe, table.getSnapshot, table.getSnapshot); // J31: an external engine re-renders us too\n useIsoLayoutEffect(() => {\n // a render may have rebuilt the body rows; getHover, not a ref, so a (re)mount or a new table shows its own hover\n if (ref.current) applyHover(ref.current, table, table.getHover());\n restore.current?.();\n restore.current = null;\n });\n const base: RenderOptions = { fonts: false, ...(css !== undefined && { css }) };\n // M3: without controls, the engine's current page, coloured from all its rows (A49)\n if (!interactive)\n return staticBlock(\n renderParts(table.spec, table.rows, { ...base, ...tableRenderOptions(table), interactive: false }),\n );\n\n const p = renderParts(table.spec, table.rows, { ...base, ...tableRenderOptions(table) });\n const onInput = (e: SyntheticEvent): void => handleInput(table, e.target);\n const atStart = table.state.page === 0;\n const atEnd = table.state.page >= table.pageCount - 1;\n // A50: the hydrated SSR markup: an edge button is aria-disabled (not disabled), the label a polite live region\n const pager =\n table.state.pageSize === Number.POSITIVE_INFINITY\n ? null\n : createElement(\n \"nav\",\n { className: \"sdvt-pager\", \"data-sdv-pager\": \"\", \"aria-label\": \"Pagination\" },\n createElement(\n \"button\",\n {\n type: \"button\",\n className: \"sdvt-page\",\n \"data-sdv-page\": \"prev\",\n \"aria-label\": \"Previous page\",\n \"aria-disabled\": atStart ? \"true\" : undefined,\n },\n \"‹\",\n ),\n createElement(\n \"span\",\n { className: \"sdvt-page-label\", \"data-sdv-page-label\": \"\", \"aria-live\": \"polite\" },\n pagerLabel(table),\n ),\n createElement(\n \"button\",\n {\n type: \"button\",\n className: \"sdvt-page\",\n \"data-sdv-page\": \"next\",\n \"aria-label\": \"Next page\",\n \"aria-disabled\": atEnd ? \"true\" : undefined,\n },\n \"›\",\n ),\n );\n return createElement(\n \"div\",\n {\n ...wrapperProps(p),\n ref,\n onClick: (e: SyntheticEvent) => handleClick(table, e.target),\n onMouseOver: (e: SyntheticEvent) => handleHover(table, e.target), // J31 (A8): no attribute, so SSR is unchanged\n onMouseLeave: (e: SyntheticEvent) => handleHover(table, e.target),\n // Task 10: hotkeys and the grid's keys, as in hydrate; an event prop writes no attribute\n onKeyDown: (e: ReactKeyboardEvent) => {\n if (ref.current) handleKeydown(table, ref.current, e.nativeEvent);\n },\n },\n // biome-ignore lint/security/noDangerouslySetInnerHtml: escaped renderer output (see `raw`)\n p.sheet ? createElement(\"style\", { dangerouslySetInnerHTML: raw(p.sheet) }) : null,\n createElement(\n \"div\",\n { className: \"sdvt-toolbar\" },\n searchBox(\n \"\",\n `${p.id}-search`,\n \"Search all columns\",\n \"sdvt-global-filter\",\n { \"data-sdv-global-filter\": \"\" },\n table.state.globalFilter,\n onInput,\n \"Search\",\n ),\n // keyed by column, so hiding a column never hands another column's input (and its focus) over\n filterInputs(table, p.labels).map((f, j) =>\n searchBox(\n f.key,\n `${p.id}-filter-${j}`,\n `Filter ${f.label}`,\n \"sdvt-filter\",\n { \"data-sdv-filter\": f.key },\n f.value,\n onInput,\n ),\n ),\n ),\n createElement(\"div\", {\n className: \"sdvt-body\",\n \"data-sdv-body\": \"\",\n // biome-ignore lint/security/noDangerouslySetInnerHtml: escaped renderer output (see `raw`)\n dangerouslySetInnerHTML: raw(tableHTML(p)),\n }),\n pager,\n );\n}\n"]}