@zakkster/lite-table 1.1.0 → 1.3.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/CHANGELOG.md +151 -0
- package/README.md +223 -18
- package/Table.d.ts +178 -0
- package/Table.js +1136 -148
- package/llms.txt +212 -4
- package/package.json +5 -3
package/Table.d.ts
CHANGED
|
@@ -47,12 +47,27 @@ export interface ColumnDef<Row = any, Value = unknown> {
|
|
|
47
47
|
filter?: (value: Value, query: string, row: Row) => boolean;
|
|
48
48
|
/** M2: placeholder text for the filter input. Default "Filter…". */
|
|
49
49
|
filterPlaceholder?: string;
|
|
50
|
+
/** M3: aggregate reducer for group-header + grand-total cells. One of the
|
|
51
|
+
* built-in strings, or a custom `(rows, col) => value` function. `null`
|
|
52
|
+
* or omitted means "no aggregate" -- the column shows blank in header
|
|
53
|
+
* rows. Aggregates always fold over LEAF rows at every depth (safe
|
|
54
|
+
* for non-associative reducers like median or last-value-wins). */
|
|
55
|
+
aggregate?: AggregateSpec<Row> | null;
|
|
56
|
+
/** M3: format the aggregate for display. Only affects rendered text --
|
|
57
|
+
* `entry.aggregates.get(key)` still returns the raw value so consumers
|
|
58
|
+
* can format independently for export etc. Errors are caught + logged. */
|
|
59
|
+
aggregateFormat?: (value: unknown, col: ColumnState<Row>, count: number) => string;
|
|
50
60
|
/** Computed value override; if absent, `row[key]` is used. */
|
|
51
61
|
accessor?: (row: Row) => Value;
|
|
52
62
|
/** Sort comparator; default is null-safe number-or-string compare. */
|
|
53
63
|
compare?: (a: Value, b: Value) => number;
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
/** M3: aggregate spec on a column. */
|
|
67
|
+
export type AggregateSpec<Row = any> =
|
|
68
|
+
| "sum" | "avg" | "min" | "max" | "count"
|
|
69
|
+
| ((rows: readonly Row[], col: ColumnState<Row>) => unknown);
|
|
70
|
+
|
|
56
71
|
/** Live reactive state for a column, exposed on `table.columns[i]`. */
|
|
57
72
|
export interface ColumnState<Row = any> {
|
|
58
73
|
readonly key: string;
|
|
@@ -117,6 +132,42 @@ export interface SortEntry {
|
|
|
117
132
|
dir: SortDir;
|
|
118
133
|
}
|
|
119
134
|
|
|
135
|
+
/** Per-column layout snapshot inside a {@link ViewState}. */
|
|
136
|
+
export interface ViewColumnState {
|
|
137
|
+
width: number;
|
|
138
|
+
hidden: boolean;
|
|
139
|
+
pin: PinSide;
|
|
140
|
+
flex: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A JSON-serializable snapshot of the table's LAYOUT + QUERY -- sort,
|
|
145
|
+
* per-column layout, order, filters, grouping. Produced by
|
|
146
|
+
* `getViewState()` and consumed by `setViewState()`. No live signal refs,
|
|
147
|
+
* no `Map`/`Set` instances: `filters` is a plain object, `collapsedGroups`
|
|
148
|
+
* an array. This is the persistence seam lite-headless `createSavedViews`
|
|
149
|
+
* consumes; selection/focus/edit state are transient and excluded.
|
|
150
|
+
*/
|
|
151
|
+
export interface ViewState {
|
|
152
|
+
/** Schema version. Always `1` in this release. */
|
|
153
|
+
v: 1;
|
|
154
|
+
/** Sort chain, outermost key first. */
|
|
155
|
+
sort: SortEntry[];
|
|
156
|
+
/** Column order by key. */
|
|
157
|
+
columnOrder: string[];
|
|
158
|
+
/** Full per-column layout for every current column, keyed by column key. */
|
|
159
|
+
columns: { [key: string]: ViewColumnState };
|
|
160
|
+
/** Active column filters by key; empty/whitespace queries are omitted. */
|
|
161
|
+
filters: { [key: string]: string };
|
|
162
|
+
/** Group-by keys, outermost first. */
|
|
163
|
+
groupBy: string[];
|
|
164
|
+
/** Collapsed group path-strings (U+001F separator). */
|
|
165
|
+
collapsedGroups: string[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Reserved options for {@link TableCore.setViewState}. Ignored in v1. */
|
|
169
|
+
export interface SetViewStateOptions {}
|
|
170
|
+
|
|
120
171
|
export type SelectMode = "set" | "add" | "toggle" | "range";
|
|
121
172
|
|
|
122
173
|
/** Selection state is a PREDICATE, not a list of IDs.
|
|
@@ -152,6 +203,69 @@ export interface CreateTableConfig<Row = any> {
|
|
|
152
203
|
* optimistic update, etc.). Skipped when newValue === oldValue.
|
|
153
204
|
* Errors thrown by the handler are caught + logged. */
|
|
154
205
|
onCellEdit?: (payload: CellEditPayload<Row>) => void | Promise<unknown>;
|
|
206
|
+
|
|
207
|
+
/** M3: group rows by one or more column keys. `null` (default) or an
|
|
208
|
+
* empty array = no grouping. A bare string is normalized to `[string]`.
|
|
209
|
+
* Unknown column keys are silently dropped so persisted state that
|
|
210
|
+
* outlives a column config survives without crashes. */
|
|
211
|
+
groupBy?: string | readonly string[] | null;
|
|
212
|
+
/** M3: pre-seed the collapsed set. Each entry is a group path -- e.g.
|
|
213
|
+
* `[["Europe"], ["Asia", "Books"]]`. Unknown paths become no-ops when
|
|
214
|
+
* their groups don't exist. */
|
|
215
|
+
initialCollapsedGroups?: readonly (readonly string[])[];
|
|
216
|
+
/** M3: append a grand-total entry at the tail of `visibleEntries` when
|
|
217
|
+
* any column has an `aggregate` spec. Grand-total aggregates fold
|
|
218
|
+
* over `filteredRows()` and are stable across collapse operations. */
|
|
219
|
+
showGrandTotal?: boolean;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// --- M3: grouping + aggregation types ---------------------------------------
|
|
223
|
+
|
|
224
|
+
/** One node in the grouped-rows tree. Leaf nodes have `rows` populated and
|
|
225
|
+
* `subGroups: null`; internal nodes have `subGroups` populated and
|
|
226
|
+
* `rows: null`. Aggregates always fold over leaf rows recursively. */
|
|
227
|
+
export interface GroupNode<Row = any> {
|
|
228
|
+
readonly depth: number;
|
|
229
|
+
/** The column key this depth groups on. */
|
|
230
|
+
readonly key: string;
|
|
231
|
+
/** The group's identifying value (post-`accessor`). `null` for the
|
|
232
|
+
* null / undefined bucket. */
|
|
233
|
+
readonly value: unknown;
|
|
234
|
+
/** Path of ancestor values including this node. e.g. `["Europe", "Books"]`. */
|
|
235
|
+
readonly path: readonly string[];
|
|
236
|
+
/** Stringified path (U+001F separator) -- the storage key for the
|
|
237
|
+
* collapsed-groups Set. */
|
|
238
|
+
readonly pathStr: string;
|
|
239
|
+
/** Recursive leaf-row count. */
|
|
240
|
+
readonly count: number;
|
|
241
|
+
readonly aggregates: ReadonlyMap<string, unknown>;
|
|
242
|
+
readonly subGroups: readonly GroupNode<Row>[] | null;
|
|
243
|
+
readonly rows: readonly Row[] | null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** A row in `visibleEntries`. The mount layer dispatches per-slot rendering
|
|
247
|
+
* on `type`. */
|
|
248
|
+
export type Entry<Row = any> = DataEntry<Row> | GroupHeaderEntry | GrandTotalEntry;
|
|
249
|
+
|
|
250
|
+
export interface DataEntry<Row = any> {
|
|
251
|
+
readonly type: "data";
|
|
252
|
+
readonly row: Row;
|
|
253
|
+
}
|
|
254
|
+
export interface GroupHeaderEntry {
|
|
255
|
+
readonly type: "group-header";
|
|
256
|
+
readonly depth: number;
|
|
257
|
+
readonly key: string;
|
|
258
|
+
readonly value: unknown;
|
|
259
|
+
readonly path: readonly string[];
|
|
260
|
+
readonly pathStr: string;
|
|
261
|
+
readonly count: number;
|
|
262
|
+
readonly aggregates: ReadonlyMap<string, unknown>;
|
|
263
|
+
readonly isCollapsed: boolean;
|
|
264
|
+
}
|
|
265
|
+
export interface GrandTotalEntry {
|
|
266
|
+
readonly type: "grand-total";
|
|
267
|
+
readonly aggregates: ReadonlyMap<string, unknown>;
|
|
268
|
+
readonly count: number;
|
|
155
269
|
}
|
|
156
270
|
|
|
157
271
|
// --- Export option types ----------------------------------------------------
|
|
@@ -215,6 +329,28 @@ export interface TableCore<Row = any> {
|
|
|
215
329
|
readonly visibleRows: Computed<readonly Row[]>;
|
|
216
330
|
readonly rowCount: Computed<number>;
|
|
217
331
|
|
|
332
|
+
// ---- Reactive: grouping + aggregation (M3) ----
|
|
333
|
+
/** Current grouping keys. Empty array = no grouping (fast path).
|
|
334
|
+
* Assign via `setGroupBy`; the raw signal is exposed for read-only
|
|
335
|
+
* access + persistence with `@zakkster/lite-persist`. */
|
|
336
|
+
readonly groupBy: Signal<readonly string[]>;
|
|
337
|
+
/** Set of collapsed group path-strings (U+001F separator). Mutated via
|
|
338
|
+
* `toggleGroup`, `collapseGroup`, `expandGroup`, `collapseAllGroups`,
|
|
339
|
+
* `expandAllGroups`. `isGroupCollapsed(path)` is the O(1) predicate. */
|
|
340
|
+
readonly collapsedGroups: Signal<ReadonlySet<string>>;
|
|
341
|
+
/** Grouped tree of `filteredRows`, sorted within leaves per `sortChain`.
|
|
342
|
+
* `null` when `groupBy` is empty -- consumers can branch on this to
|
|
343
|
+
* render a flat list without walking the tree. */
|
|
344
|
+
readonly groupedRows: Computed<readonly GroupNode<Row>[] | null>;
|
|
345
|
+
/** Flat interleaved list of `{type: "data" | "group-header" | "grand-total"}`
|
|
346
|
+
* entries. Drives the mount layer's slot rendering. Length matches
|
|
347
|
+
* `entryCount()`. */
|
|
348
|
+
readonly visibleEntries: Computed<readonly Entry<Row>[]>;
|
|
349
|
+
/** Total entry count including group headers + grand total. Drives the
|
|
350
|
+
* virtual axis in the mount layer -- `rowCount` (data-only) is kept
|
|
351
|
+
* as a separate consumer stat. */
|
|
352
|
+
readonly entryCount: Computed<number>;
|
|
353
|
+
|
|
218
354
|
// ---- Reactive: columns ----
|
|
219
355
|
readonly columnOrder: Signal<readonly string[]>;
|
|
220
356
|
readonly visibleColumns: Computed<readonly ColumnState<Row>[]>;
|
|
@@ -293,6 +429,30 @@ export interface TableCore<Row = any> {
|
|
|
293
429
|
/** Clear all filters. No-op + no notify if the filter map is already empty. */
|
|
294
430
|
clearColumnFilters(): void;
|
|
295
431
|
|
|
432
|
+
// ---- Methods: grouping (M3) ----
|
|
433
|
+
/** Set the grouping keys. Accepts a bare string, a string[], or
|
|
434
|
+
* null/empty (no grouping). Unknown column keys are silently dropped.
|
|
435
|
+
* Idempotent -- setting the same effective value is a no-op that
|
|
436
|
+
* doesn't churn the signal. */
|
|
437
|
+
setGroupBy(v: string | readonly string[] | null): void;
|
|
438
|
+
/** Flip the collapsed state for the given group path. */
|
|
439
|
+
toggleGroup(path: readonly string[]): void;
|
|
440
|
+
/** Expand a specific group (no-op if already expanded). */
|
|
441
|
+
expandGroup(path: readonly string[]): void;
|
|
442
|
+
/** Collapse a specific group (no-op if already collapsed). */
|
|
443
|
+
collapseGroup(path: readonly string[]): void;
|
|
444
|
+
/** Expand every group -- clears the collapsed set. */
|
|
445
|
+
expandAllGroups(): void;
|
|
446
|
+
/** Collapse every group in the current tree. Walks `groupedRows()` once. */
|
|
447
|
+
collapseAllGroups(): void;
|
|
448
|
+
/** O(1) predicate: is this exact group path currently collapsed? */
|
|
449
|
+
isGroupCollapsed(path: readonly string[]): boolean;
|
|
450
|
+
/** Given an entry index (typically `axis.start()`), return the group
|
|
451
|
+
* headers that CONTAIN it -- one per depth level, deepest last.
|
|
452
|
+
* Used by consumers implementing sticky group headers. Returns [] for
|
|
453
|
+
* the ungrouped fast path or when the target is above the first header. */
|
|
454
|
+
groupAncestryAt(entryIndex: number): readonly GroupHeaderEntry[];
|
|
455
|
+
|
|
296
456
|
// ---- Methods: editing (M2) ----
|
|
297
457
|
/** Start editing the (rowId, columnKey) cell. No-op on non-editable
|
|
298
458
|
* columns. Auto-commits any in-flight edit of a different cell. Idempotent
|
|
@@ -314,6 +474,24 @@ export interface TableCore<Row = any> {
|
|
|
314
474
|
// ---- Methods: focus ----
|
|
315
475
|
moveFocus(direction: FocusDirection, opts?: { pageSize?: number }): void;
|
|
316
476
|
|
|
477
|
+
// ---- Methods: view state (persistence seam) ----
|
|
478
|
+
/**
|
|
479
|
+
* Snapshot the full layout + query as a JSON-serializable {@link ViewState}
|
|
480
|
+
* -- deep-copied, no live signal refs, no `Map`/`Set` instances. Persist
|
|
481
|
+
* it, then restore it with {@link TableCore.setViewState}.
|
|
482
|
+
*/
|
|
483
|
+
getViewState(): ViewState;
|
|
484
|
+
/**
|
|
485
|
+
* Restore a {@link ViewState} with REPLACE semantics, applied atomically
|
|
486
|
+
* inside a single batch. Throws `TypeError` before any mutation on a
|
|
487
|
+
* non-object, a missing `v`, or a `v` other than `1`. Within a v1 view a
|
|
488
|
+
* single malformed entry (bad sort dir, non-number width, unknown key) is
|
|
489
|
+
* skipped and the rest applied. Stale column orders are reconciled, so a
|
|
490
|
+
* dropped/added column never rejects the restore. `opts` is reserved and
|
|
491
|
+
* ignored in v1.
|
|
492
|
+
*/
|
|
493
|
+
setViewState(view: ViewState, opts?: SetViewStateOptions): void;
|
|
494
|
+
|
|
317
495
|
// ---- Lifecycle ----
|
|
318
496
|
dispose(): void;
|
|
319
497
|
|