@zakkster/lite-table 1.0.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/Table.d.ts ADDED
@@ -0,0 +1,227 @@
1
+ /**
2
+ * @zakkster/lite-table - type declarations (M1)
3
+ */
4
+
5
+ import type { Signal, Computed } from "@zakkster/lite-signal";
6
+ import type { VirtualAxis, ScrollAlign } from "@zakkster/lite-virtual";
7
+
8
+ // --- Column types -----------------------------------------------------------
9
+
10
+ export type PinSide = "left" | "none" | "right";
11
+ export type SortDir = "asc" | "desc";
12
+
13
+ export interface ColumnDef<Row = any, Value = unknown> {
14
+ /** Field key on the row. Used for `row[key]` access and cell ids. */
15
+ key: string;
16
+ /** Header label. Defaults to `key`. */
17
+ header?: string;
18
+ /** Initial pixel width. Default 120. */
19
+ width?: number;
20
+ /** Resize lower bound. Default 40. */
21
+ minWidth?: number;
22
+ /** Resize upper bound. Default 1600. */
23
+ maxWidth?: number;
24
+ /** Initially hidden. Default false. */
25
+ hidden?: boolean;
26
+ /** Initial pin side. Default "none". */
27
+ pin?: PinSide;
28
+ /** Initial flex weight. Default 0 (fixed width). Set > 0 to make the
29
+ * column share leftover horizontal space proportionally: it renders as
30
+ * `minmax(<minWidth>px, <flex>fr)`. When any column has flex > 0, the
31
+ * trailing 1fr filler is dropped so flex columns absorb the space. */
32
+ flex?: number;
33
+ /** Feature flags -- disable per-column interactions. All default true. */
34
+ sortable?: boolean;
35
+ resizable?: boolean;
36
+ pinnable?: boolean;
37
+ hideable?: boolean;
38
+ reorderable?: boolean;
39
+ /** Computed value override; if absent, `row[key]` is used. */
40
+ accessor?: (row: Row) => Value;
41
+ /** Sort comparator; default is null-safe number-or-string compare. */
42
+ compare?: (a: Value, b: Value) => number;
43
+ }
44
+
45
+ /** Live reactive state for a column, exposed on `table.columns[i]`. */
46
+ export interface ColumnState<Row = any> {
47
+ readonly key: string;
48
+ readonly header: string;
49
+ readonly accessor: ((row: Row) => unknown) | null;
50
+ readonly compare: (a: unknown, b: unknown) => number;
51
+ readonly sortable: boolean;
52
+ readonly resizable: boolean;
53
+ readonly pinnable: boolean;
54
+ readonly hideable: boolean;
55
+ readonly reorderable: boolean;
56
+ readonly minWidth: number;
57
+ readonly maxWidth: number;
58
+ readonly width: Signal<number>;
59
+ readonly hidden: Signal<boolean>;
60
+ readonly pin: Signal<PinSide>;
61
+ readonly flex: Signal<number>;
62
+ }
63
+
64
+ // --- Focus / sort / selection types -----------------------------------------
65
+
66
+ export interface FocusedCell {
67
+ rowId: string | number;
68
+ columnKey: string;
69
+ }
70
+
71
+ export interface SortEntry {
72
+ key: string;
73
+ dir: SortDir;
74
+ }
75
+
76
+ export type SelectMode = "set" | "add" | "toggle" | "range";
77
+
78
+ /** Selection state is a PREDICATE, not a list of IDs.
79
+ * - `mode: "whitelist"`: `set` contains the selected IDs.
80
+ * - `mode: "all"`: `set` is a blacklist -- every row is selected EXCEPT
81
+ * those in `set`. This makes Ctrl+A across 1M rows O(1) -- no walk of
82
+ * the row source, no per-ID allocation. Materialization (e.g. for
83
+ * Submit/Copy/Export) is on demand via `selectedIds` / `selectedRows`
84
+ * / `forEachSelected`. */
85
+ export interface SelectionState<RowId = string | number> {
86
+ mode: "whitelist" | "all";
87
+ set: Set<RowId>;
88
+ }
89
+
90
+ export type FocusDirection =
91
+ | "up" | "down" | "left" | "right"
92
+ | "home" | "end" | "rowStart" | "rowEnd"
93
+ | "pageUp" | "pageDown";
94
+
95
+ // --- Config -----------------------------------------------------------------
96
+
97
+ export interface CreateTableConfig<Row = any> {
98
+ rows: readonly Row[] | (() => readonly Row[]);
99
+ columns: readonly ColumnDef<Row>[];
100
+ getRowId: (row: Row) => string | number;
101
+ rowHeight?: number;
102
+ overscan?: number;
103
+ initialFocus?: FocusedCell | null;
104
+ initialSort?: readonly SortEntry[];
105
+ }
106
+
107
+ // --- Headless core ----------------------------------------------------------
108
+
109
+ export interface TableCore<Row = any> {
110
+ // ---- Static ----
111
+ readonly columns: readonly ColumnState<Row>[];
112
+ readonly rowHeight: number;
113
+ readonly overscan: number;
114
+ getRowId(row: Row): string | number;
115
+ cellId(rowId: string | number, columnKey: string): string;
116
+
117
+ // ---- Reactive: data ----
118
+ rowsGetter(): readonly Row[];
119
+ readonly visibleRows: Computed<readonly Row[]>;
120
+ readonly rowCount: Computed<number>;
121
+
122
+ // ---- Reactive: columns ----
123
+ readonly columnOrder: Signal<readonly string[]>;
124
+ readonly visibleColumns: Computed<readonly ColumnState<Row>[]>;
125
+ /** 0-indexed position of each column within `visibleColumns`. */
126
+ readonly displayIndexByKey: Computed<Map<string, number>>;
127
+ /** 1-indexed CSS grid-column placement for each column. Accounts for the
128
+ * 1fr filler that lives between unpinned and right-pinned columns so
129
+ * right-pinned cells sit flush against the right edge. */
130
+ readonly colPlacement: Computed<Map<string, number>>;
131
+ readonly colTemplate: Computed<string>;
132
+ readonly contentWidth: Computed<number>;
133
+ readonly leftOffsets: Computed<Map<string, number>>;
134
+ readonly rightOffsets: Computed<Map<string, number>>;
135
+
136
+ // ---- Reactive: sort / focus / selection ----
137
+ readonly sortChain: Signal<readonly SortEntry[]>;
138
+ readonly focusedCell: Signal<FocusedCell | null>;
139
+ /** Predicate-based selection state. Reading `.set.has(id)` directly is
140
+ * wrong in all-mode -- use `isSelected(id)` for membership. */
141
+ readonly selection: Signal<SelectionState>;
142
+ readonly selectionAnchor: Signal<string | number | null>;
143
+ /** Reactive O(1) selected count. In all-mode this is
144
+ * `rowCount() - blacklist.size`; otherwise `whitelist.size`. */
145
+ readonly selectedCount: Computed<number>;
146
+
147
+ // ---- Methods: sort ----
148
+ setSort(key: string, dir: SortDir | null): void;
149
+ addSort(key: string, dir: SortDir | null): void;
150
+ toggleSort(key: string, opts?: { additive?: boolean }): void;
151
+ clearSort(): void;
152
+
153
+ // ---- Methods: selection ----
154
+ selectRow(rowId: string | number, mode?: SelectMode): void;
155
+ selectRowRange(anchorId: string | number | null, targetId: string | number): void;
156
+ /** Select every row. O(1) -- flips to all-mode with an empty blacklist. */
157
+ selectAll(): void;
158
+ clearSelection(): void;
159
+ /** O(1) predicate. Handles both whitelist and all-mode transparently. */
160
+ isSelected(rowId: string | number): boolean;
161
+ /** Materialize the selected IDs. O(N) in the row source. Defaults to
162
+ * `visibleRows()`; pass a source for export against the unsorted master. */
163
+ selectedIds<R = Row>(source?: readonly R[]): (string | number)[];
164
+ /** Materialize the selected row objects. O(N) in the row source. */
165
+ selectedRows<R = Row>(source?: readonly R[]): R[];
166
+ /** Stream selected rows without materializing the full list. Returning
167
+ * `false` from `fn` stops iteration. Use this for CSV / network export. */
168
+ forEachSelected<R = Row>(
169
+ fn: (row: R, id: string | number, index: number) => boolean | void,
170
+ source?: readonly R[]
171
+ ): void;
172
+
173
+ // ---- Methods: columns ----
174
+ setColumnWidth(key: string, w: number): void;
175
+ setColumnHidden(key: string, hidden: boolean): void;
176
+ setColumnPin(key: string, side: PinSide): void;
177
+ setColumnFlex(key: string, flex: number): void;
178
+ setColumnOrder(keys: readonly string[]): void;
179
+ moveColumn(fromKey: string, toKey: string, opts?: { before?: boolean }): void;
180
+
181
+ // ---- Methods: focus ----
182
+ moveFocus(direction: FocusDirection, opts?: { pageSize?: number }): void;
183
+
184
+ // ---- Lifecycle ----
185
+ dispose(): void;
186
+
187
+ /**
188
+ * @internal
189
+ * Reactive scope used internally to track signals/computeds/effects
190
+ * created by this table. Exposed so tests can register additional
191
+ * effects whose disposal is tied to `table.dispose()`. Not for
192
+ * application use -- the public surface above covers all intended
193
+ * patterns.
194
+ */
195
+ readonly _scope: {
196
+ effect(fn: () => void): () => void;
197
+ onCleanup(fn: () => void): void;
198
+ };
199
+ }
200
+
201
+ export function createTable<Row = any>(
202
+ config: CreateTableConfig<Row>
203
+ ): TableCore<Row>;
204
+
205
+ // --- DOM mount --------------------------------------------------------------
206
+
207
+ export interface MountOptions {
208
+ injectStyles?: boolean;
209
+ initialViewportHeight?: number;
210
+ }
211
+
212
+ export interface TableMount {
213
+ readonly root: HTMLElement;
214
+ readonly viewport: HTMLElement;
215
+ readonly axis: VirtualAxis;
216
+ scrollToIndex(index: number, align?: ScrollAlign): void;
217
+ poolSize(): number;
218
+ dispose(): void;
219
+ }
220
+
221
+ export function mountTable<Row = any>(
222
+ host: HTMLElement,
223
+ table: TableCore<Row>,
224
+ options?: MountOptions
225
+ ): TableMount;
226
+
227
+ export function _resetStylesForTest(): void;