@gridkitjs/core 0.8.0 → 0.9.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.d.ts +187 -15
- package/dist/index.js +340 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -35,16 +35,27 @@ interface CellTemplateContext<Row> {
|
|
|
35
35
|
value: unknown;
|
|
36
36
|
/** The whole row, for a template that needs a sibling field. */
|
|
37
37
|
row: Row;
|
|
38
|
-
/**
|
|
39
|
-
* Position among the rows as rendered. Once paging exists this is the index
|
|
40
|
-
* within the page; an index into the whole dataset would be a second field.
|
|
41
|
-
*/
|
|
38
|
+
/** Position among the rows as rendered — the index within the current page once paging is on. */
|
|
42
39
|
rowIndex: number;
|
|
40
|
+
/** This row's absolute position in the whole filtered/sorted/grouped dataset, unaffected by which page is showing. */
|
|
41
|
+
datasetIndex: number;
|
|
43
42
|
/** This row's id, as `resolveRowId` settled it. */
|
|
44
43
|
rowId: string;
|
|
45
44
|
/** Whether this row is selected, so a template can style itself to match. */
|
|
46
45
|
selected: boolean;
|
|
47
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* What a column's `footerTemplate` receives. Mirrors `CellTemplateContext`'s
|
|
49
|
+
* shape where it applies — `value`, `rows` — minus everything scoped to one
|
|
50
|
+
* rendered row (`rowIndex`, `rowId`, `selected`), since a footer addresses
|
|
51
|
+
* every row an aggregate was computed over at once, not a single one.
|
|
52
|
+
*/
|
|
53
|
+
interface FooterTemplateContext<Row> {
|
|
54
|
+
/** This column's aggregate result, as `computeAggregates` produced it. */
|
|
55
|
+
value: unknown;
|
|
56
|
+
/** Every row the aggregate was computed over — the grand total's full dataset, or one group's full leaf descendants. */
|
|
57
|
+
rows: readonly Row[];
|
|
58
|
+
}
|
|
48
59
|
/**
|
|
49
60
|
* @typeParam Node - What a header or cell renders to. This package is
|
|
50
61
|
* framework-agnostic so it defaults to `string`; `@gridkitjs/react` binds it to
|
|
@@ -71,6 +82,13 @@ interface ColumnDefinition<Row, Node = string> {
|
|
|
71
82
|
* it never repeats the field path.
|
|
72
83
|
*/
|
|
73
84
|
cellTemplate?: ((context: CellTemplateContext<Row>) => Node) | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Renders this column's own aggregate result in a group footer or the
|
|
87
|
+
* grand-total footer, in place of the raw computed value. Absent for a
|
|
88
|
+
* column with no aggregate: nothing is shown, the same way a column
|
|
89
|
+
* missing from `AggregateState` contributes no cell to either footer.
|
|
90
|
+
*/
|
|
91
|
+
footerTemplate?: ((context: FooterTemplateContext<Row>) => Node) | undefined;
|
|
74
92
|
/**
|
|
75
93
|
* The value type of this column's cells. This package is framework-agnostic so it
|
|
76
94
|
* defaults to `string`;
|
|
@@ -150,6 +168,16 @@ interface ColumnWrapConfig {
|
|
|
150
168
|
* still takes effect, and resetting is discarding the state.
|
|
151
169
|
*/
|
|
152
170
|
type ColumnSizingState = Readonly<Record<string, number>>;
|
|
171
|
+
/** The active page, and how many rows/units make one up. */
|
|
172
|
+
interface PaginationState {
|
|
173
|
+
readonly pageIndex: number;
|
|
174
|
+
readonly pageSize: number;
|
|
175
|
+
}
|
|
176
|
+
/** Reports a user page/page-size change whole — the one to persist from, mirroring ColumnSortEvent. */
|
|
177
|
+
interface PaginationChangeEvent {
|
|
178
|
+
readonly pagination: PaginationState;
|
|
179
|
+
readonly pageCount: number;
|
|
180
|
+
}
|
|
153
181
|
/** Sizes used for a column that does not specify its own. */
|
|
154
182
|
interface ColumnSizeDefaults {
|
|
155
183
|
width: number;
|
|
@@ -304,9 +332,8 @@ interface GroupExpansionEvent {
|
|
|
304
332
|
* down to and including this group's own, outermost first — what a header
|
|
305
333
|
* template needs to render "West / Enterprise", not just "Enterprise".
|
|
306
334
|
*
|
|
307
|
-
* Not parameterized by `Row`: nothing here holds a `Row`-typed value today
|
|
308
|
-
*
|
|
309
|
-
* exists, rather than carrying an unused one now.
|
|
335
|
+
* Not parameterized by `Row`: nothing here holds a `Row`-typed value today —
|
|
336
|
+
* `aggregates` holds computed `unknown` results, not rows themselves.
|
|
310
337
|
*/
|
|
311
338
|
interface ResolvedGroupRow {
|
|
312
339
|
readonly kind: "group";
|
|
@@ -323,14 +350,92 @@ interface ResolvedGroupRow {
|
|
|
323
350
|
readonly count: number;
|
|
324
351
|
/** Position among the display rows as rendered — same invariant ResolvedRow.rowIndex keeps. */
|
|
325
352
|
readonly rowIndex: number;
|
|
353
|
+
/** This header's absolute position in the whole filtered/sorted/grouped dataset, matching ResolvedRow.datasetIndex. */
|
|
354
|
+
readonly datasetIndex: number;
|
|
355
|
+
/**
|
|
356
|
+
* This group's own aggregate results, computed over its full leaf-row
|
|
357
|
+
* descendants regardless of collapse state — see `withGroupAggregates`.
|
|
358
|
+
* Empty when no `AggregateState` is active.
|
|
359
|
+
*/
|
|
360
|
+
readonly aggregates: AggregateResults;
|
|
326
361
|
}
|
|
327
362
|
/**
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
363
|
+
* A group's own aggregate results, rendered as a row of its own rather than
|
|
364
|
+
* inline in its header — see `groupAggregateDisplay: "row"`. Emitted by
|
|
365
|
+
* `withGroupAggregates` immediately after a group's last visible
|
|
366
|
+
* descendant (its last nested group or data row, or immediately after its
|
|
367
|
+
* own header when collapsed), at that same group's `level`, one per group
|
|
368
|
+
* with `AggregateState` active — mirroring `ResolvedGroupRow` closely
|
|
369
|
+
* enough that the two are meant to be read side by side.
|
|
370
|
+
*/
|
|
371
|
+
interface ResolvedGroupSummaryRow {
|
|
372
|
+
readonly kind: "group-summary";
|
|
373
|
+
/** The group this summary belongs to — shared with that group's own `ResolvedGroupRow.groupId`, not a new id of its own. */
|
|
374
|
+
readonly groupId: string;
|
|
375
|
+
/** Nesting depth, matching the group's own `level`. */
|
|
376
|
+
readonly level: number;
|
|
377
|
+
/** Position among the display rows as rendered — same invariant every other DisplayRow's `rowIndex` keeps. */
|
|
378
|
+
readonly rowIndex: number;
|
|
379
|
+
/** This row's absolute position in the whole filtered/sorted/grouped dataset, matching ResolvedRow.datasetIndex. */
|
|
380
|
+
readonly datasetIndex: number;
|
|
381
|
+
/** This group's own aggregate results — identical to its `ResolvedGroupRow.aggregates`, computed once and shared rather than recomputed. */
|
|
382
|
+
readonly aggregates: AggregateResults;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* One entry of a grouped result: an ordinary data row (no `kind` field at
|
|
386
|
+
* all — ResolvedRow is unchanged, so every existing consumer of plain
|
|
387
|
+
* ResolvedRow[] keeps compiling), a group header, or — only when
|
|
388
|
+
* `groupAggregateDisplay: "row"` is active — that group's own summary row.
|
|
389
|
+
* Discriminate with `"kind" in entry`, then `entry.kind`.
|
|
390
|
+
*/
|
|
391
|
+
type DisplayRow<Row> = ResolvedRow<Row> | ResolvedGroupRow | ResolvedGroupSummaryRow;
|
|
392
|
+
/** A reducer this package computes without a custom `AggregateFn`. */
|
|
393
|
+
type BuiltInAggregate = "sum" | "avg" | "min" | "max" | "count" | "countDistinct";
|
|
394
|
+
/**
|
|
395
|
+
* Called with every raw row in scope — always the full leaf set, never a
|
|
396
|
+
* partial reduction combined from child groups, so a non-associative
|
|
397
|
+
* aggregate (median, distinct count) is never computed from
|
|
398
|
+
* already-aggregated values. Mirrors `FilterPredicate`'s shape, minus the
|
|
399
|
+
* per-row `value` since an aggregate reduces across rows rather than
|
|
400
|
+
* testing one.
|
|
401
|
+
*/
|
|
402
|
+
type AggregateFn<Row> = (rows: readonly Row[]) => unknown;
|
|
403
|
+
/** One aggregate to compute against a column — a built-in reducer, or custom logic via `fn`. */
|
|
404
|
+
interface AggregateSpec<Row> {
|
|
405
|
+
readonly columnId: string;
|
|
406
|
+
readonly fn: BuiltInAggregate | AggregateFn<Row>;
|
|
407
|
+
/** Distinguishes two aggregates on the same column (e.g. both sum and avg of Amount). Defaults to columnId, mirroring ColumnDefinition.id defaulting to field. */
|
|
408
|
+
readonly id?: string;
|
|
409
|
+
/**
|
|
410
|
+
* Overrides this aggregate's own cell alignment, in the grand-total
|
|
411
|
+
* footer and a group's own summary row (`groupAggregateDisplay: "row"`)
|
|
412
|
+
* — inline rendering (`"inline"`) is text, not a cell, so this has no
|
|
413
|
+
* effect there. Falls back to the column's own `alignment` when omitted.
|
|
414
|
+
* When two specs share a column and disagree, the first one in
|
|
415
|
+
* `AggregateState` wins for that shared cell, since one `<td>` can only
|
|
416
|
+
* take one `text-align`.
|
|
417
|
+
*/
|
|
418
|
+
readonly alignment?: ColumnAlignment;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The active aggregates to compute. Empty for a grid with none, mirroring
|
|
422
|
+
* `FilterState`'s empty array for "no filter". A fully controlled prop —
|
|
423
|
+
* unlike `sort`/`filter`/`groupBy`/`pagination`, there is no built-in UI
|
|
424
|
+
* affordance for a user to add or remove an aggregate interactively.
|
|
425
|
+
*/
|
|
426
|
+
type AggregateState<Row> = readonly AggregateSpec<Row>[];
|
|
427
|
+
/** One computed aggregate result, keyed by `spec.id ?? spec.columnId`. */
|
|
428
|
+
type AggregateResults = ReadonlyMap<string, unknown>;
|
|
429
|
+
/**
|
|
430
|
+
* Where a group's own aggregate results render. `"inline"` (the default)
|
|
431
|
+
* keeps them as text in the group header itself, next to its leaf-row
|
|
432
|
+
* count. `"row"` instead emits a `ResolvedGroupSummaryRow` immediately
|
|
433
|
+
* after that group's last visible descendant, with each aggregate's value
|
|
434
|
+
* in the `<td>` for its own column — the same alignment a grand-total
|
|
435
|
+
* footer cell has to its column, just scoped to one group instead of the
|
|
436
|
+
* whole dataset.
|
|
332
437
|
*/
|
|
333
|
-
type
|
|
438
|
+
type GroupAggregateDisplay = "inline" | "row";
|
|
334
439
|
interface FilterEntryBase<Row> {
|
|
335
440
|
readonly columnId?: FieldPath<Row> | (string & {});
|
|
336
441
|
}
|
|
@@ -452,6 +557,8 @@ interface ResolvedRow<Row> {
|
|
|
452
557
|
readonly row: Row;
|
|
453
558
|
/** Position among the rows as rendered, matching `CellTemplateContext`. */
|
|
454
559
|
readonly rowIndex: number;
|
|
560
|
+
/** This row's absolute position in the whole filtered/sorted/grouped dataset, matching `CellTemplateContext`. */
|
|
561
|
+
readonly datasetIndex: number;
|
|
455
562
|
}
|
|
456
563
|
/**
|
|
457
564
|
* A selected column. Carries the resolved column rather than the bare
|
|
@@ -624,8 +731,16 @@ declare function clampFocus(focus: GridFocus, rowCount: number, columnCount: num
|
|
|
624
731
|
* `pageSize` is a pre-measured row count rather than a callback: this
|
|
625
732
|
* function takes plain data only, so a caller measures the viewport once and
|
|
626
733
|
* hands over the number.
|
|
627
|
-
|
|
628
|
-
|
|
734
|
+
*
|
|
735
|
+
* `isSkippableRow` names rows the vertical moves (`ArrowUp`/`ArrowDown`/
|
|
736
|
+
* `PageUp`/`PageDown`/`Ctrl+End`) never land a tab stop on — a group's own
|
|
737
|
+
* summary row (`groupAggregateDisplay: "row"`), which still occupies a real
|
|
738
|
+
* row slot for `rowCount`/`aria-rowindex` purposes but is presentational,
|
|
739
|
+
* not a stop of its own. Left/right and plain `Home`/`End` stay row-scoped
|
|
740
|
+
* and so never consult it. Defaults to skipping nothing, so every existing
|
|
741
|
+
* caller (and every horizontal move) is unaffected.
|
|
742
|
+
*/
|
|
743
|
+
declare function nextFocusForKey(key: string, modifiers: NavigationModifiers, focus: GridFocus, rowCount: number, columnCount: number, pageSize: number, isSkippableRow?: (rowIndex: number) => boolean): GridFocus | null;
|
|
629
744
|
|
|
630
745
|
/**
|
|
631
746
|
* Puts `columns` in the order `order` gives. Ids it lists come first, in that
|
|
@@ -1018,4 +1133,61 @@ declare function resolveColumns<Row, Node>(columns: readonly ResolvedColumn<Row,
|
|
|
1018
1133
|
*/
|
|
1019
1134
|
declare function resolveCell<Row, Node>(rowsById: ReadonlyMap<string, ResolvedRow<Row>>, columns: readonly ResolvedColumn<Row, Node>[], cell: SelectedCellRef | null): SelectedCell<Row, Node> | null;
|
|
1020
1135
|
|
|
1021
|
-
|
|
1136
|
+
interface PaginatedRows<Row> {
|
|
1137
|
+
readonly rows: readonly DisplayRow<Row>[];
|
|
1138
|
+
readonly pageCount: number;
|
|
1139
|
+
/**
|
|
1140
|
+
* `pagination.pageIndex` clamped into `[0, pageCount - 1]` (or `0` when
|
|
1141
|
+
* `pageCount` is `0`) — read this back rather than trusting the input
|
|
1142
|
+
* verbatim, the same way `sortRows`/`filterRows` tolerate a stale stored
|
|
1143
|
+
* state.
|
|
1144
|
+
*/
|
|
1145
|
+
readonly pageIndex: number;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* `rows` sliced into pages of `pagination.pageSize` units — see `unitsOf`
|
|
1149
|
+
* for what a unit is. `pageIndex` is clamped into range, `pageSize <= 0` is
|
|
1150
|
+
* clamped to `1` (this codebase's general tolerance for stale/odd stored
|
|
1151
|
+
* state, matching `sortRows`/`filterRows`), and the returned page's
|
|
1152
|
+
* `rowIndex` is renumbered to be page-relative — 0-based from the top of the
|
|
1153
|
+
* page, header rows included, mirroring `groupRows`'s own renumbering
|
|
1154
|
+
* invariant. `datasetIndex` is left untouched: it is assigned once, ahead of
|
|
1155
|
+
* filter/sort/group/paginate, and reports each row's position in the whole
|
|
1156
|
+
* dataset regardless of which page is showing.
|
|
1157
|
+
*/
|
|
1158
|
+
declare function paginateRows<Row>(rows: readonly DisplayRow<Row>[], pagination: PaginationState): PaginatedRows<Row>;
|
|
1159
|
+
|
|
1160
|
+
type PaginationWindowEntry = number | "ellipsis";
|
|
1161
|
+
/**
|
|
1162
|
+
* Which page numbers a numbered pager should show for `currentPage` of
|
|
1163
|
+
* `pageCount`, collapsing any gap wider than one page into a single
|
|
1164
|
+
* `"ellipsis"` entry — the boundary + sibling windowing MUI's
|
|
1165
|
+
* `usePagination` and antd's `Pagination` both use. Always includes the
|
|
1166
|
+
* first/last `boundaryCount` pages and `currentPage`'s `siblingCount`
|
|
1167
|
+
* neighbors on each side; a gap of exactly one page is filled in rather than
|
|
1168
|
+
* collapsed, since an ellipsis standing in for a single page saves no space.
|
|
1169
|
+
*/
|
|
1170
|
+
declare function paginationWindow(currentPage: number, pageCount: number, options?: {
|
|
1171
|
+
boundaryCount?: number | undefined;
|
|
1172
|
+
siblingCount?: number | undefined;
|
|
1173
|
+
}): readonly PaginationWindowEntry[];
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* `rows` reduced through every spec in `specs`, keyed by `spec.id ??
|
|
1177
|
+
* spec.columnId`. A custom `fn` (a plain function, not a `BuiltInAggregate`
|
|
1178
|
+
* name) is called once with the raw `rows` — no wrapping, no
|
|
1179
|
+
* pre-processing, so it sees exactly the same leaf set a built-in reducer
|
|
1180
|
+
* does.
|
|
1181
|
+
*
|
|
1182
|
+
* Two specs resolving to the same key: the later one wins, matching
|
|
1183
|
+
* `setColumnFilter`'s own silent-replace precedent for a key collision
|
|
1184
|
+
* rather than throwing.
|
|
1185
|
+
*
|
|
1186
|
+
* Returns a fresh empty `Map` for empty `rows` or empty `specs` — never a
|
|
1187
|
+
* shared singleton — matching this codebase's general avoidance of shared
|
|
1188
|
+
* mutable module state.
|
|
1189
|
+
*/
|
|
1190
|
+
declare function computeAggregates<Row>(rows: readonly Row[], specs: AggregateState<Row>, columns: readonly ResolvedColumn<Row, unknown>[]): AggregateResults;
|
|
1191
|
+
declare function withGroupAggregates<Row>(displayRows: readonly DisplayRow<Row>[], flatRows: readonly ResolvedRow<Row>[], groupBy: GroupByState, specs: AggregateState<Row>, columns: readonly ResolvedColumn<Row, unknown>[], display?: GroupAggregateDisplay): readonly DisplayRow<Row>[];
|
|
1192
|
+
|
|
1193
|
+
export { type AggregateFn, type AggregateResults, type AggregateSpec, type AggregateState, type BuiltInAggregate, type CellSelectEvent, type CellSelectionChangeEvent, type CellSelectionMode, type CellSelectionState, type CellTemplateContext, type ColumnAlignment, type ColumnConstraints, type ColumnDefinition, type ColumnOrderEvent, type ColumnOrderState, type ColumnResizeEvent, type ColumnResizeSession, type ColumnResolveOptions, type ColumnSelectEvent, type ColumnSelectionChangeEvent, type ColumnSizeDefaults, type ColumnSizingState, type ColumnSortEntry, type ColumnSortEvent, type ColumnSortState, type ColumnType, type ColumnWrapConfig, type ColumnsSelectEvent, DEFAULT_COLUMN_SIZES, type DisplayRow, type DropSide, type FieldPath, type FilterEntry, type FilterPredicate, type FilterState, type FooterTemplateContext, type GridFocus, type GroupAggregateDisplay, type GroupByEntry, type GroupByEvent, type GroupByState, type GroupExpansionEvent, type GroupExpansionState, type GroupFilterEntry, HEADER_ROW, KEYBOARD_STEP, type KeyShortcutCapabilities, type NavigationModifiers, type PaginatedRows, type PaginationChangeEvent, type PaginationState, type PaginationWindowEntry, type PredicateFilterEntry, type ResolvedColumn, type ResolvedGroupRow, type ResolvedGroupSummaryRow, type ResolvedRow, type RowSelectEvent, type RowSelectionChangeEvent, type RowsSelectEvent, type SelectIntent, type SelectableConfig, type SelectedCell, type SelectedCellRef, type SelectedColumn, type SelectionDiff, type SelectionMode, type SelectionState, type SortDirection, type TextFilterEntry, type ValueFilterEntry, accessDotted, alignmentForType, applyColumnOrder, applyColumnResize, applySelectionIntent, beginColumnResize, buildKeyShortcuts, clampColumnWidth, clampFocus, clearAllFilters, clearSelection, collapseAllGroups, compareValues, computeAggregates, defineColumnsFromRows, diffSelection, expandAllGroups, filterQueryFor, filterRows, fitColumnsToWidth, getColumnId, groupRowId, groupRows, intentOf, isSameCell, matchesQuery, moveColumnBefore, moveGroupByBefore, movesColumn, movesGroupBy, nextFocusForKey, paginateRows, paginationWindow, resolveCell, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, resolveColumns, resolveDropBefore, resolveKeyboardDropTarget, resolveRowId, resolveRows, resolveShownRows, revertColumnSize, selectAll, selectCell, selectOnly, selectRange, setColumnFilter, sizeColumnToContent, sortDirectionFor, sortPriorityFor, sortRows, toggleCellSelection, toggleColumnSort, toggleGroupExpansion, toggleSelection, totalColumnWidth, withGroupAggregates };
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,21 @@ function clampFocus(focus, rowCount, columnCount) {
|
|
|
98
98
|
)
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
-
function
|
|
101
|
+
function noRowsSkippable() {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
function skipSkippableRows(rowIndex, direction, rowCount, isSkippableRow, fallback) {
|
|
105
|
+
let next = rowIndex;
|
|
106
|
+
while (next >= 0 && next < rowCount && isSkippableRow(next)) {
|
|
107
|
+
const stepped = next + direction;
|
|
108
|
+
if (stepped < HEADER_ROW || stepped >= rowCount) {
|
|
109
|
+
return fallback;
|
|
110
|
+
}
|
|
111
|
+
next = stepped;
|
|
112
|
+
}
|
|
113
|
+
return next;
|
|
114
|
+
}
|
|
115
|
+
function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize, isSkippableRow = noRowsSkippable) {
|
|
102
116
|
if (modifiers.altKey) {
|
|
103
117
|
return null;
|
|
104
118
|
}
|
|
@@ -111,19 +125,64 @@ function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize)
|
|
|
111
125
|
if (modifiers.ctrlKey) return null;
|
|
112
126
|
return { rowIndex, columnIndex: columnIndex + 1 };
|
|
113
127
|
case "ArrowUp":
|
|
114
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
rowIndex: skipSkippableRows(
|
|
130
|
+
rowIndex - 1,
|
|
131
|
+
-1,
|
|
132
|
+
rowCount,
|
|
133
|
+
isSkippableRow,
|
|
134
|
+
rowIndex
|
|
135
|
+
),
|
|
136
|
+
columnIndex
|
|
137
|
+
};
|
|
115
138
|
case "ArrowDown":
|
|
116
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
rowIndex: skipSkippableRows(
|
|
141
|
+
rowIndex + 1,
|
|
142
|
+
1,
|
|
143
|
+
rowCount,
|
|
144
|
+
isSkippableRow,
|
|
145
|
+
rowIndex
|
|
146
|
+
),
|
|
147
|
+
columnIndex
|
|
148
|
+
};
|
|
117
149
|
// Ctrl takes Home and End to the grid's ends rather than the row's, which
|
|
118
150
|
// is the one place Ctrl is navigation rather than reorder.
|
|
119
151
|
case "Home":
|
|
120
152
|
return modifiers.ctrlKey ? { rowIndex: HEADER_ROW, columnIndex: 0 } : { rowIndex, columnIndex: 0 };
|
|
121
153
|
case "End":
|
|
122
|
-
return modifiers.ctrlKey ? {
|
|
154
|
+
return modifiers.ctrlKey ? {
|
|
155
|
+
rowIndex: skipSkippableRows(
|
|
156
|
+
rowCount - 1,
|
|
157
|
+
-1,
|
|
158
|
+
rowCount,
|
|
159
|
+
isSkippableRow,
|
|
160
|
+
rowIndex
|
|
161
|
+
),
|
|
162
|
+
columnIndex: columnCount - 1
|
|
163
|
+
} : { rowIndex, columnIndex: columnCount - 1 };
|
|
123
164
|
case "PageUp":
|
|
124
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
rowIndex: skipSkippableRows(
|
|
167
|
+
rowIndex - pageSize,
|
|
168
|
+
-1,
|
|
169
|
+
rowCount,
|
|
170
|
+
isSkippableRow,
|
|
171
|
+
rowIndex
|
|
172
|
+
),
|
|
173
|
+
columnIndex
|
|
174
|
+
};
|
|
125
175
|
case "PageDown":
|
|
126
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
rowIndex: skipSkippableRows(
|
|
178
|
+
rowIndex + pageSize,
|
|
179
|
+
1,
|
|
180
|
+
rowCount,
|
|
181
|
+
isSkippableRow,
|
|
182
|
+
rowIndex
|
|
183
|
+
),
|
|
184
|
+
columnIndex
|
|
185
|
+
};
|
|
127
186
|
default:
|
|
128
187
|
return null;
|
|
129
188
|
}
|
|
@@ -728,8 +787,14 @@ function buildLevel(rows, levels, parentPath, expansion, byId, out) {
|
|
|
728
787
|
path,
|
|
729
788
|
expanded,
|
|
730
789
|
count: group.rows.length,
|
|
731
|
-
// Renumbered globally once `groupRows` has the whole flattened array
|
|
732
|
-
|
|
790
|
+
// Renumbered globally once `groupRows` has the whole flattened array —
|
|
791
|
+
// a header has no row of its own to carry a dataset position, so
|
|
792
|
+
// `datasetIndex` takes the same renumbered value `rowIndex` does.
|
|
793
|
+
rowIndex: -1,
|
|
794
|
+
datasetIndex: -1,
|
|
795
|
+
// Set by `withGroupAggregates`, downstream of grouping — empty here
|
|
796
|
+
// since `groupRows` itself never knows about `AggregateState`.
|
|
797
|
+
aggregates: /* @__PURE__ */ new Map()
|
|
733
798
|
};
|
|
734
799
|
out.push(header);
|
|
735
800
|
if (expanded) {
|
|
@@ -748,9 +813,12 @@ function groupRows(rows, groupBy, expansion, columns) {
|
|
|
748
813
|
}
|
|
749
814
|
const out = [];
|
|
750
815
|
buildLevel(rows, active, [], expansion, byId, out);
|
|
751
|
-
return out.map(
|
|
752
|
-
(
|
|
753
|
-
|
|
816
|
+
return out.map((entry, index) => {
|
|
817
|
+
if ("kind" in entry) {
|
|
818
|
+
return entry.rowIndex === index && entry.datasetIndex === index ? entry : { ...entry, rowIndex: index, datasetIndex: index };
|
|
819
|
+
}
|
|
820
|
+
return entry.rowIndex === index ? entry : { ...entry, rowIndex: index };
|
|
821
|
+
});
|
|
754
822
|
}
|
|
755
823
|
function toggleGroupExpansion(expansion, groupId) {
|
|
756
824
|
return expansion.includes(groupId) ? expansion.filter((id) => id !== groupId) : [...expansion, groupId];
|
|
@@ -817,6 +885,262 @@ function resolveCell(rowsById, columns, cell) {
|
|
|
817
885
|
value: accessDotted(entry.row, column.column.field)
|
|
818
886
|
};
|
|
819
887
|
}
|
|
888
|
+
|
|
889
|
+
// src/util/paging.ts
|
|
890
|
+
function unitsOf(rows) {
|
|
891
|
+
const units = [];
|
|
892
|
+
let start = 0;
|
|
893
|
+
let openedByGroup = false;
|
|
894
|
+
for (let index = 0; index < rows.length; index++) {
|
|
895
|
+
const entry = rows[index];
|
|
896
|
+
if (entry === void 0) {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const isTopLevelHeader = "kind" in entry && entry.kind === "group" && entry.level === 0;
|
|
900
|
+
const isBoundary = index !== start && (isTopLevelHeader || !openedByGroup);
|
|
901
|
+
if (isBoundary) {
|
|
902
|
+
units.push({ start, end: index });
|
|
903
|
+
start = index;
|
|
904
|
+
openedByGroup = isTopLevelHeader;
|
|
905
|
+
} else if (index === start) {
|
|
906
|
+
openedByGroup = isTopLevelHeader;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (start < rows.length) {
|
|
910
|
+
units.push({ start, end: rows.length });
|
|
911
|
+
}
|
|
912
|
+
return units;
|
|
913
|
+
}
|
|
914
|
+
function paginateRows(rows, pagination) {
|
|
915
|
+
const pageSize = Math.max(1, pagination.pageSize);
|
|
916
|
+
const units = unitsOf(rows);
|
|
917
|
+
const pageCount = Math.ceil(units.length / pageSize);
|
|
918
|
+
if (pageCount === 0) {
|
|
919
|
+
return { rows: [], pageCount: 0, pageIndex: 0 };
|
|
920
|
+
}
|
|
921
|
+
const pageIndex = Math.min(Math.max(pagination.pageIndex, 0), pageCount - 1);
|
|
922
|
+
const firstUnit = units[pageIndex * pageSize];
|
|
923
|
+
const lastUnit = units[Math.min(pageIndex * pageSize + pageSize, units.length) - 1];
|
|
924
|
+
if (firstUnit === void 0 || lastUnit === void 0) {
|
|
925
|
+
return { rows: [], pageCount, pageIndex };
|
|
926
|
+
}
|
|
927
|
+
const page = rows.slice(firstUnit.start, lastUnit.end);
|
|
928
|
+
const renumbered = page.map(
|
|
929
|
+
(entry, index) => entry.rowIndex === index ? entry : { ...entry, rowIndex: index }
|
|
930
|
+
);
|
|
931
|
+
return { rows: renumbered, pageCount, pageIndex };
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/util/paginationWindow.ts
|
|
935
|
+
function paginationWindow(currentPage, pageCount, options) {
|
|
936
|
+
if (pageCount <= 0) {
|
|
937
|
+
return [];
|
|
938
|
+
}
|
|
939
|
+
const boundaryCount = Math.max(0, options?.boundaryCount ?? 1);
|
|
940
|
+
const siblingCount = Math.max(0, options?.siblingCount ?? 1);
|
|
941
|
+
const current = Math.min(Math.max(currentPage, 1), pageCount);
|
|
942
|
+
const pages = /* @__PURE__ */ new Set();
|
|
943
|
+
for (let page = 1; page <= Math.min(boundaryCount, pageCount); page++) {
|
|
944
|
+
pages.add(page);
|
|
945
|
+
}
|
|
946
|
+
for (let page = Math.max(pageCount - boundaryCount + 1, 1); page <= pageCount; page++) {
|
|
947
|
+
pages.add(page);
|
|
948
|
+
}
|
|
949
|
+
for (let page = Math.max(current - siblingCount, 1); page <= Math.min(current + siblingCount, pageCount); page++) {
|
|
950
|
+
pages.add(page);
|
|
951
|
+
}
|
|
952
|
+
const sorted = Array.from(pages).sort((a, b) => a - b);
|
|
953
|
+
const entries = [];
|
|
954
|
+
let previous;
|
|
955
|
+
for (const page of sorted) {
|
|
956
|
+
if (previous !== void 0) {
|
|
957
|
+
if (page - previous === 2) {
|
|
958
|
+
entries.push(previous + 1);
|
|
959
|
+
} else if (page - previous > 2) {
|
|
960
|
+
entries.push("ellipsis");
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
entries.push(page);
|
|
964
|
+
previous = page;
|
|
965
|
+
}
|
|
966
|
+
return entries;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/util/aggregation.ts
|
|
970
|
+
function isEmpty2(value) {
|
|
971
|
+
return value === null || value === void 0;
|
|
972
|
+
}
|
|
973
|
+
function toNumber(value) {
|
|
974
|
+
return Number(value);
|
|
975
|
+
}
|
|
976
|
+
function distinctKey(value) {
|
|
977
|
+
if (isEmpty2(value)) {
|
|
978
|
+
return "\0empty";
|
|
979
|
+
}
|
|
980
|
+
if (value instanceof Date) {
|
|
981
|
+
return `\0date:${String(value.getTime())}`;
|
|
982
|
+
}
|
|
983
|
+
switch (typeof value) {
|
|
984
|
+
case "string":
|
|
985
|
+
case "number":
|
|
986
|
+
case "boolean":
|
|
987
|
+
case "bigint":
|
|
988
|
+
case "symbol":
|
|
989
|
+
return `\0${typeof value}:${String(value)}`;
|
|
990
|
+
case "function":
|
|
991
|
+
return "\0function";
|
|
992
|
+
default:
|
|
993
|
+
return `\0object:${JSON.stringify(value)}`;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function reduceBuiltIn(rows, fn, column) {
|
|
997
|
+
if (fn === "count") {
|
|
998
|
+
return rows.length;
|
|
999
|
+
}
|
|
1000
|
+
const field = column?.column.field;
|
|
1001
|
+
const values = field === void 0 ? [] : rows.map((row) => accessDotted(row, field));
|
|
1002
|
+
switch (fn) {
|
|
1003
|
+
case "sum": {
|
|
1004
|
+
let total = 0;
|
|
1005
|
+
for (const value of values) {
|
|
1006
|
+
if (isEmpty2(value)) continue;
|
|
1007
|
+
total += toNumber(value);
|
|
1008
|
+
}
|
|
1009
|
+
return total;
|
|
1010
|
+
}
|
|
1011
|
+
case "avg": {
|
|
1012
|
+
const present = values.filter((value) => !isEmpty2(value));
|
|
1013
|
+
if (present.length === 0) return void 0;
|
|
1014
|
+
const total = present.reduce(
|
|
1015
|
+
(acc, value) => acc + toNumber(value),
|
|
1016
|
+
0
|
|
1017
|
+
);
|
|
1018
|
+
return total / present.length;
|
|
1019
|
+
}
|
|
1020
|
+
case "min":
|
|
1021
|
+
case "max": {
|
|
1022
|
+
const type = column?.column.type;
|
|
1023
|
+
let result;
|
|
1024
|
+
for (const value of values) {
|
|
1025
|
+
if (isEmpty2(value)) continue;
|
|
1026
|
+
if (result === void 0) {
|
|
1027
|
+
result = value;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
const comparison = compareValues(value, result, type);
|
|
1031
|
+
if (fn === "min" ? comparison < 0 : comparison > 0) {
|
|
1032
|
+
result = value;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return result;
|
|
1036
|
+
}
|
|
1037
|
+
case "countDistinct": {
|
|
1038
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1039
|
+
for (const value of values) {
|
|
1040
|
+
if (isEmpty2(value)) continue;
|
|
1041
|
+
seen.add(distinctKey(value));
|
|
1042
|
+
}
|
|
1043
|
+
return seen.size;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function keyOf(spec) {
|
|
1048
|
+
return spec.id ?? spec.columnId;
|
|
1049
|
+
}
|
|
1050
|
+
function computeAggregates(rows, specs, columns) {
|
|
1051
|
+
const results = /* @__PURE__ */ new Map();
|
|
1052
|
+
if (specs.length === 0) {
|
|
1053
|
+
return results;
|
|
1054
|
+
}
|
|
1055
|
+
const byId = new Map(columns.map((entry) => [entry.id, entry]));
|
|
1056
|
+
for (const spec of specs) {
|
|
1057
|
+
const value = typeof spec.fn === "function" ? spec.fn(rows) : reduceBuiltIn(rows, spec.fn, byId.get(spec.columnId));
|
|
1058
|
+
results.set(keyOf(spec), value);
|
|
1059
|
+
}
|
|
1060
|
+
return results;
|
|
1061
|
+
}
|
|
1062
|
+
function leafDescendantsOf(flatRows, path, groupBy, byId) {
|
|
1063
|
+
const keys = path.map((value) => bucketKey(value));
|
|
1064
|
+
return flatRows.filter(
|
|
1065
|
+
(row) => keys.every((key, level) => {
|
|
1066
|
+
const columnId = groupBy[level]?.columnId;
|
|
1067
|
+
const column = columnId === void 0 ? void 0 : byId.get(columnId);
|
|
1068
|
+
if (column === void 0) return false;
|
|
1069
|
+
return bucketKey(accessDotted(row.row, column.field)) === key;
|
|
1070
|
+
})
|
|
1071
|
+
).map((row) => row.row);
|
|
1072
|
+
}
|
|
1073
|
+
function appendAggregatedSpan(displayRows, start, levelFloor, flatRows, groupBy, byId, specs, columns, display, out) {
|
|
1074
|
+
let index = start;
|
|
1075
|
+
while (index < displayRows.length) {
|
|
1076
|
+
const entry = displayRows[index];
|
|
1077
|
+
if (entry === void 0) {
|
|
1078
|
+
index++;
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
if (levelFloor !== null && "kind" in entry && entry.kind === "group" && entry.level <= levelFloor) {
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
index++;
|
|
1085
|
+
if (!("kind" in entry) || entry.kind !== "group") {
|
|
1086
|
+
out.push(entry);
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
const leaves = leafDescendantsOf(flatRows, entry.path, groupBy, byId);
|
|
1090
|
+
const aggregates = computeAggregates(leaves, specs, columns);
|
|
1091
|
+
const withAggregates = { ...entry, aggregates };
|
|
1092
|
+
out.push(withAggregates);
|
|
1093
|
+
index = appendAggregatedSpan(
|
|
1094
|
+
displayRows,
|
|
1095
|
+
index,
|
|
1096
|
+
entry.level,
|
|
1097
|
+
flatRows,
|
|
1098
|
+
groupBy,
|
|
1099
|
+
byId,
|
|
1100
|
+
specs,
|
|
1101
|
+
columns,
|
|
1102
|
+
display,
|
|
1103
|
+
out
|
|
1104
|
+
);
|
|
1105
|
+
if (display === "row") {
|
|
1106
|
+
const summary = {
|
|
1107
|
+
kind: "group-summary",
|
|
1108
|
+
groupId: entry.groupId,
|
|
1109
|
+
level: entry.level,
|
|
1110
|
+
rowIndex: -1,
|
|
1111
|
+
datasetIndex: -1,
|
|
1112
|
+
aggregates
|
|
1113
|
+
};
|
|
1114
|
+
out.push(summary);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return index;
|
|
1118
|
+
}
|
|
1119
|
+
function withGroupAggregates(displayRows, flatRows, groupBy, specs, columns, display = "inline") {
|
|
1120
|
+
if (specs.length === 0) {
|
|
1121
|
+
return displayRows;
|
|
1122
|
+
}
|
|
1123
|
+
const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
|
|
1124
|
+
const out = [];
|
|
1125
|
+
appendAggregatedSpan(
|
|
1126
|
+
displayRows,
|
|
1127
|
+
0,
|
|
1128
|
+
null,
|
|
1129
|
+
flatRows,
|
|
1130
|
+
groupBy,
|
|
1131
|
+
byId,
|
|
1132
|
+
specs,
|
|
1133
|
+
columns,
|
|
1134
|
+
display,
|
|
1135
|
+
out
|
|
1136
|
+
);
|
|
1137
|
+
return out.map((entry, position) => {
|
|
1138
|
+
if ("kind" in entry) {
|
|
1139
|
+
return entry.rowIndex === position && entry.datasetIndex === position ? entry : { ...entry, rowIndex: position, datasetIndex: position };
|
|
1140
|
+
}
|
|
1141
|
+
return entry.rowIndex === position ? entry : { ...entry, rowIndex: position };
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
820
1144
|
export {
|
|
821
1145
|
DEFAULT_COLUMN_SIZES,
|
|
822
1146
|
HEADER_ROW,
|
|
@@ -834,6 +1158,7 @@ export {
|
|
|
834
1158
|
clearSelection,
|
|
835
1159
|
collapseAllGroups,
|
|
836
1160
|
compareValues,
|
|
1161
|
+
computeAggregates,
|
|
837
1162
|
defineColumnsFromRows,
|
|
838
1163
|
diffSelection,
|
|
839
1164
|
expandAllGroups,
|
|
@@ -851,6 +1176,8 @@ export {
|
|
|
851
1176
|
movesColumn,
|
|
852
1177
|
movesGroupBy,
|
|
853
1178
|
nextFocusForKey,
|
|
1179
|
+
paginateRows,
|
|
1180
|
+
paginationWindow,
|
|
854
1181
|
resolveCell,
|
|
855
1182
|
resolveColumnConstraints,
|
|
856
1183
|
resolveColumnLabel,
|
|
@@ -875,6 +1202,7 @@ export {
|
|
|
875
1202
|
toggleColumnSort,
|
|
876
1203
|
toggleGroupExpansion,
|
|
877
1204
|
toggleSelection,
|
|
878
|
-
totalColumnWidth
|
|
1205
|
+
totalColumnWidth,
|
|
1206
|
+
withGroupAggregates
|
|
879
1207
|
};
|
|
880
1208
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/grid.ts","../src/util/navigation.ts","../src/util/ordering.ts","../src/util/selection.ts","../src/util/sizing.ts","../src/util/sorting.ts","../src/util/filtering.ts","../src/util/grouping.ts","../src/util/rows.ts"],"sourcesContent":["import type { ColumnAlignment, ColumnDefinition, ColumnType } from \"../types\";\n\n/**\n * Narrows to a value worth drilling into. Mirrors the `LeafValue` union in\n * `../types`: arrays and `Date`s are cell values rather than groups of\n * columns, and `null` cannot be introspected. Keep the two in sync.\n */\nfunction isNested(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\nfunction getType(value: unknown): ColumnType {\n if (value instanceof Date) return \"dateTime\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"bigint\") return \"number\";\n if (typeof value === \"symbol\") return \"string\";\n return \"string\";\n}\n\n/**\n * How a column of `type` aligns by default. Numeric types align right so that\n * their digits line up by place value; everything else reads from the left.\n */\nexport function alignmentForType(type: ColumnType): ColumnAlignment {\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n\nfunction getAlignment(value: unknown): ColumnAlignment {\n return alignmentForType(getType(value));\n}\n\n/**\n * Derives one column per field across `rows`, in the order each field is first\n * seen — so a key that only appears in a later row still gets a column,\n * appended at the end. Nested objects contribute their non-object properties\n * as `\"Parent.Child\"`; anything deeper is skipped, as is the nested object\n * itself.\n *\n * Order follows `Object.entries`, which lists integer-like keys first\n * regardless of the order they were written in.\n */\nexport function defineColumnsFromRows<Row>(\n rows: readonly Row[],\n): readonly ColumnDefinition<Row>[] {\n const columns: ColumnDefinition<Row>[] = [];\n const seen = new Set<string>();\n\n function add(field: string, value: unknown): void {\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n columns.push({\n field,\n type: getType(value),\n alignment: getAlignment(value),\n });\n }\n\n for (const row of rows) {\n if (!isNested(row)) {\n continue;\n }\n for (const [key, value] of Object.entries(row)) {\n if (!isNested(value)) {\n add(key, value);\n continue;\n }\n for (const [nestedKey, nestedValue] of Object.entries(value)) {\n if (!isNested(nestedValue)) {\n add(`${key}.${nestedKey}`, nestedValue);\n }\n }\n }\n }\n\n return columns;\n}\n\n/**\n * The key state about a column is stored under, and the key it renders with.\n * Every such lookup goes through here so the two can never disagree.\n */\nexport function getColumnId<Row>(\n column: ColumnDefinition<Row, unknown>,\n): string {\n return column.id ?? column.field;\n}\n\n/**\n * The key state about a row is stored under, and the key it renders with — the\n * row's counterpart to `getColumnId`, and for the same reason: every such\n * lookup goes through here so no two can disagree.\n *\n * Falls back to the position so a grid given no `getRowId` still works. That\n * fallback ties a selection to where a row sits rather than to the row, so\n * anything that sorts, filters or pages its data has to supply one — otherwise\n * a re-sort leaves the same positions selected under different rows.\n */\nexport function resolveRowId<Row>(\n row: Row,\n index: number,\n getRowId?: (row: Row, index: number) => string,\n): string {\n return getRowId?.(row, index) ?? String(index);\n}\n\n/**\n * What a column's header shows: its own `headerTemplate`, called first if it is\n * a function, or else a label read off the field path — `\"Application.Id\"`\n * reading as `\"Application Id\"`.\n */\nexport function resolveColumnLabel<Row, Node>(\n column: ColumnDefinition<Row, Node>,\n): Node | string {\n const { headerTemplate } = column;\n if (headerTemplate === undefined) {\n return column.field.split(\".\").join(\" \");\n }\n // `Node` is open, so it could itself be a function type and the `typeof`\n // check cannot narrow on its own. A header that is callable is the lazy\n // form by definition — no adapter binds `Node` to a function.\n return typeof headerTemplate === \"function\"\n ? (headerTemplate as () => Node)()\n : headerTemplate;\n}\n\n/** Which of a column header's keyboard interactions to announce. */\nexport interface KeyShortcutCapabilities {\n reorderable: boolean;\n resizable: boolean;\n sortable: boolean;\n groupable: boolean;\n}\n\n/**\n * The `aria-keyshortcuts` string for a column header, naming only the keys\n * that column actually responds to — so the keys are discoverable, since\n * none of them is a convention a user would try unprompted.\n */\nexport function buildKeyShortcuts(\n capabilities: KeyShortcutCapabilities,\n): string {\n return [\n capabilities.reorderable ? \"Control+ArrowLeft Control+ArrowRight\" : \"\",\n // Alt+Enter is the keyboard equivalent of double-clicking the resize\n // handle to size the column to its content — same gate as the nudge\n // shortcuts, since both are resize actions.\n capabilities.resizable ? \"Alt+ArrowLeft Alt+ArrowRight Alt+Enter\" : \"\",\n capabilities.sortable ? \"Alt+ArrowUp\" : \"\",\n // Down rather than reusing Up: it reads as \"drill into groups\", and Up is\n // already sort's own mnemonic on the same header.\n capabilities.groupable ? \"Alt+ArrowDown\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\n/**\n * Accesses a value at a dotted path, e.g. `foo.bar.baz`.\n *\n * @param obj The object to access\n * @param path Path to the value, e.g. `\"foo.bar\"`\n * @returns The value at `path`, or `undefined` if it doesn't exist\n */\nexport function accessDotted(obj: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce((value, key) => (isNested(value) ? value[key] : undefined), obj);\n}\n","/**\n * The header's row index. One coordinate space covers the header and the body\n * so that arrowing up out of the first row reaches the header without a case\n * of its own, and every other move is plain arithmetic.\n */\nexport const HEADER_ROW = -1;\n\n/** The cell the grid's single tab stop sits on. */\nexport interface GridFocus {\n readonly rowIndex: number;\n readonly columnIndex: number;\n}\n\n/** The modifier keys held with a navigation key press. */\nexport interface NavigationModifiers {\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n readonly altKey: boolean;\n readonly shiftKey: boolean;\n}\n\n/**\n * Holds a coordinate inside the grid, so that a focus surviving a column\n * removal or a shorter page never leaves the grid with no tab stop at all —\n * which is a grid a keyboard cannot reach.\n */\nexport function clampFocus(\n focus: GridFocus,\n rowCount: number,\n columnCount: number,\n): GridFocus {\n return {\n rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),\n columnIndex: Math.min(\n Math.max(focus.columnIndex, 0),\n Math.max(columnCount - 1, 0),\n ),\n };\n}\n\n/**\n * The focus a navigation key press moves to, or `null` when the key isn't\n * this grid's to handle — left for the browser or another handler.\n *\n * `pageSize` is a pre-measured row count rather than a callback: this\n * function takes plain data only, so a caller measures the viewport once and\n * hands over the number.\n */\nexport function nextFocusForKey(\n key: string,\n modifiers: NavigationModifiers,\n focus: GridFocus,\n rowCount: number,\n columnCount: number,\n pageSize: number,\n): GridFocus | null {\n // Alt on an arrow resizes and Ctrl on one reorders; the header claims both\n // before this runs, and elsewhere they belong to the browser.\n if (modifiers.altKey) {\n return null;\n }\n\n const { rowIndex, columnIndex } = focus;\n\n switch (key) {\n case \"ArrowLeft\":\n if (modifiers.ctrlKey) return null;\n return { rowIndex, columnIndex: columnIndex - 1 };\n case \"ArrowRight\":\n if (modifiers.ctrlKey) return null;\n return { rowIndex, columnIndex: columnIndex + 1 };\n case \"ArrowUp\":\n return { rowIndex: rowIndex - 1, columnIndex };\n case \"ArrowDown\":\n return { rowIndex: rowIndex + 1, columnIndex };\n // Ctrl takes Home and End to the grid's ends rather than the row's, which\n // is the one place Ctrl is navigation rather than reorder.\n case \"Home\":\n return modifiers.ctrlKey\n ? { rowIndex: HEADER_ROW, columnIndex: 0 }\n : { rowIndex, columnIndex: 0 };\n case \"End\":\n return modifiers.ctrlKey\n ? { rowIndex: rowCount - 1, columnIndex: columnCount - 1 }\n : { rowIndex, columnIndex: columnCount - 1 };\n case \"PageUp\":\n return { rowIndex: rowIndex - pageSize, columnIndex };\n case \"PageDown\":\n return { rowIndex: rowIndex + pageSize, columnIndex };\n default:\n return null;\n }\n}\n","import type { ColumnDefinition, ColumnOrderState, DropSide } from \"../types\";\nimport { getColumnId } from \"./grid\";\n\n/**\n * Puts `columns` in the order `order` gives. Ids it lists come first, in that\n * order; columns it omits keep their position among the definitions and\n * follow. Ids naming no column are ignored.\n *\n * Tolerating both gaps is what lets a stored order outlive the column list it\n * was made against.\n */\nexport function applyColumnOrder<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n order: ColumnOrderState,\n): readonly ColumnDefinition<Row, Node>[] {\n if (order.length === 0) {\n return columns;\n }\n\n const byId = new Map(columns.map((column) => [getColumnId(column), column]));\n const listed = order\n .map((id) => byId.get(id))\n .filter((column) => column !== undefined);\n const taken = new Set(listed.map((column) => getColumnId(column)));\n\n return [\n ...listed,\n ...columns.filter((column) => !taken.has(getColumnId(column))),\n ];\n}\n\n/**\n * The order with `movedId` lifted out and put back in front of `beforeId`, or\n * at the end when that is `null`.\n *\n * Returns `orderedIds` itself when the move changes nothing, so callers can\n * treat an unchanged reference as \"no move happened\".\n */\nexport function moveColumnBefore(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): ColumnOrderState {\n if (movedId === beforeId || !orderedIds.includes(movedId)) {\n return orderedIds;\n }\n\n const without = orderedIds.filter((id) => id !== movedId);\n // Taken after the removal: an index read before it would be one too far\n // right for every move that travels rightwards.\n const target = beforeId === null ? without.length : without.indexOf(beforeId);\n if (target === -1) {\n return orderedIds;\n }\n\n const next = [...without];\n next.splice(target, 0, movedId);\n return next.every((id, index) => id === orderedIds[index])\n ? orderedIds\n : next;\n}\n\n/**\n * Whether moving `movedId` in front of `beforeId` would rearrange anything, so\n * that a drop indicator promises exactly the moves that happen.\n *\n * Defined against `moveColumnBefore` rather than restating its conditions, so\n * the two cannot disagree about what a no-op is.\n */\nexport function movesColumn(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): boolean {\n return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;\n}\n\n/**\n * The id a drop on `side` of `targetId` lands in front of, which is `null`\n * past the last column.\n *\n * \"After C\" and \"before D\" name the same gap, so collapsing both to one id\n * keeps a drop target a single nullable string.\n */\nexport function resolveDropBefore(\n orderedIds: ColumnOrderState,\n targetId: string,\n side: DropSide,\n): string | null {\n if (side === \"before\") {\n return targetId;\n }\n const index = orderedIds.indexOf(targetId);\n return index === -1 ? null : (orderedIds[index + 1] ?? null);\n}\n\n/**\n * The `beforeId` a keyboard nudge on `columnId` produces, for `onDrop` to\n * apply the same way a pointer drop does — or `undefined` when the nudge is a\n * no-op: `columnId` is not in `orderedIds`, or the nudge would move it left\n * of the start.\n *\n * `null` is a real answer here, not \"no-op\": it means append to the end, the\n * same convention `moveColumnBefore` uses.\n */\nexport function resolveKeyboardDropTarget(\n orderedIds: ColumnOrderState,\n columnId: string,\n direction: -1 | 1,\n): string | null | undefined {\n const index = orderedIds.indexOf(columnId);\n if (index === -1) {\n return undefined;\n }\n\n /**\n * Moving right lands in front of the id two along: the gap immediately\n * after a column is the one it already sits in.\n */\n const targetIndex = direction === -1 ? index - 1 : index + 2;\n if (targetIndex < 0) {\n return undefined;\n }\n\n return orderedIds[targetIndex] ?? null;\n}\n","import type {\n CellSelectionMode,\n CellSelectionState,\n SelectedCellRef,\n SelectIntent,\n SelectionDiff,\n SelectionMode,\n SelectionState,\n} from \"../types\";\n\n/** Nothing changed, so nothing to report — shared to save an allocation. */\nconst NO_CHANGE: SelectionDiff = { added: [], removed: [] };\n\n/**\n * Whether two selections hold the same ids in the same order.\n *\n * Order counts because the selection's own order is what makes `at(-1)` the\n * most recently selected; a comparison that ignored it would have a transform\n * return the old reference after a reordering that a consumer can observe.\n */\nfunction sameSelection(a: SelectionState, b: SelectionState): boolean {\n return a.length === b.length && a.every((id, index) => id === b[index]);\n}\n\n/**\n * The selection with `id` added if absent and removed if present — a\n * Ctrl-click, or Space on the focused member.\n *\n * The mode is enforced here rather than by each caller, so `\"single\"` cannot\n * accumulate no matter which interaction reaches it. Returns `selection`\n * itself when `mode` is `false`, keeping the unchanged-reference contract\n * `moveColumnBefore` set: a caller treats an unchanged reference as \"nothing\n * happened\" and neither renders nor reports.\n */\nexport function toggleSelection(\n selection: SelectionState,\n id: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode === false) {\n return selection;\n }\n if (selection.includes(id)) {\n return selection.filter((selectedId) => selectedId !== id);\n }\n return mode === \"single\" ? [id] : [...selection, id];\n}\n\n/**\n * The selection reduced to `id` alone — a plain click, which discards what\n * came before rather than adding to it.\n *\n * Never deselects, so clicking the one selected row leaves it selected.\n * Letting go of it is Ctrl-click's job, and `toggleSelection`'s.\n */\nexport function selectOnly(\n selection: SelectionState,\n id: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode === false) {\n return selection;\n }\n return selection.length === 1 && selection[0] === id ? selection : [id];\n}\n\n/**\n * The selection replaced by every id from `anchorId` to `focusId` inclusive — a\n * Shift-click. The two may be given in either order.\n *\n * Spans `orderedIds`, the order as displayed, rather than the selection's own:\n * the range a user drew across the screen is the range they mean, whatever\n * order they clicked its ends in. Degrades to `selectOnly` under `\"single\"`,\n * where a range has nowhere to go.\n */\nexport function selectRange(\n selection: SelectionState,\n orderedIds: readonly string[],\n anchorId: string,\n focusId: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode !== \"multiple\") {\n return selectOnly(selection, focusId, mode);\n }\n\n const anchor = orderedIds.indexOf(anchorId);\n const focus = orderedIds.indexOf(focusId);\n if (anchor === -1 || focus === -1) {\n return selection;\n }\n\n const next = orderedIds.slice(\n Math.min(anchor, focus),\n Math.max(anchor, focus) + 1,\n );\n return sameSelection(selection, next) ? selection : next;\n}\n\n/**\n * Every id in `orderedIds`, or `selection` untouched under any mode but\n * `\"multiple\"` — there is no selecting all of one thing.\n */\nexport function selectAll(\n selection: SelectionState,\n orderedIds: readonly string[],\n mode: SelectionMode,\n): SelectionState {\n if (mode !== \"multiple\") {\n return selection;\n }\n return sameSelection(selection, orderedIds) ? selection : orderedIds;\n}\n\nexport function clearSelection(selection: SelectionState): SelectionState {\n return selection.length === 0 ? selection : [];\n}\n\n/**\n * What one transition added and what it removed.\n *\n * Defined here rather than in an adapter so that every callback reporting a\n * selection change reads one answer, and a select and a deselect fired for the\n * same interaction cannot disagree about which it was.\n */\nexport function diffSelection(\n previous: SelectionState,\n next: SelectionState,\n): SelectionDiff {\n if (previous === next) {\n return NO_CHANGE;\n }\n\n const before = new Set(previous);\n const after = new Set(next);\n return {\n added: next.filter((id) => !before.has(id)),\n removed: previous.filter((id) => !after.has(id)),\n };\n}\n\nexport function isSameCell(\n a: CellSelectionState,\n b: CellSelectionState,\n): boolean {\n if (a === null || b === null) {\n return a === b;\n }\n return a.rowId === b.rowId && a.columnId === b.columnId;\n}\n\n/**\n * The cell selection moved to `cell` — a plain click. Idempotent: clicking the\n * selected cell again leaves it selected, matching `selectOnly`.\n */\nexport function selectCell(\n selection: CellSelectionState,\n cell: SelectedCellRef,\n mode: CellSelectionMode,\n): CellSelectionState {\n if (mode === false || isSameCell(selection, cell)) {\n return selection;\n }\n return cell;\n}\n\n/**\n * The cell selection moved to `cell`, or cleared when it is already there — a\n * Ctrl-click, matching `toggleSelection`.\n */\nexport function toggleCellSelection(\n selection: CellSelectionState,\n cell: SelectedCellRef,\n mode: CellSelectionMode,\n): CellSelectionState {\n if (mode === false) {\n return selection;\n }\n return isSameCell(selection, cell) ? null : cell;\n}\n\n/** The intent a click or key press carries, by the modifiers held with it. */\nexport function intentOf(event: {\n ctrlKey: boolean;\n metaKey: boolean;\n shiftKey: boolean;\n}): SelectIntent {\n if (event.shiftKey) {\n return \"range\";\n }\n // Cmd on a Mac is Ctrl everywhere else, and both mean the same thing here.\n return event.ctrlKey || event.metaKey ? \"toggle\" : \"replace\";\n}\n\n/**\n * The transform an intent asks for. A function rather than a branch inside\n * each caller, so a row and a column cannot come to mean different things by\n * the same click.\n */\nexport function applySelectionIntent(\n selection: SelectionState,\n orderedIds: readonly string[],\n anchorId: string | null,\n id: string,\n intent: SelectIntent,\n mode: SelectionMode,\n): SelectionState {\n switch (intent) {\n case \"toggle\":\n return toggleSelection(selection, id, mode);\n // A range with no anchor yet spans from the id to itself, which is the\n // plain click a first Shift-click may as well be.\n case \"range\":\n return selectRange(selection, orderedIds, anchorId ?? id, id, mode);\n default:\n return selectOnly(selection, id, mode);\n }\n}\n","import type {\n ColumnConstraints,\n ColumnDefinition,\n ColumnResizeSession,\n ColumnResolveOptions,\n ColumnSizeDefaults,\n ColumnSizingState,\n ResolvedColumn,\n} from \"../types\";\nimport { alignmentForType, getColumnId, resolveColumnLabel } from \"./grid\";\n\n/**\n * Applied to any column that does not size itself. The minimum is a width a\n * header stays legible at rather than a measured value — a column dragged\n * narrower than this reads as a rendering fault.\n */\nexport const DEFAULT_COLUMN_SIZES: ColumnSizeDefaults = {\n width: 150,\n minWidth: 40,\n maxWidth: Number.POSITIVE_INFINITY,\n};\n\n/** How far one arrow key press moves a column edge. */\nexport const KEYBOARD_STEP = 10;\n\nfunction withDefaults(\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnSizeDefaults {\n return { ...DEFAULT_COLUMN_SIZES, ...defaults };\n}\n\n/**\n * The bounds `column` may be sized between. Resolved in one place so that a\n * drag, a clamp and an auto-fit cannot disagree about a column's limits.\n */\nexport function resolveColumnConstraints<Row>(\n column: ColumnDefinition<Row, unknown>,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnConstraints {\n const resolved = withDefaults(defaults);\n return {\n minWidth: column.minWidth ?? resolved.minWidth,\n maxWidth: column.maxWidth ?? resolved.maxWidth,\n };\n}\n\n/** Holds `width` within `constraints`. */\nexport function clampColumnWidth(\n width: number,\n constraints: ColumnConstraints,\n): number {\n return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);\n}\n\n/**\n * Pairs each column with everything needed to render it — the width it sits\n * at, the label its header shows, whether it may be resized and how its cells\n * align. Widths take the first of the sizing state, the column's own `width`,\n * and the default, then clamp.\n *\n * The sizing state winning means a user's drag outlives a re-render; a column\n * absent from it still tracks a `width` edited in the definition.\n */\nexport function resolveColumnWidths<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n sizing: ColumnSizingState,\n options?: ColumnResolveOptions,\n): readonly ResolvedColumn<Row, Node>[] {\n const resolved = withDefaults(options?.sizes);\n\n return columns.map((column) => {\n const id = getColumnId(column);\n const stored = sizing[id];\n\n return {\n column,\n id,\n sized: stored !== undefined,\n width: clampColumnWidth(\n stored ?? column.width ?? resolved.width,\n resolveColumnConstraints(column, resolved),\n ),\n label: resolveColumnLabel(column),\n resizable: column.resizable ?? options?.resizable ?? false,\n reorderable: column.reorderable ?? options?.reorderable ?? false,\n groupByDraggable:\n column.groupByDraggable ?? options?.groupByDraggable ?? false,\n alignment: column.alignment ?? alignmentForType(column.type ?? \"string\"),\n };\n });\n}\n\nexport function totalColumnWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n): number {\n return resolved.reduce((total, entry) => total + entry.width, 0);\n}\n\n/**\n * Opens a resize. The session captures its constraints and starting point up\n * front, so applying a pointer position later is arithmetic on numbers alone —\n * which is what keeps the drag testable without a DOM.\n *\n * @param startPosition Where the pointer went down, on the axis being dragged.\n */\nexport function beginColumnResize<Row>(\n column: ColumnDefinition<Row, unknown>,\n startWidth: number,\n startPosition: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnResizeSession {\n return {\n columnId: getColumnId(column),\n startWidth,\n startPosition,\n constraints: resolveColumnConstraints(column, defaults),\n };\n}\n\n/**\n * The width the dragged column takes with the pointer at `position`. The delta\n * is signed, so a right-to-left adapter negates it and nothing else changes.\n */\nexport function applyColumnResize(\n session: ColumnResizeSession,\n position: number,\n): number {\n return clampColumnWidth(\n session.startWidth + (position - session.startPosition),\n session.constraints,\n );\n}\n\ninterface SizingEntry<Row, Node> {\n entry: ResolvedColumn<Row, Node>;\n width: number;\n bound: number;\n adjustable: boolean;\n}\n\n/**\n * Whether `item` has reached `bound` in the direction it moves toward — above\n * it for a column growing to a `maxWidth`, below it for one shrinking to a\n * `minWidth`.\n */\nfunction reachedBound<Row, Node>(\n width: number,\n item: SizingEntry<Row, Node>,\n): boolean {\n return item.bound > item.width ? width >= item.bound : width <= item.bound;\n}\n\n/**\n * Moves every adjustable entry's `width` toward `bound` by its proportional\n * share of `remaining`'s current surplus or deficit, looping so a column\n * clamped at its bound in one pass frees the remainder for the others to\n * absorb in the next.\n */\nfunction redistribute<Row, Node>(\n entries: readonly SizingEntry<Row, Node>[],\n remaining: (sum: number) => number,\n): void {\n for (;;) {\n const movable = entries.filter(\n (item) => item.adjustable && !reachedBound(item.width, item),\n );\n const left = remaining(entries.reduce((t, i) => t + i.width, 0));\n if (left <= 0 || movable.length === 0) {\n break;\n }\n\n const base = movable.reduce((total, item) => total + item.width, 0);\n let clamped = false;\n\n for (const item of movable) {\n const share =\n base === 0 ? left / movable.length : (left * item.width) / base;\n const next = item.width + (item.bound > item.width ? share : -share);\n\n if (reachedBound(next, item)) {\n item.width = item.bound;\n clamped = true;\n } else {\n item.width = next;\n }\n }\n\n if (!clamped) {\n break;\n }\n }\n}\n\n/**\n * Fits columns to `availableWidth`: grows them to fill leftover space, or\n * shrinks them to relieve an overflow, in both cases proportionally to each\n * column's current width. Columns the user has sized are left alone in\n * either direction, so dragging one column neither rubber-bands it back nor\n * has it absorb a squeeze meant for the rest.\n *\n * Growing stops at each column's `maxWidth`; shrinking stops at its\n * `minWidth`. If shrinking every adjustable column to its floor still does\n * not reach `availableWidth`, the columns are returned untouched and the\n * container scrolls rather than crushing them further.\n */\nexport function fitColumnsToWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n availableWidth: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): readonly ResolvedColumn<Row, Node>[] {\n const target = Math.floor(availableWidth);\n const total = totalColumnWidth(resolved);\n if (resolved.length === 0 || total === target) {\n return resolved;\n }\n\n const growing = total < target;\n const entries: SizingEntry<Row, Node>[] = resolved.map((entry) => {\n const constraints = resolveColumnConstraints(entry.column, defaults);\n return {\n entry,\n width: entry.width,\n bound: growing ? constraints.maxWidth : constraints.minWidth,\n adjustable: !entry.sized,\n };\n });\n\n if (!growing) {\n // Only unsized columns can be shrunk, so if they alone cannot reach the\n // target the grid genuinely cannot fit and should scroll, unchanged.\n const floor = entries.reduce(\n (sum, item) => sum + (item.adjustable ? item.bound : item.width),\n 0,\n );\n if (floor > target) {\n return resolved;\n }\n }\n\n redistribute(entries, (sum) => (growing ? target - sum : sum - target));\n\n // Fractional widths would leave the table a sub-pixel off its container and\n // show a scrollbar that never goes away, so round every column toward the\n // target and hand the remaining pixels back out (growing) or claw them\n // back (shrinking) one at a time.\n const rounded = entries.map((item) =>\n growing ? Math.floor(item.width) : Math.ceil(item.width),\n );\n let remainder = target - rounded.reduce((total, width) => total + width, 0);\n\n return entries.map((item, index) => {\n let width = rounded[index] ?? item.width;\n // Rounding toward the target can only ever leave it short when growing\n // (floor) or over when shrinking (ceil), so only one direction is ever\n // live here — but each still needs room left before its own bound.\n const hasRoom = growing ? width < item.bound : width > item.bound;\n\n if (remainder !== 0 && item.adjustable && hasRoom) {\n width += growing ? 1 : -1;\n remainder += growing ? -1 : 1;\n }\n\n return { ...item.entry, width };\n });\n}\n\n/**\n * The width a column needs to show `measuredWidth` of content.\n *\n * `padding` is an allowance on top of the measurement, since a column sized to\n * exactly its content can still round down into an ellipsis.\n */\nexport function sizeColumnToContent(\n measuredWidth: number,\n constraints: ColumnConstraints,\n padding = 2,\n): number {\n return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);\n}\n\n/**\n * `current` with `columnId` reverted to its width in `base` — or omitted\n * entirely if `base` had none — for Escape to restore a resize in progress.\n *\n * A column with no stored width in `base` is correctly omitted by deleting\n * the key rather than merging its `base` width back in, which would\n * otherwise leave it pinned and hidden from auto-fit.\n */\nexport function revertColumnSize(\n current: ColumnSizingState,\n base: ColumnSizingState,\n columnId: string,\n): ColumnSizingState {\n const next = Object.fromEntries(\n Object.entries(current).filter(([id]) => id !== columnId),\n );\n if (columnId in base) {\n next[columnId] = base[columnId] as number;\n }\n return next;\n}\n","import type {\n ColumnSortState,\n ColumnType,\n ResolvedColumn,\n ResolvedRow,\n SortDirection,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/** A date/time value converted to a comparable instant. */\nfunction toTime(value: unknown): number {\n return value instanceof Date\n ? value.getTime()\n : new Date(String(value)).getTime();\n}\n\n/**\n * Compares two cell values the way `type` implies — numerically for the\n * numeric types, chronologically for the date/time types, lexically\n * otherwise. Defaults to string comparison when `type` is absent, mirroring\n * `ColumnDefinition.type`'s own default.\n *\n * `null`/`undefined` sort after every value on both sides. Used directly,\n * that ordering is fixed; `sortRows` applies it directionally without\n * disturbing it — see its own doc comment.\n */\nexport function compareValues(\n a: unknown,\n b: unknown,\n type?: ColumnType,\n): number {\n if (isEmpty(a)) {\n return isEmpty(b) ? 0 : 1;\n }\n if (isEmpty(b)) {\n return -1;\n }\n\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return Number(a) - Number(b);\n case \"boolean\":\n return Number(Boolean(a)) - Number(Boolean(b));\n case \"date\":\n case \"dateTime\":\n case \"time\":\n return toTime(a) - toTime(b);\n default:\n return String(a).localeCompare(String(b));\n }\n}\n\n/** This column's direction in `sort`, or `null` outside it. */\nexport function sortDirectionFor(\n sort: ColumnSortState,\n columnId: string,\n): SortDirection | null {\n return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;\n}\n\n/** This column's 1-based place in `sort`, or `null` outside it. */\nexport function sortPriorityFor(\n sort: ColumnSortState,\n columnId: string,\n): number | null {\n const index = sort.findIndex((entry) => entry.columnId === columnId);\n return index === -1 ? null : index + 1;\n}\n\n/**\n * The sort with `columnId` cycled to its next direction — none → asc → desc →\n * none.\n *\n * `stack: false` (a plain click) means \"sort by only this column\": unless\n * `columnId` is already the sole entry, in which case its own cycle just\n * continues, it collapses `sort` down to `columnId` alone at `\"asc\"`,\n * regardless of any direction it held as part of a larger stack.\n *\n * `stack: true` (a Shift-click) cycles or appends `columnId` in place without\n * disturbing the rest of the stack; cycling it back to `\"none\"` removes it\n * and closes the gap, leaving the others in their existing order.\n */\nexport function toggleColumnSort(\n sort: ColumnSortState,\n columnId: string,\n options: { readonly stack: boolean },\n): ColumnSortState {\n const index = sort.findIndex((entry) => entry.columnId === columnId);\n const isSoleEntry = sort.length === 1 && index === 0;\n\n if (!options.stack && !isSoleEntry) {\n return [{ columnId, direction: \"asc\" }];\n }\n\n const current = index === -1 ? undefined : sort[index]?.direction;\n const next: SortDirection | null =\n current === undefined ? \"asc\" : current === \"asc\" ? \"desc\" : null;\n\n if (next === null) {\n return sort.filter((entry) => entry.columnId !== columnId);\n }\n if (index === -1) {\n return [...sort, { columnId, direction: next }];\n }\n return sort.map((entry, i) =>\n i === index ? { columnId, direction: next } : entry,\n );\n}\n\n/**\n * `rows` ordered by `sort` — each entry read against `columns` for its\n * `field` and `type` — with `rowIndex` renumbered to match the new\n * positions, the invariant the rest of the grid relies on for keyboard\n * navigation and `aria-rowindex`.\n *\n * Stable (`Array.prototype.sort` has been stable since ES2019), so rows\n * equal under every active key keep their relative order. A `null`/\n * `undefined` value sorts last under either direction — resolved before\n * `compareValues` is asked, and left out of the direction flip below, so\n * that toggling a column reorders the values it holds rather than moving its\n * gaps to the front.\n *\n * Returns `rows` itself, untouched, when `sort` is empty or names only\n * columns absent from `columns` — a stored sort tolerates a column removed\n * from the definition the same way a stored `ColumnOrderState` does.\n */\nexport function sortRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n sort: ColumnSortState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n if (sort.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = sort.filter((entry) => byId.has(entry.columnId));\n if (active.length === 0) {\n return rows;\n }\n\n const sorted = [...rows].sort((a, b) => {\n for (const { columnId, direction } of active) {\n const column = byId.get(columnId);\n if (column === undefined) {\n continue;\n }\n const left = accessDotted(a.row, column.field);\n const right = accessDotted(b.row, column.field);\n const leftEmpty = isEmpty(left);\n const rightEmpty = isEmpty(right);\n if (leftEmpty || rightEmpty) {\n if (leftEmpty && rightEmpty) {\n continue;\n }\n return leftEmpty ? 1 : -1;\n }\n\n const result = compareValues(left, right, column.type);\n if (result !== 0) {\n return direction === \"asc\" ? result : -result;\n }\n }\n return 0;\n });\n\n return sorted.map((row, index) =>\n row.rowIndex === index ? row : { ...row, rowIndex: index },\n );\n}\n","import type {\n ColumnDefinition,\n ColumnType,\n FieldPath,\n FilterEntry,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n TextFilterEntry,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\n\ninterface FilterPattern {\n mode: \"exact\" | \"contains\" | \"startsWith\" | \"endsWith\";\n text: string; // anchors stripped, lowercased once\n}\n\n/**\n * Reads a query's leading/trailing `%` into a match mode — see\n * `TextFilterEntry`'s own doc comment for the four shapes. Only the first\n * and last characters are read as anchors — a `%` anywhere else in the\n * query is a literal character, not a wildcard.\n */\nfunction parseFilterPattern(query: string): FilterPattern {\n const start = query.startsWith(\"%\");\n const end = query.endsWith(\"%\");\n let text = query;\n if (start) {\n text = text.slice(1);\n }\n if (end) {\n text = text.slice(0, -1);\n }\n text = text.toLowerCase();\n\n if (start && end) {\n return { mode: \"contains\", text };\n }\n if (start) {\n return { mode: \"endsWith\", text };\n }\n if (end) {\n return { mode: \"startsWith\", text };\n }\n return { mode: \"exact\", text };\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/**\n * `parseFilterPattern`, compiled to a reusable test — parsed once, reused\n * per row, unlike a per-call `matchesQuery`.\n */\nfunction compileFilterQuery(query: string): (value: unknown) => boolean {\n const { mode, text } = parseFilterPattern(query);\n return (value: unknown) => {\n const candidate = (isEmptyValue(value) ? \"\" : String(value)).toLowerCase();\n switch (mode) {\n case \"exact\":\n return candidate === text;\n case \"contains\":\n return candidate.includes(text);\n case \"startsWith\":\n return candidate.startsWith(text);\n case \"endsWith\":\n return candidate.endsWith(text);\n }\n };\n}\n\n/**\n * Whether `target` (a `ValueFilterEntry`'s value) equals `value` under\n * `type` — `false` whenever `type` doesn't agree with `target`'s own kind,\n * so a number filter never silently matches a string column.\n */\nfunction matchesTypedValue(\n value: unknown,\n target: number | boolean | Date,\n type: ColumnType | undefined,\n): boolean {\n if (typeof target === \"number\") {\n if (\n type !== \"number\" &&\n type !== \"decimal\" &&\n type !== \"currency\" &&\n type !== \"percent\"\n ) {\n return false;\n }\n return typeof value === \"number\" && value === target;\n }\n if (typeof target === \"boolean\") {\n if (type !== \"boolean\") {\n return false;\n }\n return typeof value === \"boolean\" && value === target;\n }\n if (type !== \"date\" && type !== \"dateTime\" && type !== \"time\") {\n return false;\n }\n return value instanceof Date && value.getTime() === target.getTime();\n}\n\n/**\n * Whether a non-group entry names a column present in `byId` (or names\n * none at all) — lets a stored filter tolerate a column removed from the\n * definition the same way a stored `ColumnSortState` does, by dropping only\n * the entries that reference it.\n *\n * A `GroupFilterEntry` is always usable, regardless of its own contents:\n * its vacuous truth value under an empty or fully-unusable `entries` list\n * depends on its own `combinator` (`\"and\"` vacuously matches everything,\n * `\"or\"` vacuously matches nothing) — a group can't be dropped from its\n * parent's list the way an unusable simple entry can, since whether\n * dropping it would be safe depends on the *parent's* combinator too,\n * which this check has no way to know. Compiling it and letting `.every()`/\n * `.some()` resolve the vacuous case naturally is what stays correct in\n * every context; deciding upfront here is not.\n */\nfunction isEntryUsable<Row>(\n entry: FilterEntry<Row>,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n): boolean {\n if (\"combinator\" in entry) {\n return true;\n }\n return entry.columnId === undefined || byId.has(entry.columnId);\n}\n\n/**\n * One compiled test per usable `FilterEntry`, closing over its column\n * lookup so `filterRows`'s row loop is just `.every(test)`.\n */\nfunction compileFilterEntry<Row>(\n entry: FilterEntry<Row>,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): (row: ResolvedRow<Row>) => boolean {\n if (\"combinator\" in entry) {\n const tests = entry.entries\n .filter((nested) => isEntryUsable(nested, byId))\n .map((nested) => compileFilterEntry(nested, byId, columns));\n return entry.combinator === \"and\"\n ? (row) => tests.every((test) => test(row))\n : (row) => tests.some((test) => test(row));\n }\n\n if (\"predicate\" in entry) {\n const { columnId, predicate } = entry;\n const column = columnId === undefined ? undefined : byId.get(columnId);\n return (row) =>\n predicate(\n column === undefined ? undefined : accessDotted(row.row, column.field),\n row.row,\n );\n }\n\n if (\"value\" in entry) {\n const { columnId, value } = entry;\n if (columnId !== undefined) {\n const column = byId.get(columnId);\n if (column === undefined) {\n return () => false;\n }\n return (row) =>\n matchesTypedValue(\n accessDotted(row.row, column.field),\n value,\n column.type,\n );\n }\n return (row) =>\n columns.some((entry) =>\n matchesTypedValue(\n accessDotted(row.row, entry.column.field),\n value,\n entry.column.type,\n ),\n );\n }\n\n const { columnId, query } = entry;\n const test = compileFilterQuery(query);\n if (columnId !== undefined) {\n const column = byId.get(columnId);\n if (column === undefined) {\n return () => false;\n }\n return (row) => test(accessDotted(row.row, column.field));\n }\n return (row) =>\n columns.some((entry) => test(accessDotted(row.row, entry.column.field)));\n}\n\n/**\n * Whether `value`, stringified, matches `query`'s SQL LIKE-style pattern —\n * case-insensitive always. A bare query is an exact match; `%text%` is\n * contains; `text%` is starts-with; `%text` is ends-with. `null`/`undefined`\n * stringify to `\"\"`.\n *\n * A one-shot convenience: parses `query` fresh on every call. `filterRows`\n * does not call this per cell — it compiles each `TextFilterEntry` once,\n * outside its row loop.\n */\nexport function matchesQuery(value: unknown, query: string): boolean {\n return compileFilterQuery(query)(value);\n}\n\n/**\n * This column's current text query, or the global entry's when `columnId`\n * is omitted; `null` if no `TextFilterEntry` names it — including when the\n * entry that exists for that key is a `ValueFilterEntry`/\n * `PredicateFilterEntry`/`GroupFilterEntry` instead, since none of those\n * hold a query string to report.\n */\nexport function filterQueryFor<Row>(\n filter: FilterState<Row>,\n columnId?: FieldPath<Row> | (string & {}),\n): string | null {\n const entry = filter.find(\n (candidate): candidate is TextFilterEntry<Row> =>\n \"query\" in candidate && candidate.columnId === columnId,\n );\n return entry?.query ?? null;\n}\n\n/**\n * The filter with the `TextFilterEntry` for `columnId` (or the global\n * entry, when `columnId` is omitted) set to `query` — replacing whatever\n * entry already holds that key, of any variant, if present; appending a new\n * `TextFilterEntry` if not. An empty `query` removes the entry instead of\n * storing a filter that can never usefully match.\n *\n * Returns `filter` itself, unchanged, when the result would be a no-op: an\n * empty `query` naming a key with no existing entry, or a `query` equal to\n * an existing `TextFilterEntry`'s.\n */\nexport function setColumnFilter<Row>(\n filter: FilterState<Row>,\n columnId: (FieldPath<Row> | (string & {})) | undefined,\n query: string,\n): FilterState<Row> {\n const index = filter.findIndex(\n (entry) => !(\"combinator\" in entry) && entry.columnId === columnId,\n );\n\n if (query === \"\") {\n return index === -1 ? filter : filter.filter((_entry, i) => i !== index);\n }\n\n const next: TextFilterEntry<Row> =\n columnId === undefined ? { query } : { columnId, query };\n\n if (index === -1) {\n return [...filter, next];\n }\n const existing = filter[index];\n if (\n existing !== undefined &&\n \"query\" in existing &&\n existing.query === query\n ) {\n return filter;\n }\n return filter.map((entry, i) => (i === index ? next : entry));\n}\n\n/** `filter` reduced to empty. Returns `filter` itself when already empty. */\nexport function clearAllFilters<Row>(\n filter: FilterState<Row>,\n): FilterState<Row> {\n return filter.length === 0 ? filter : [];\n}\n\n/**\n * `rows` reduced to those matching every entry in `filter` (AND at the top\n * level — nest a `GroupFilterEntry` for OR). `rowIndex` is renumbered to\n * match the retained positions — the same invariant `sortRows` keeps,\n * needed here independently since `filterRows` runs before `sortRows` in\n * the composed pipeline (see `resolveShownRows`).\n *\n * Order-preserving: surviving rows keep their relative order, which is what\n * makes running this before a sort produce the identical final order a\n * sort-then-filter would — only the cost differs.\n *\n * Every entry is compiled once, before the row loop.\n *\n * Returns `rows` itself, untouched, when `filter` is empty or names only\n * columns absent from `columns` — a stored filter tolerates a column\n * removed from the definition the same way a stored `ColumnSortState` does.\n */\nexport function filterRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n filter: FilterState<Row>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n if (filter.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = filter.filter((entry) => isEntryUsable(entry, byId));\n if (active.length === 0) {\n return rows;\n }\n\n const compiled = active.map((entry) =>\n compileFilterEntry(entry, byId, columns),\n );\n const filtered = rows.filter((row) => compiled.every((test) => test(row)));\n\n return filtered.map((row, index) =>\n row.rowIndex === index ? row : { ...row, rowIndex: index },\n );\n}\n","import type {\n ColumnDefinition,\n DisplayRow,\n GroupByEntry,\n GroupByState,\n GroupExpansionState,\n ResolvedColumn,\n ResolvedGroupRow,\n ResolvedRow,\n SortDirection,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\nimport { compareValues } from \"./sorting\";\n\n/**\n * This group's id, from its path. Every group-id producer/consumer in this\n * package goes through this — never join `path` by hand elsewhere.\n *\n * `JSON.stringify` rather than a delimiter join: a delimiter would have to be\n * escaped in every segment to stay collision-free against a value that\n * legitimately contains it, and `JSON.stringify` already draws that\n * boundary between array entries for free.\n */\nexport function groupRowId(path: readonly unknown[]): string {\n return JSON.stringify(path);\n}\n\ninterface ValueGroup<Row> {\n value: unknown;\n rows: ResolvedRow<Row>[];\n}\n\n/**\n * A key `accessDotted`'s result can be bucketed by, distinguishing values\n * that `compareValues`/`===` would treat as equal-but-not-identical (two\n * `Date` instances for the same instant) while keeping values of different\n * types apart (the number `1` and the string `\"1\"`).\n */\nfunction bucketKey(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\\0empty\";\n }\n if (value instanceof Date) {\n return `\\0date:${String(value.getTime())}`;\n }\n switch (typeof value) {\n case \"string\":\n case \"number\":\n case \"boolean\":\n case \"bigint\":\n case \"symbol\":\n return `\\0${typeof value}:${String(value)}`;\n case \"function\":\n return \"\\0function\";\n default:\n // Arrays, and anything else `accessDotted` could return.\n return `\\0object:${JSON.stringify(value)}`;\n }\n}\n\n/**\n * `rows` bucketed by `column`'s value, one bucket per distinct value in\n * first-seen order, then ordered by `direction` via `compareValues` — the\n * same comparator `sortRows` uses, so a grouped column and a sorted column\n * order their values identically.\n */\nfunction partitionByValue<Row>(\n rows: readonly ResolvedRow<Row>[],\n column: ColumnDefinition<Row, unknown>,\n direction: SortDirection,\n): readonly ValueGroup<Row>[] {\n const buckets = new Map<string, ValueGroup<Row>>();\n for (const row of rows) {\n const value = accessDotted(row.row, column.field);\n const key = bucketKey(value);\n const bucket = buckets.get(key);\n if (bucket === undefined) {\n buckets.set(key, { value, rows: [row] });\n } else {\n bucket.rows.push(row);\n }\n }\n\n const groups = [...buckets.values()];\n groups.sort((a, b) => {\n const result = compareValues(a.value, b.value, column.type);\n return direction === \"asc\" ? result : -result;\n });\n return groups;\n}\n\n/**\n * Depth-first builder behind `groupRows`: partitions `rows` by `levels[0]`,\n * appends a header per resulting group, and recurses into `levels.slice(1)`\n * for each — unless that group is collapsed, in which case only its header\n * is appended. `rows` with no levels left (either `levels` started empty, or\n * recursion exhausted it) are appended as plain data rows, keeping the\n * relative order filter/sort already gave them.\n */\nfunction buildLevel<Row>(\n rows: readonly ResolvedRow<Row>[],\n levels: readonly GroupByEntry[],\n parentPath: readonly unknown[],\n expansion: GroupExpansionState,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n out: DisplayRow<Row>[],\n): void {\n const level = levels[0];\n if (level === undefined) {\n out.push(...rows);\n return;\n }\n\n // `levels` is pre-filtered to entries naming a column present in `byId`\n // (see `groupRows`), so this is always found.\n const column = byId.get(level.columnId);\n if (column === undefined) {\n out.push(...rows);\n return;\n }\n\n const rest = levels.slice(1);\n const groups = partitionByValue(rows, column, level.direction ?? \"asc\");\n\n for (const group of groups) {\n const path = [...parentPath, group.value];\n const groupId = groupRowId(path);\n const expanded = !expansion.includes(groupId);\n const header: ResolvedGroupRow = {\n kind: \"group\",\n groupId,\n level: parentPath.length,\n columnId: level.columnId,\n value: group.value,\n path,\n expanded,\n count: group.rows.length,\n // Renumbered globally once `groupRows` has the whole flattened array.\n rowIndex: -1,\n };\n out.push(header);\n if (expanded) {\n buildLevel(group.rows, rest, path, expansion, byId, out);\n }\n }\n}\n\n/**\n * `rows`, regrouped into `groupBy`'s stacked levels — a group header per\n * distinct value at each level, ordered by `compareValues` per that level's\n * own `direction`, followed depth-first by its nested groups and/or data\n * rows. A collapsed group (its `groupId` present in `expansion`) contributes\n * only its own header, nothing beneath it — but its `count` still reports\n * the group's full leaf-row count either way.\n *\n * Filter and sort are expected to have already run (see `resolveShownRows`):\n * this only reshapes their output, and never changes which rows appear or\n * their relative order within a group.\n *\n * `rowIndex` is renumbered across the whole flattened output, header rows\n * included — the same invariant `sortRows`/`filterRows` keep for\n * `ResolvedRow.rowIndex`, extended to `ResolvedGroupRow.rowIndex` since\n * `GridBody` addresses both kinds by the same flat position.\n *\n * Returns `rows` itself, untouched, when `groupBy` is empty or names only\n * columns absent from `columns` — a stored grouping tolerates a column\n * removed from the definition the same way a stored `ColumnSortState` does.\n */\nexport function groupRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n groupBy: GroupByState,\n expansion: GroupExpansionState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly DisplayRow<Row>[] {\n if (groupBy.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = groupBy.filter((entry) => byId.has(entry.columnId));\n if (active.length === 0) {\n return rows;\n }\n\n const out: DisplayRow<Row>[] = [];\n buildLevel(rows, active, [], expansion, byId, out);\n\n return out.map((entry, index) =>\n entry.rowIndex === index ? entry : { ...entry, rowIndex: index },\n );\n}\n\n/**\n * The expansion with `groupId` collapsed if it was expanded, or expanded if\n * it was collapsed.\n */\nexport function toggleGroupExpansion(\n expansion: GroupExpansionState,\n groupId: string,\n): GroupExpansionState {\n return expansion.includes(groupId)\n ? expansion.filter((id) => id !== groupId)\n : [...expansion, groupId];\n}\n\n/** `expansion` reduced to empty — every group expanded. Mirrors `clearAllFilters`. */\nexport function expandAllGroups(\n expansion: GroupExpansionState,\n): GroupExpansionState {\n return expansion.length === 0 ? expansion : [];\n}\n\n/**\n * Every group id present in `rows` — for collapsing every group currently\n * shown in one call. Operates on the already-grouped display, not the raw\n * grouping, so a group hidden beneath an already-collapsed ancestor (and so\n * absent from `rows`) keeps whatever expansion it already had rather than\n * being forced closed too; expanding that ancestor again reveals it exactly\n * as it was left.\n */\nexport function collapseAllGroups<Row>(\n rows: readonly DisplayRow<Row>[],\n): GroupExpansionState {\n return rows\n .filter((entry): entry is ResolvedGroupRow => \"kind\" in entry)\n .map((entry) => entry.groupId);\n}\n\n/**\n * `groupBy` with `columnId` positioned in front of `beforeColumnId` — or at\n * the end when that is `null` — inserting a fresh entry if `columnId` isn't\n * already part of the stack, or lifting its existing entry out and back in\n * if it is. The one function both a header dropped on the group-by bar and\n * a chip dragged within it need: the former's `columnId` is usually new to\n * the stack, the latter's never is, and this doesn't need to be told which.\n *\n * An existing entry keeps its own object (and so its own `direction`)\n * rather than being rebuilt, so a level merely repositioned stays\n * reference-equal at its new index — the same reason `moveColumnBefore`\n * moves ids rather than rebuilding them.\n *\n * Returns `groupBy` itself, untouched, when the move changes nothing: a\n * drop on the entry's own id, or back into the gap it already occupies, or\n * naming a `beforeColumnId` absent from the stack. Inserting a column not\n * yet in the stack is never a no-op.\n */\nexport function moveGroupByBefore(\n groupBy: GroupByState,\n columnId: string,\n beforeColumnId: string | null,\n): GroupByState {\n if (columnId === beforeColumnId) {\n return groupBy;\n }\n\n const without = groupBy.filter((entry) => entry.columnId !== columnId);\n const moved = groupBy.find((entry) => entry.columnId === columnId) ?? {\n columnId,\n };\n const target =\n beforeColumnId === null\n ? without.length\n : without.findIndex((entry) => entry.columnId === beforeColumnId);\n if (target === -1) {\n return groupBy;\n }\n\n const next = [...without];\n next.splice(target, 0, moved);\n return next.length === groupBy.length &&\n next.every((entry, index) => entry === groupBy[index])\n ? groupBy\n : next;\n}\n\n/**\n * Whether moving `columnId` in front of `beforeColumnId` would change\n * `groupBy` at all, so that a drop indicator promises exactly the move that\n * happens. Defined against `moveGroupByBefore` rather than restating its\n * conditions, the same relationship `movesColumn` keeps with\n * `moveColumnBefore`.\n */\nexport function movesGroupBy(\n groupBy: GroupByState,\n columnId: string,\n beforeColumnId: string | null,\n): boolean {\n return moveGroupByBefore(groupBy, columnId, beforeColumnId) !== groupBy;\n}\n","import type {\n ColumnSortState,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n SelectedCell,\n SelectedCellRef,\n SelectedColumn,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\nimport { filterRows } from \"./filtering\";\nimport { sortRows } from \"./sorting\";\n\n/**\n * `rows`, filtered then sorted — the one function `DataGrid.tsx` (and any\n * future framework binding) calls instead of composing `filterRows`/\n * `sortRows` itself.\n *\n * Filter always runs first: an O(n) pass that shrinks what the O(n log n)\n * sort has to touch whenever a filter is active, free when it isn't. This\n * is a performance choice, not a correctness one — filtering is an\n * order-preserving predicate and sort is a stable total order over whatever\n * it's handed, so the final order is identical either way.\n */\nexport function resolveShownRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n filter: FilterState<Row>,\n sort: ColumnSortState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n return sortRows(filterRows(rows, filter, columns), sort, columns);\n}\n\n/**\n * `ids` resolved back to the rows behind them, keyed by `rowsById`.\n *\n * An id with no row behind it is dropped rather than left `undefined`: a\n * selection outlives a change to the data, so that filtering a row out and\n * back leaves it selected, and only what still exists can be handed to a\n * callback.\n */\nexport function resolveRows<Row>(\n rowsById: ReadonlyMap<string, ResolvedRow<Row>>,\n ids: readonly string[],\n): readonly ResolvedRow<Row>[] {\n return ids.map((id) => rowsById.get(id)).filter((row) => row !== undefined);\n}\n\n/**\n * `ids` resolved back to the columns behind them, paired with the position\n * each holds in `columns` — a stale id is dropped, matching `resolveRows`.\n */\nexport function resolveColumns<Row, Node>(\n columns: readonly ResolvedColumn<Row, Node>[],\n ids: readonly string[],\n): readonly SelectedColumn<Row, Node>[] {\n return ids\n .map((id) => {\n const columnIndex = columns.findIndex((entry) => entry.id === id);\n const column = columns[columnIndex];\n return column === undefined\n ? undefined\n : { columnId: id, column, columnIndex };\n })\n .filter((column) => column !== undefined);\n}\n\n/**\n * `cell` resolved down to the row, column and value it addresses — `null`\n * when there is no cell, or when either half of it names a row or column\n * that no longer exists.\n */\nexport function resolveCell<Row, Node>(\n rowsById: ReadonlyMap<string, ResolvedRow<Row>>,\n columns: readonly ResolvedColumn<Row, Node>[],\n cell: SelectedCellRef | null,\n): SelectedCell<Row, Node> | null {\n if (cell === null) {\n return null;\n }\n const entry = rowsById.get(cell.rowId);\n const columnIndex = columns.findIndex(\n (candidate) => candidate.id === cell.columnId,\n );\n const column = columns[columnIndex];\n if (entry === undefined || column === undefined) {\n return null;\n }\n return {\n rowId: cell.rowId,\n columnId: cell.columnId,\n row: entry.row,\n column,\n rowIndex: entry.rowIndex,\n columnIndex,\n value: accessDotted(entry.row, column.column.field),\n };\n}\n"],"mappings":"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","names":["total","test","columnId","entry"]}
|
|
1
|
+
{"version":3,"sources":["../src/util/grid.ts","../src/util/navigation.ts","../src/util/ordering.ts","../src/util/selection.ts","../src/util/sizing.ts","../src/util/sorting.ts","../src/util/filtering.ts","../src/util/grouping.ts","../src/util/rows.ts","../src/util/paging.ts","../src/util/paginationWindow.ts","../src/util/aggregation.ts"],"sourcesContent":["import type { ColumnAlignment, ColumnDefinition, ColumnType } from \"../types\";\n\n/**\n * Narrows to a value worth drilling into. Mirrors the `LeafValue` union in\n * `../types`: arrays and `Date`s are cell values rather than groups of\n * columns, and `null` cannot be introspected. Keep the two in sync.\n */\nfunction isNested(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\nfunction getType(value: unknown): ColumnType {\n if (value instanceof Date) return \"dateTime\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"bigint\") return \"number\";\n if (typeof value === \"symbol\") return \"string\";\n return \"string\";\n}\n\n/**\n * How a column of `type` aligns by default. Numeric types align right so that\n * their digits line up by place value; everything else reads from the left.\n */\nexport function alignmentForType(type: ColumnType): ColumnAlignment {\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n\nfunction getAlignment(value: unknown): ColumnAlignment {\n return alignmentForType(getType(value));\n}\n\n/**\n * Derives one column per field across `rows`, in the order each field is first\n * seen — so a key that only appears in a later row still gets a column,\n * appended at the end. Nested objects contribute their non-object properties\n * as `\"Parent.Child\"`; anything deeper is skipped, as is the nested object\n * itself.\n *\n * Order follows `Object.entries`, which lists integer-like keys first\n * regardless of the order they were written in.\n */\nexport function defineColumnsFromRows<Row>(\n rows: readonly Row[],\n): readonly ColumnDefinition<Row>[] {\n const columns: ColumnDefinition<Row>[] = [];\n const seen = new Set<string>();\n\n function add(field: string, value: unknown): void {\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n columns.push({\n field,\n type: getType(value),\n alignment: getAlignment(value),\n });\n }\n\n for (const row of rows) {\n if (!isNested(row)) {\n continue;\n }\n for (const [key, value] of Object.entries(row)) {\n if (!isNested(value)) {\n add(key, value);\n continue;\n }\n for (const [nestedKey, nestedValue] of Object.entries(value)) {\n if (!isNested(nestedValue)) {\n add(`${key}.${nestedKey}`, nestedValue);\n }\n }\n }\n }\n\n return columns;\n}\n\n/**\n * The key state about a column is stored under, and the key it renders with.\n * Every such lookup goes through here so the two can never disagree.\n */\nexport function getColumnId<Row>(\n column: ColumnDefinition<Row, unknown>,\n): string {\n return column.id ?? column.field;\n}\n\n/**\n * The key state about a row is stored under, and the key it renders with — the\n * row's counterpart to `getColumnId`, and for the same reason: every such\n * lookup goes through here so no two can disagree.\n *\n * Falls back to the position so a grid given no `getRowId` still works. That\n * fallback ties a selection to where a row sits rather than to the row, so\n * anything that sorts, filters or pages its data has to supply one — otherwise\n * a re-sort leaves the same positions selected under different rows.\n */\nexport function resolveRowId<Row>(\n row: Row,\n index: number,\n getRowId?: (row: Row, index: number) => string,\n): string {\n return getRowId?.(row, index) ?? String(index);\n}\n\n/**\n * What a column's header shows: its own `headerTemplate`, called first if it is\n * a function, or else a label read off the field path — `\"Application.Id\"`\n * reading as `\"Application Id\"`.\n */\nexport function resolveColumnLabel<Row, Node>(\n column: ColumnDefinition<Row, Node>,\n): Node | string {\n const { headerTemplate } = column;\n if (headerTemplate === undefined) {\n return column.field.split(\".\").join(\" \");\n }\n // `Node` is open, so it could itself be a function type and the `typeof`\n // check cannot narrow on its own. A header that is callable is the lazy\n // form by definition — no adapter binds `Node` to a function.\n return typeof headerTemplate === \"function\"\n ? (headerTemplate as () => Node)()\n : headerTemplate;\n}\n\n/** Which of a column header's keyboard interactions to announce. */\nexport interface KeyShortcutCapabilities {\n reorderable: boolean;\n resizable: boolean;\n sortable: boolean;\n groupable: boolean;\n}\n\n/**\n * The `aria-keyshortcuts` string for a column header, naming only the keys\n * that column actually responds to — so the keys are discoverable, since\n * none of them is a convention a user would try unprompted.\n */\nexport function buildKeyShortcuts(\n capabilities: KeyShortcutCapabilities,\n): string {\n return [\n capabilities.reorderable ? \"Control+ArrowLeft Control+ArrowRight\" : \"\",\n // Alt+Enter is the keyboard equivalent of double-clicking the resize\n // handle to size the column to its content — same gate as the nudge\n // shortcuts, since both are resize actions.\n capabilities.resizable ? \"Alt+ArrowLeft Alt+ArrowRight Alt+Enter\" : \"\",\n capabilities.sortable ? \"Alt+ArrowUp\" : \"\",\n // Down rather than reusing Up: it reads as \"drill into groups\", and Up is\n // already sort's own mnemonic on the same header.\n capabilities.groupable ? \"Alt+ArrowDown\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\n/**\n * Accesses a value at a dotted path, e.g. `foo.bar.baz`.\n *\n * @param obj The object to access\n * @param path Path to the value, e.g. `\"foo.bar\"`\n * @returns The value at `path`, or `undefined` if it doesn't exist\n */\nexport function accessDotted(obj: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce((value, key) => (isNested(value) ? value[key] : undefined), obj);\n}\n","/**\n * The header's row index. One coordinate space covers the header and the body\n * so that arrowing up out of the first row reaches the header without a case\n * of its own, and every other move is plain arithmetic.\n */\nexport const HEADER_ROW = -1;\n\n/** The cell the grid's single tab stop sits on. */\nexport interface GridFocus {\n readonly rowIndex: number;\n readonly columnIndex: number;\n}\n\n/** The modifier keys held with a navigation key press. */\nexport interface NavigationModifiers {\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n readonly altKey: boolean;\n readonly shiftKey: boolean;\n}\n\n/**\n * Holds a coordinate inside the grid, so that a focus surviving a column\n * removal or a shorter page never leaves the grid with no tab stop at all —\n * which is a grid a keyboard cannot reach.\n */\nexport function clampFocus(\n focus: GridFocus,\n rowCount: number,\n columnCount: number,\n): GridFocus {\n return {\n rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),\n columnIndex: Math.min(\n Math.max(focus.columnIndex, 0),\n Math.max(columnCount - 1, 0),\n ),\n };\n}\n\n/** Never treats any row as unreachable — the default when a caller has no skippable rows at all. */\nfunction noRowsSkippable(): boolean {\n return false;\n}\n\n/**\n * `rowIndex` moved forward one row at a time in `direction` (`1` or `-1`)\n * until it lands on a row `isSkippableRow` says is reachable, or would run\n * off the grid's own bounds (`HEADER_ROW` to `rowCount - 1`) trying — in\n * which case it returns `fallback` (the position before this move started)\n * instead of an out-of-range index.\n *\n * Deliberately not \"step one past the bound\" the way an ordinary move here\n * left unclamped would: `clampFocus` downstream holds any out-of-range\n * `rowIndex` at the nearest in-range one, and if that nearest row happens\n * to be the very one that was skippable (the grid's actual last row is a\n * group's own summary row, say), clamping back onto it would silently\n * defeat the skip. Staying at `fallback` is what a \"there is nowhere valid\n * to move to\" press should do instead — the same way `ArrowLeft` at column\n * 0 already leaves `rowIndex`/`columnIndex` exactly where they were,\n * `clampFocus` is left with nothing to correct either way, and a caller\n * that still wants to detect \"did this move go anywhere\" can compare the\n * result to `focus` itself.\n */\nfunction skipSkippableRows(\n rowIndex: number,\n direction: 1 | -1,\n rowCount: number,\n isSkippableRow: (rowIndex: number) => boolean,\n fallback: number,\n): number {\n let next = rowIndex;\n // The header (HEADER_ROW) and anything already out of the body's own\n // [0, rowCount) range are never skippable — an ordinary PageUp/ArrowUp\n // overshoot past the header, in particular, has nothing to skip past, so\n // this function has nothing to do; `clampFocus` downstream still handles\n // holding it in bounds as it always has.\n while (next >= 0 && next < rowCount && isSkippableRow(next)) {\n const stepped = next + direction;\n // Landing on the header itself (moving up from row 0) is always valid —\n // only a step that would leave the header *and* the body's own range\n // behind (moving down past the last row) has nowhere left to go.\n if (stepped < HEADER_ROW || stepped >= rowCount) {\n return fallback;\n }\n next = stepped;\n }\n return next;\n}\n\n/**\n * The focus a navigation key press moves to, or `null` when the key isn't\n * this grid's to handle — left for the browser or another handler.\n *\n * `pageSize` is a pre-measured row count rather than a callback: this\n * function takes plain data only, so a caller measures the viewport once and\n * hands over the number.\n *\n * `isSkippableRow` names rows the vertical moves (`ArrowUp`/`ArrowDown`/\n * `PageUp`/`PageDown`/`Ctrl+End`) never land a tab stop on — a group's own\n * summary row (`groupAggregateDisplay: \"row\"`), which still occupies a real\n * row slot for `rowCount`/`aria-rowindex` purposes but is presentational,\n * not a stop of its own. Left/right and plain `Home`/`End` stay row-scoped\n * and so never consult it. Defaults to skipping nothing, so every existing\n * caller (and every horizontal move) is unaffected.\n */\nexport function nextFocusForKey(\n key: string,\n modifiers: NavigationModifiers,\n focus: GridFocus,\n rowCount: number,\n columnCount: number,\n pageSize: number,\n isSkippableRow: (rowIndex: number) => boolean = noRowsSkippable,\n): GridFocus | null {\n // Alt on an arrow resizes and Ctrl on one reorders; the header claims both\n // before this runs, and elsewhere they belong to the browser.\n if (modifiers.altKey) {\n return null;\n }\n\n const { rowIndex, columnIndex } = focus;\n\n switch (key) {\n case \"ArrowLeft\":\n if (modifiers.ctrlKey) return null;\n return { rowIndex, columnIndex: columnIndex - 1 };\n case \"ArrowRight\":\n if (modifiers.ctrlKey) return null;\n return { rowIndex, columnIndex: columnIndex + 1 };\n case \"ArrowUp\":\n return {\n rowIndex: skipSkippableRows(\n rowIndex - 1,\n -1,\n rowCount,\n isSkippableRow,\n rowIndex,\n ),\n columnIndex,\n };\n case \"ArrowDown\":\n return {\n rowIndex: skipSkippableRows(\n rowIndex + 1,\n 1,\n rowCount,\n isSkippableRow,\n rowIndex,\n ),\n columnIndex,\n };\n // Ctrl takes Home and End to the grid's ends rather than the row's, which\n // is the one place Ctrl is navigation rather than reorder.\n case \"Home\":\n return modifiers.ctrlKey\n ? { rowIndex: HEADER_ROW, columnIndex: 0 }\n : { rowIndex, columnIndex: 0 };\n case \"End\":\n return modifiers.ctrlKey\n ? {\n rowIndex: skipSkippableRows(\n rowCount - 1,\n -1,\n rowCount,\n isSkippableRow,\n rowIndex,\n ),\n columnIndex: columnCount - 1,\n }\n : { rowIndex, columnIndex: columnCount - 1 };\n case \"PageUp\":\n return {\n rowIndex: skipSkippableRows(\n rowIndex - pageSize,\n -1,\n rowCount,\n isSkippableRow,\n rowIndex,\n ),\n columnIndex,\n };\n case \"PageDown\":\n return {\n rowIndex: skipSkippableRows(\n rowIndex + pageSize,\n 1,\n rowCount,\n isSkippableRow,\n rowIndex,\n ),\n columnIndex,\n };\n default:\n return null;\n }\n}\n","import type { ColumnDefinition, ColumnOrderState, DropSide } from \"../types\";\nimport { getColumnId } from \"./grid\";\n\n/**\n * Puts `columns` in the order `order` gives. Ids it lists come first, in that\n * order; columns it omits keep their position among the definitions and\n * follow. Ids naming no column are ignored.\n *\n * Tolerating both gaps is what lets a stored order outlive the column list it\n * was made against.\n */\nexport function applyColumnOrder<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n order: ColumnOrderState,\n): readonly ColumnDefinition<Row, Node>[] {\n if (order.length === 0) {\n return columns;\n }\n\n const byId = new Map(columns.map((column) => [getColumnId(column), column]));\n const listed = order\n .map((id) => byId.get(id))\n .filter((column) => column !== undefined);\n const taken = new Set(listed.map((column) => getColumnId(column)));\n\n return [\n ...listed,\n ...columns.filter((column) => !taken.has(getColumnId(column))),\n ];\n}\n\n/**\n * The order with `movedId` lifted out and put back in front of `beforeId`, or\n * at the end when that is `null`.\n *\n * Returns `orderedIds` itself when the move changes nothing, so callers can\n * treat an unchanged reference as \"no move happened\".\n */\nexport function moveColumnBefore(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): ColumnOrderState {\n if (movedId === beforeId || !orderedIds.includes(movedId)) {\n return orderedIds;\n }\n\n const without = orderedIds.filter((id) => id !== movedId);\n // Taken after the removal: an index read before it would be one too far\n // right for every move that travels rightwards.\n const target = beforeId === null ? without.length : without.indexOf(beforeId);\n if (target === -1) {\n return orderedIds;\n }\n\n const next = [...without];\n next.splice(target, 0, movedId);\n return next.every((id, index) => id === orderedIds[index])\n ? orderedIds\n : next;\n}\n\n/**\n * Whether moving `movedId` in front of `beforeId` would rearrange anything, so\n * that a drop indicator promises exactly the moves that happen.\n *\n * Defined against `moveColumnBefore` rather than restating its conditions, so\n * the two cannot disagree about what a no-op is.\n */\nexport function movesColumn(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): boolean {\n return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;\n}\n\n/**\n * The id a drop on `side` of `targetId` lands in front of, which is `null`\n * past the last column.\n *\n * \"After C\" and \"before D\" name the same gap, so collapsing both to one id\n * keeps a drop target a single nullable string.\n */\nexport function resolveDropBefore(\n orderedIds: ColumnOrderState,\n targetId: string,\n side: DropSide,\n): string | null {\n if (side === \"before\") {\n return targetId;\n }\n const index = orderedIds.indexOf(targetId);\n return index === -1 ? null : (orderedIds[index + 1] ?? null);\n}\n\n/**\n * The `beforeId` a keyboard nudge on `columnId` produces, for `onDrop` to\n * apply the same way a pointer drop does — or `undefined` when the nudge is a\n * no-op: `columnId` is not in `orderedIds`, or the nudge would move it left\n * of the start.\n *\n * `null` is a real answer here, not \"no-op\": it means append to the end, the\n * same convention `moveColumnBefore` uses.\n */\nexport function resolveKeyboardDropTarget(\n orderedIds: ColumnOrderState,\n columnId: string,\n direction: -1 | 1,\n): string | null | undefined {\n const index = orderedIds.indexOf(columnId);\n if (index === -1) {\n return undefined;\n }\n\n /**\n * Moving right lands in front of the id two along: the gap immediately\n * after a column is the one it already sits in.\n */\n const targetIndex = direction === -1 ? index - 1 : index + 2;\n if (targetIndex < 0) {\n return undefined;\n }\n\n return orderedIds[targetIndex] ?? null;\n}\n","import type {\n CellSelectionMode,\n CellSelectionState,\n SelectedCellRef,\n SelectIntent,\n SelectionDiff,\n SelectionMode,\n SelectionState,\n} from \"../types\";\n\n/** Nothing changed, so nothing to report — shared to save an allocation. */\nconst NO_CHANGE: SelectionDiff = { added: [], removed: [] };\n\n/**\n * Whether two selections hold the same ids in the same order.\n *\n * Order counts because the selection's own order is what makes `at(-1)` the\n * most recently selected; a comparison that ignored it would have a transform\n * return the old reference after a reordering that a consumer can observe.\n */\nfunction sameSelection(a: SelectionState, b: SelectionState): boolean {\n return a.length === b.length && a.every((id, index) => id === b[index]);\n}\n\n/**\n * The selection with `id` added if absent and removed if present — a\n * Ctrl-click, or Space on the focused member.\n *\n * The mode is enforced here rather than by each caller, so `\"single\"` cannot\n * accumulate no matter which interaction reaches it. Returns `selection`\n * itself when `mode` is `false`, keeping the unchanged-reference contract\n * `moveColumnBefore` set: a caller treats an unchanged reference as \"nothing\n * happened\" and neither renders nor reports.\n */\nexport function toggleSelection(\n selection: SelectionState,\n id: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode === false) {\n return selection;\n }\n if (selection.includes(id)) {\n return selection.filter((selectedId) => selectedId !== id);\n }\n return mode === \"single\" ? [id] : [...selection, id];\n}\n\n/**\n * The selection reduced to `id` alone — a plain click, which discards what\n * came before rather than adding to it.\n *\n * Never deselects, so clicking the one selected row leaves it selected.\n * Letting go of it is Ctrl-click's job, and `toggleSelection`'s.\n */\nexport function selectOnly(\n selection: SelectionState,\n id: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode === false) {\n return selection;\n }\n return selection.length === 1 && selection[0] === id ? selection : [id];\n}\n\n/**\n * The selection replaced by every id from `anchorId` to `focusId` inclusive — a\n * Shift-click. The two may be given in either order.\n *\n * Spans `orderedIds`, the order as displayed, rather than the selection's own:\n * the range a user drew across the screen is the range they mean, whatever\n * order they clicked its ends in. Degrades to `selectOnly` under `\"single\"`,\n * where a range has nowhere to go.\n */\nexport function selectRange(\n selection: SelectionState,\n orderedIds: readonly string[],\n anchorId: string,\n focusId: string,\n mode: SelectionMode,\n): SelectionState {\n if (mode !== \"multiple\") {\n return selectOnly(selection, focusId, mode);\n }\n\n const anchor = orderedIds.indexOf(anchorId);\n const focus = orderedIds.indexOf(focusId);\n if (anchor === -1 || focus === -1) {\n return selection;\n }\n\n const next = orderedIds.slice(\n Math.min(anchor, focus),\n Math.max(anchor, focus) + 1,\n );\n return sameSelection(selection, next) ? selection : next;\n}\n\n/**\n * Every id in `orderedIds`, or `selection` untouched under any mode but\n * `\"multiple\"` — there is no selecting all of one thing.\n */\nexport function selectAll(\n selection: SelectionState,\n orderedIds: readonly string[],\n mode: SelectionMode,\n): SelectionState {\n if (mode !== \"multiple\") {\n return selection;\n }\n return sameSelection(selection, orderedIds) ? selection : orderedIds;\n}\n\nexport function clearSelection(selection: SelectionState): SelectionState {\n return selection.length === 0 ? selection : [];\n}\n\n/**\n * What one transition added and what it removed.\n *\n * Defined here rather than in an adapter so that every callback reporting a\n * selection change reads one answer, and a select and a deselect fired for the\n * same interaction cannot disagree about which it was.\n */\nexport function diffSelection(\n previous: SelectionState,\n next: SelectionState,\n): SelectionDiff {\n if (previous === next) {\n return NO_CHANGE;\n }\n\n const before = new Set(previous);\n const after = new Set(next);\n return {\n added: next.filter((id) => !before.has(id)),\n removed: previous.filter((id) => !after.has(id)),\n };\n}\n\nexport function isSameCell(\n a: CellSelectionState,\n b: CellSelectionState,\n): boolean {\n if (a === null || b === null) {\n return a === b;\n }\n return a.rowId === b.rowId && a.columnId === b.columnId;\n}\n\n/**\n * The cell selection moved to `cell` — a plain click. Idempotent: clicking the\n * selected cell again leaves it selected, matching `selectOnly`.\n */\nexport function selectCell(\n selection: CellSelectionState,\n cell: SelectedCellRef,\n mode: CellSelectionMode,\n): CellSelectionState {\n if (mode === false || isSameCell(selection, cell)) {\n return selection;\n }\n return cell;\n}\n\n/**\n * The cell selection moved to `cell`, or cleared when it is already there — a\n * Ctrl-click, matching `toggleSelection`.\n */\nexport function toggleCellSelection(\n selection: CellSelectionState,\n cell: SelectedCellRef,\n mode: CellSelectionMode,\n): CellSelectionState {\n if (mode === false) {\n return selection;\n }\n return isSameCell(selection, cell) ? null : cell;\n}\n\n/** The intent a click or key press carries, by the modifiers held with it. */\nexport function intentOf(event: {\n ctrlKey: boolean;\n metaKey: boolean;\n shiftKey: boolean;\n}): SelectIntent {\n if (event.shiftKey) {\n return \"range\";\n }\n // Cmd on a Mac is Ctrl everywhere else, and both mean the same thing here.\n return event.ctrlKey || event.metaKey ? \"toggle\" : \"replace\";\n}\n\n/**\n * The transform an intent asks for. A function rather than a branch inside\n * each caller, so a row and a column cannot come to mean different things by\n * the same click.\n */\nexport function applySelectionIntent(\n selection: SelectionState,\n orderedIds: readonly string[],\n anchorId: string | null,\n id: string,\n intent: SelectIntent,\n mode: SelectionMode,\n): SelectionState {\n switch (intent) {\n case \"toggle\":\n return toggleSelection(selection, id, mode);\n // A range with no anchor yet spans from the id to itself, which is the\n // plain click a first Shift-click may as well be.\n case \"range\":\n return selectRange(selection, orderedIds, anchorId ?? id, id, mode);\n default:\n return selectOnly(selection, id, mode);\n }\n}\n","import type {\n ColumnConstraints,\n ColumnDefinition,\n ColumnResizeSession,\n ColumnResolveOptions,\n ColumnSizeDefaults,\n ColumnSizingState,\n ResolvedColumn,\n} from \"../types\";\nimport { alignmentForType, getColumnId, resolveColumnLabel } from \"./grid\";\n\n/**\n * Applied to any column that does not size itself. The minimum is a width a\n * header stays legible at rather than a measured value — a column dragged\n * narrower than this reads as a rendering fault.\n */\nexport const DEFAULT_COLUMN_SIZES: ColumnSizeDefaults = {\n width: 150,\n minWidth: 40,\n maxWidth: Number.POSITIVE_INFINITY,\n};\n\n/** How far one arrow key press moves a column edge. */\nexport const KEYBOARD_STEP = 10;\n\nfunction withDefaults(\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnSizeDefaults {\n return { ...DEFAULT_COLUMN_SIZES, ...defaults };\n}\n\n/**\n * The bounds `column` may be sized between. Resolved in one place so that a\n * drag, a clamp and an auto-fit cannot disagree about a column's limits.\n */\nexport function resolveColumnConstraints<Row>(\n column: ColumnDefinition<Row, unknown>,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnConstraints {\n const resolved = withDefaults(defaults);\n return {\n minWidth: column.minWidth ?? resolved.minWidth,\n maxWidth: column.maxWidth ?? resolved.maxWidth,\n };\n}\n\n/** Holds `width` within `constraints`. */\nexport function clampColumnWidth(\n width: number,\n constraints: ColumnConstraints,\n): number {\n return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);\n}\n\n/**\n * Pairs each column with everything needed to render it — the width it sits\n * at, the label its header shows, whether it may be resized and how its cells\n * align. Widths take the first of the sizing state, the column's own `width`,\n * and the default, then clamp.\n *\n * The sizing state winning means a user's drag outlives a re-render; a column\n * absent from it still tracks a `width` edited in the definition.\n */\nexport function resolveColumnWidths<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n sizing: ColumnSizingState,\n options?: ColumnResolveOptions,\n): readonly ResolvedColumn<Row, Node>[] {\n const resolved = withDefaults(options?.sizes);\n\n return columns.map((column) => {\n const id = getColumnId(column);\n const stored = sizing[id];\n\n return {\n column,\n id,\n sized: stored !== undefined,\n width: clampColumnWidth(\n stored ?? column.width ?? resolved.width,\n resolveColumnConstraints(column, resolved),\n ),\n label: resolveColumnLabel(column),\n resizable: column.resizable ?? options?.resizable ?? false,\n reorderable: column.reorderable ?? options?.reorderable ?? false,\n groupByDraggable:\n column.groupByDraggable ?? options?.groupByDraggable ?? false,\n alignment: column.alignment ?? alignmentForType(column.type ?? \"string\"),\n };\n });\n}\n\nexport function totalColumnWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n): number {\n return resolved.reduce((total, entry) => total + entry.width, 0);\n}\n\n/**\n * Opens a resize. The session captures its constraints and starting point up\n * front, so applying a pointer position later is arithmetic on numbers alone —\n * which is what keeps the drag testable without a DOM.\n *\n * @param startPosition Where the pointer went down, on the axis being dragged.\n */\nexport function beginColumnResize<Row>(\n column: ColumnDefinition<Row, unknown>,\n startWidth: number,\n startPosition: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnResizeSession {\n return {\n columnId: getColumnId(column),\n startWidth,\n startPosition,\n constraints: resolveColumnConstraints(column, defaults),\n };\n}\n\n/**\n * The width the dragged column takes with the pointer at `position`. The delta\n * is signed, so a right-to-left adapter negates it and nothing else changes.\n */\nexport function applyColumnResize(\n session: ColumnResizeSession,\n position: number,\n): number {\n return clampColumnWidth(\n session.startWidth + (position - session.startPosition),\n session.constraints,\n );\n}\n\ninterface SizingEntry<Row, Node> {\n entry: ResolvedColumn<Row, Node>;\n width: number;\n bound: number;\n adjustable: boolean;\n}\n\n/**\n * Whether `item` has reached `bound` in the direction it moves toward — above\n * it for a column growing to a `maxWidth`, below it for one shrinking to a\n * `minWidth`.\n */\nfunction reachedBound<Row, Node>(\n width: number,\n item: SizingEntry<Row, Node>,\n): boolean {\n return item.bound > item.width ? width >= item.bound : width <= item.bound;\n}\n\n/**\n * Moves every adjustable entry's `width` toward `bound` by its proportional\n * share of `remaining`'s current surplus or deficit, looping so a column\n * clamped at its bound in one pass frees the remainder for the others to\n * absorb in the next.\n */\nfunction redistribute<Row, Node>(\n entries: readonly SizingEntry<Row, Node>[],\n remaining: (sum: number) => number,\n): void {\n for (;;) {\n const movable = entries.filter(\n (item) => item.adjustable && !reachedBound(item.width, item),\n );\n const left = remaining(entries.reduce((t, i) => t + i.width, 0));\n if (left <= 0 || movable.length === 0) {\n break;\n }\n\n const base = movable.reduce((total, item) => total + item.width, 0);\n let clamped = false;\n\n for (const item of movable) {\n const share =\n base === 0 ? left / movable.length : (left * item.width) / base;\n const next = item.width + (item.bound > item.width ? share : -share);\n\n if (reachedBound(next, item)) {\n item.width = item.bound;\n clamped = true;\n } else {\n item.width = next;\n }\n }\n\n if (!clamped) {\n break;\n }\n }\n}\n\n/**\n * Fits columns to `availableWidth`: grows them to fill leftover space, or\n * shrinks them to relieve an overflow, in both cases proportionally to each\n * column's current width. Columns the user has sized are left alone in\n * either direction, so dragging one column neither rubber-bands it back nor\n * has it absorb a squeeze meant for the rest.\n *\n * Growing stops at each column's `maxWidth`; shrinking stops at its\n * `minWidth`. If shrinking every adjustable column to its floor still does\n * not reach `availableWidth`, the columns are returned untouched and the\n * container scrolls rather than crushing them further.\n */\nexport function fitColumnsToWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n availableWidth: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): readonly ResolvedColumn<Row, Node>[] {\n const target = Math.floor(availableWidth);\n const total = totalColumnWidth(resolved);\n if (resolved.length === 0 || total === target) {\n return resolved;\n }\n\n const growing = total < target;\n const entries: SizingEntry<Row, Node>[] = resolved.map((entry) => {\n const constraints = resolveColumnConstraints(entry.column, defaults);\n return {\n entry,\n width: entry.width,\n bound: growing ? constraints.maxWidth : constraints.minWidth,\n adjustable: !entry.sized,\n };\n });\n\n if (!growing) {\n // Only unsized columns can be shrunk, so if they alone cannot reach the\n // target the grid genuinely cannot fit and should scroll, unchanged.\n const floor = entries.reduce(\n (sum, item) => sum + (item.adjustable ? item.bound : item.width),\n 0,\n );\n if (floor > target) {\n return resolved;\n }\n }\n\n redistribute(entries, (sum) => (growing ? target - sum : sum - target));\n\n // Fractional widths would leave the table a sub-pixel off its container and\n // show a scrollbar that never goes away, so round every column toward the\n // target and hand the remaining pixels back out (growing) or claw them\n // back (shrinking) one at a time.\n const rounded = entries.map((item) =>\n growing ? Math.floor(item.width) : Math.ceil(item.width),\n );\n let remainder = target - rounded.reduce((total, width) => total + width, 0);\n\n return entries.map((item, index) => {\n let width = rounded[index] ?? item.width;\n // Rounding toward the target can only ever leave it short when growing\n // (floor) or over when shrinking (ceil), so only one direction is ever\n // live here — but each still needs room left before its own bound.\n const hasRoom = growing ? width < item.bound : width > item.bound;\n\n if (remainder !== 0 && item.adjustable && hasRoom) {\n width += growing ? 1 : -1;\n remainder += growing ? -1 : 1;\n }\n\n return { ...item.entry, width };\n });\n}\n\n/**\n * The width a column needs to show `measuredWidth` of content.\n *\n * `padding` is an allowance on top of the measurement, since a column sized to\n * exactly its content can still round down into an ellipsis.\n */\nexport function sizeColumnToContent(\n measuredWidth: number,\n constraints: ColumnConstraints,\n padding = 2,\n): number {\n return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);\n}\n\n/**\n * `current` with `columnId` reverted to its width in `base` — or omitted\n * entirely if `base` had none — for Escape to restore a resize in progress.\n *\n * A column with no stored width in `base` is correctly omitted by deleting\n * the key rather than merging its `base` width back in, which would\n * otherwise leave it pinned and hidden from auto-fit.\n */\nexport function revertColumnSize(\n current: ColumnSizingState,\n base: ColumnSizingState,\n columnId: string,\n): ColumnSizingState {\n const next = Object.fromEntries(\n Object.entries(current).filter(([id]) => id !== columnId),\n );\n if (columnId in base) {\n next[columnId] = base[columnId] as number;\n }\n return next;\n}\n","import type {\n ColumnSortState,\n ColumnType,\n ResolvedColumn,\n ResolvedRow,\n SortDirection,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/** A date/time value converted to a comparable instant. */\nfunction toTime(value: unknown): number {\n return value instanceof Date\n ? value.getTime()\n : new Date(String(value)).getTime();\n}\n\n/**\n * Compares two cell values the way `type` implies — numerically for the\n * numeric types, chronologically for the date/time types, lexically\n * otherwise. Defaults to string comparison when `type` is absent, mirroring\n * `ColumnDefinition.type`'s own default.\n *\n * `null`/`undefined` sort after every value on both sides. Used directly,\n * that ordering is fixed; `sortRows` applies it directionally without\n * disturbing it — see its own doc comment.\n */\nexport function compareValues(\n a: unknown,\n b: unknown,\n type?: ColumnType,\n): number {\n if (isEmpty(a)) {\n return isEmpty(b) ? 0 : 1;\n }\n if (isEmpty(b)) {\n return -1;\n }\n\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return Number(a) - Number(b);\n case \"boolean\":\n return Number(Boolean(a)) - Number(Boolean(b));\n case \"date\":\n case \"dateTime\":\n case \"time\":\n return toTime(a) - toTime(b);\n default:\n return String(a).localeCompare(String(b));\n }\n}\n\n/** This column's direction in `sort`, or `null` outside it. */\nexport function sortDirectionFor(\n sort: ColumnSortState,\n columnId: string,\n): SortDirection | null {\n return sort.find((entry) => entry.columnId === columnId)?.direction ?? null;\n}\n\n/** This column's 1-based place in `sort`, or `null` outside it. */\nexport function sortPriorityFor(\n sort: ColumnSortState,\n columnId: string,\n): number | null {\n const index = sort.findIndex((entry) => entry.columnId === columnId);\n return index === -1 ? null : index + 1;\n}\n\n/**\n * The sort with `columnId` cycled to its next direction — none → asc → desc →\n * none.\n *\n * `stack: false` (a plain click) means \"sort by only this column\": unless\n * `columnId` is already the sole entry, in which case its own cycle just\n * continues, it collapses `sort` down to `columnId` alone at `\"asc\"`,\n * regardless of any direction it held as part of a larger stack.\n *\n * `stack: true` (a Shift-click) cycles or appends `columnId` in place without\n * disturbing the rest of the stack; cycling it back to `\"none\"` removes it\n * and closes the gap, leaving the others in their existing order.\n */\nexport function toggleColumnSort(\n sort: ColumnSortState,\n columnId: string,\n options: { readonly stack: boolean },\n): ColumnSortState {\n const index = sort.findIndex((entry) => entry.columnId === columnId);\n const isSoleEntry = sort.length === 1 && index === 0;\n\n if (!options.stack && !isSoleEntry) {\n return [{ columnId, direction: \"asc\" }];\n }\n\n const current = index === -1 ? undefined : sort[index]?.direction;\n const next: SortDirection | null =\n current === undefined ? \"asc\" : current === \"asc\" ? \"desc\" : null;\n\n if (next === null) {\n return sort.filter((entry) => entry.columnId !== columnId);\n }\n if (index === -1) {\n return [...sort, { columnId, direction: next }];\n }\n return sort.map((entry, i) =>\n i === index ? { columnId, direction: next } : entry,\n );\n}\n\n/**\n * `rows` ordered by `sort` — each entry read against `columns` for its\n * `field` and `type` — with `rowIndex` renumbered to match the new\n * positions, the invariant the rest of the grid relies on for keyboard\n * navigation and `aria-rowindex`.\n *\n * Stable (`Array.prototype.sort` has been stable since ES2019), so rows\n * equal under every active key keep their relative order. A `null`/\n * `undefined` value sorts last under either direction — resolved before\n * `compareValues` is asked, and left out of the direction flip below, so\n * that toggling a column reorders the values it holds rather than moving its\n * gaps to the front.\n *\n * Returns `rows` itself, untouched, when `sort` is empty or names only\n * columns absent from `columns` — a stored sort tolerates a column removed\n * from the definition the same way a stored `ColumnOrderState` does.\n */\nexport function sortRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n sort: ColumnSortState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n if (sort.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = sort.filter((entry) => byId.has(entry.columnId));\n if (active.length === 0) {\n return rows;\n }\n\n const sorted = [...rows].sort((a, b) => {\n for (const { columnId, direction } of active) {\n const column = byId.get(columnId);\n if (column === undefined) {\n continue;\n }\n const left = accessDotted(a.row, column.field);\n const right = accessDotted(b.row, column.field);\n const leftEmpty = isEmpty(left);\n const rightEmpty = isEmpty(right);\n if (leftEmpty || rightEmpty) {\n if (leftEmpty && rightEmpty) {\n continue;\n }\n return leftEmpty ? 1 : -1;\n }\n\n const result = compareValues(left, right, column.type);\n if (result !== 0) {\n return direction === \"asc\" ? result : -result;\n }\n }\n return 0;\n });\n\n return sorted.map((row, index) =>\n row.rowIndex === index ? row : { ...row, rowIndex: index },\n );\n}\n","import type {\n ColumnDefinition,\n ColumnType,\n FieldPath,\n FilterEntry,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n TextFilterEntry,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\n\ninterface FilterPattern {\n mode: \"exact\" | \"contains\" | \"startsWith\" | \"endsWith\";\n text: string; // anchors stripped, lowercased once\n}\n\n/**\n * Reads a query's leading/trailing `%` into a match mode — see\n * `TextFilterEntry`'s own doc comment for the four shapes. Only the first\n * and last characters are read as anchors — a `%` anywhere else in the\n * query is a literal character, not a wildcard.\n */\nfunction parseFilterPattern(query: string): FilterPattern {\n const start = query.startsWith(\"%\");\n const end = query.endsWith(\"%\");\n let text = query;\n if (start) {\n text = text.slice(1);\n }\n if (end) {\n text = text.slice(0, -1);\n }\n text = text.toLowerCase();\n\n if (start && end) {\n return { mode: \"contains\", text };\n }\n if (start) {\n return { mode: \"endsWith\", text };\n }\n if (end) {\n return { mode: \"startsWith\", text };\n }\n return { mode: \"exact\", text };\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/**\n * `parseFilterPattern`, compiled to a reusable test — parsed once, reused\n * per row, unlike a per-call `matchesQuery`.\n */\nfunction compileFilterQuery(query: string): (value: unknown) => boolean {\n const { mode, text } = parseFilterPattern(query);\n return (value: unknown) => {\n const candidate = (isEmptyValue(value) ? \"\" : String(value)).toLowerCase();\n switch (mode) {\n case \"exact\":\n return candidate === text;\n case \"contains\":\n return candidate.includes(text);\n case \"startsWith\":\n return candidate.startsWith(text);\n case \"endsWith\":\n return candidate.endsWith(text);\n }\n };\n}\n\n/**\n * Whether `target` (a `ValueFilterEntry`'s value) equals `value` under\n * `type` — `false` whenever `type` doesn't agree with `target`'s own kind,\n * so a number filter never silently matches a string column.\n */\nfunction matchesTypedValue(\n value: unknown,\n target: number | boolean | Date,\n type: ColumnType | undefined,\n): boolean {\n if (typeof target === \"number\") {\n if (\n type !== \"number\" &&\n type !== \"decimal\" &&\n type !== \"currency\" &&\n type !== \"percent\"\n ) {\n return false;\n }\n return typeof value === \"number\" && value === target;\n }\n if (typeof target === \"boolean\") {\n if (type !== \"boolean\") {\n return false;\n }\n return typeof value === \"boolean\" && value === target;\n }\n if (type !== \"date\" && type !== \"dateTime\" && type !== \"time\") {\n return false;\n }\n return value instanceof Date && value.getTime() === target.getTime();\n}\n\n/**\n * Whether a non-group entry names a column present in `byId` (or names\n * none at all) — lets a stored filter tolerate a column removed from the\n * definition the same way a stored `ColumnSortState` does, by dropping only\n * the entries that reference it.\n *\n * A `GroupFilterEntry` is always usable, regardless of its own contents:\n * its vacuous truth value under an empty or fully-unusable `entries` list\n * depends on its own `combinator` (`\"and\"` vacuously matches everything,\n * `\"or\"` vacuously matches nothing) — a group can't be dropped from its\n * parent's list the way an unusable simple entry can, since whether\n * dropping it would be safe depends on the *parent's* combinator too,\n * which this check has no way to know. Compiling it and letting `.every()`/\n * `.some()` resolve the vacuous case naturally is what stays correct in\n * every context; deciding upfront here is not.\n */\nfunction isEntryUsable<Row>(\n entry: FilterEntry<Row>,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n): boolean {\n if (\"combinator\" in entry) {\n return true;\n }\n return entry.columnId === undefined || byId.has(entry.columnId);\n}\n\n/**\n * One compiled test per usable `FilterEntry`, closing over its column\n * lookup so `filterRows`'s row loop is just `.every(test)`.\n */\nfunction compileFilterEntry<Row>(\n entry: FilterEntry<Row>,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): (row: ResolvedRow<Row>) => boolean {\n if (\"combinator\" in entry) {\n const tests = entry.entries\n .filter((nested) => isEntryUsable(nested, byId))\n .map((nested) => compileFilterEntry(nested, byId, columns));\n return entry.combinator === \"and\"\n ? (row) => tests.every((test) => test(row))\n : (row) => tests.some((test) => test(row));\n }\n\n if (\"predicate\" in entry) {\n const { columnId, predicate } = entry;\n const column = columnId === undefined ? undefined : byId.get(columnId);\n return (row) =>\n predicate(\n column === undefined ? undefined : accessDotted(row.row, column.field),\n row.row,\n );\n }\n\n if (\"value\" in entry) {\n const { columnId, value } = entry;\n if (columnId !== undefined) {\n const column = byId.get(columnId);\n if (column === undefined) {\n return () => false;\n }\n return (row) =>\n matchesTypedValue(\n accessDotted(row.row, column.field),\n value,\n column.type,\n );\n }\n return (row) =>\n columns.some((entry) =>\n matchesTypedValue(\n accessDotted(row.row, entry.column.field),\n value,\n entry.column.type,\n ),\n );\n }\n\n const { columnId, query } = entry;\n const test = compileFilterQuery(query);\n if (columnId !== undefined) {\n const column = byId.get(columnId);\n if (column === undefined) {\n return () => false;\n }\n return (row) => test(accessDotted(row.row, column.field));\n }\n return (row) =>\n columns.some((entry) => test(accessDotted(row.row, entry.column.field)));\n}\n\n/**\n * Whether `value`, stringified, matches `query`'s SQL LIKE-style pattern —\n * case-insensitive always. A bare query is an exact match; `%text%` is\n * contains; `text%` is starts-with; `%text` is ends-with. `null`/`undefined`\n * stringify to `\"\"`.\n *\n * A one-shot convenience: parses `query` fresh on every call. `filterRows`\n * does not call this per cell — it compiles each `TextFilterEntry` once,\n * outside its row loop.\n */\nexport function matchesQuery(value: unknown, query: string): boolean {\n return compileFilterQuery(query)(value);\n}\n\n/**\n * This column's current text query, or the global entry's when `columnId`\n * is omitted; `null` if no `TextFilterEntry` names it — including when the\n * entry that exists for that key is a `ValueFilterEntry`/\n * `PredicateFilterEntry`/`GroupFilterEntry` instead, since none of those\n * hold a query string to report.\n */\nexport function filterQueryFor<Row>(\n filter: FilterState<Row>,\n columnId?: FieldPath<Row> | (string & {}),\n): string | null {\n const entry = filter.find(\n (candidate): candidate is TextFilterEntry<Row> =>\n \"query\" in candidate && candidate.columnId === columnId,\n );\n return entry?.query ?? null;\n}\n\n/**\n * The filter with the `TextFilterEntry` for `columnId` (or the global\n * entry, when `columnId` is omitted) set to `query` — replacing whatever\n * entry already holds that key, of any variant, if present; appending a new\n * `TextFilterEntry` if not. An empty `query` removes the entry instead of\n * storing a filter that can never usefully match.\n *\n * Returns `filter` itself, unchanged, when the result would be a no-op: an\n * empty `query` naming a key with no existing entry, or a `query` equal to\n * an existing `TextFilterEntry`'s.\n */\nexport function setColumnFilter<Row>(\n filter: FilterState<Row>,\n columnId: (FieldPath<Row> | (string & {})) | undefined,\n query: string,\n): FilterState<Row> {\n const index = filter.findIndex(\n (entry) => !(\"combinator\" in entry) && entry.columnId === columnId,\n );\n\n if (query === \"\") {\n return index === -1 ? filter : filter.filter((_entry, i) => i !== index);\n }\n\n const next: TextFilterEntry<Row> =\n columnId === undefined ? { query } : { columnId, query };\n\n if (index === -1) {\n return [...filter, next];\n }\n const existing = filter[index];\n if (\n existing !== undefined &&\n \"query\" in existing &&\n existing.query === query\n ) {\n return filter;\n }\n return filter.map((entry, i) => (i === index ? next : entry));\n}\n\n/** `filter` reduced to empty. Returns `filter` itself when already empty. */\nexport function clearAllFilters<Row>(\n filter: FilterState<Row>,\n): FilterState<Row> {\n return filter.length === 0 ? filter : [];\n}\n\n/**\n * `rows` reduced to those matching every entry in `filter` (AND at the top\n * level — nest a `GroupFilterEntry` for OR). `rowIndex` is renumbered to\n * match the retained positions — the same invariant `sortRows` keeps,\n * needed here independently since `filterRows` runs before `sortRows` in\n * the composed pipeline (see `resolveShownRows`).\n *\n * Order-preserving: surviving rows keep their relative order, which is what\n * makes running this before a sort produce the identical final order a\n * sort-then-filter would — only the cost differs.\n *\n * Every entry is compiled once, before the row loop.\n *\n * Returns `rows` itself, untouched, when `filter` is empty or names only\n * columns absent from `columns` — a stored filter tolerates a column\n * removed from the definition the same way a stored `ColumnSortState` does.\n */\nexport function filterRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n filter: FilterState<Row>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n if (filter.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = filter.filter((entry) => isEntryUsable(entry, byId));\n if (active.length === 0) {\n return rows;\n }\n\n const compiled = active.map((entry) =>\n compileFilterEntry(entry, byId, columns),\n );\n const filtered = rows.filter((row) => compiled.every((test) => test(row)));\n\n return filtered.map((row, index) =>\n row.rowIndex === index ? row : { ...row, rowIndex: index },\n );\n}\n","import type {\n ColumnDefinition,\n DisplayRow,\n GroupByEntry,\n GroupByState,\n GroupExpansionState,\n ResolvedColumn,\n ResolvedGroupRow,\n ResolvedRow,\n SortDirection,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\nimport { compareValues } from \"./sorting\";\n\n/**\n * This group's id, from its path. Every group-id producer/consumer in this\n * package goes through this — never join `path` by hand elsewhere.\n *\n * `JSON.stringify` rather than a delimiter join: a delimiter would have to be\n * escaped in every segment to stay collision-free against a value that\n * legitimately contains it, and `JSON.stringify` already draws that\n * boundary between array entries for free.\n */\nexport function groupRowId(path: readonly unknown[]): string {\n return JSON.stringify(path);\n}\n\ninterface ValueGroup<Row> {\n value: unknown;\n rows: ResolvedRow<Row>[];\n}\n\n/**\n * A key `accessDotted`'s result can be bucketed by, distinguishing values\n * that `compareValues`/`===` would treat as equal-but-not-identical (two\n * `Date` instances for the same instant) while keeping values of different\n * types apart (the number `1` and the string `\"1\"`).\n *\n * Exported for `aggregation.ts`, which needs the identical notion of\n * \"same value\" to match a leaf row back against a group header's `path` —\n * two producers of the same key would drift apart silently otherwise.\n */\nexport function bucketKey(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\\0empty\";\n }\n if (value instanceof Date) {\n return `\\0date:${String(value.getTime())}`;\n }\n switch (typeof value) {\n case \"string\":\n case \"number\":\n case \"boolean\":\n case \"bigint\":\n case \"symbol\":\n return `\\0${typeof value}:${String(value)}`;\n case \"function\":\n return \"\\0function\";\n default:\n // Arrays, and anything else `accessDotted` could return.\n return `\\0object:${JSON.stringify(value)}`;\n }\n}\n\n/**\n * `rows` bucketed by `column`'s value, one bucket per distinct value in\n * first-seen order, then ordered by `direction` via `compareValues` — the\n * same comparator `sortRows` uses, so a grouped column and a sorted column\n * order their values identically.\n */\nfunction partitionByValue<Row>(\n rows: readonly ResolvedRow<Row>[],\n column: ColumnDefinition<Row, unknown>,\n direction: SortDirection,\n): readonly ValueGroup<Row>[] {\n const buckets = new Map<string, ValueGroup<Row>>();\n for (const row of rows) {\n const value = accessDotted(row.row, column.field);\n const key = bucketKey(value);\n const bucket = buckets.get(key);\n if (bucket === undefined) {\n buckets.set(key, { value, rows: [row] });\n } else {\n bucket.rows.push(row);\n }\n }\n\n const groups = [...buckets.values()];\n groups.sort((a, b) => {\n const result = compareValues(a.value, b.value, column.type);\n return direction === \"asc\" ? result : -result;\n });\n return groups;\n}\n\n/**\n * Depth-first builder behind `groupRows`: partitions `rows` by `levels[0]`,\n * appends a header per resulting group, and recurses into `levels.slice(1)`\n * for each — unless that group is collapsed, in which case only its header\n * is appended. `rows` with no levels left (either `levels` started empty, or\n * recursion exhausted it) are appended as plain data rows, keeping the\n * relative order filter/sort already gave them.\n */\nfunction buildLevel<Row>(\n rows: readonly ResolvedRow<Row>[],\n levels: readonly GroupByEntry[],\n parentPath: readonly unknown[],\n expansion: GroupExpansionState,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n out: DisplayRow<Row>[],\n): void {\n const level = levels[0];\n if (level === undefined) {\n out.push(...rows);\n return;\n }\n\n // `levels` is pre-filtered to entries naming a column present in `byId`\n // (see `groupRows`), so this is always found.\n const column = byId.get(level.columnId);\n if (column === undefined) {\n out.push(...rows);\n return;\n }\n\n const rest = levels.slice(1);\n const groups = partitionByValue(rows, column, level.direction ?? \"asc\");\n\n for (const group of groups) {\n const path = [...parentPath, group.value];\n const groupId = groupRowId(path);\n const expanded = !expansion.includes(groupId);\n const header: ResolvedGroupRow = {\n kind: \"group\",\n groupId,\n level: parentPath.length,\n columnId: level.columnId,\n value: group.value,\n path,\n expanded,\n count: group.rows.length,\n // Renumbered globally once `groupRows` has the whole flattened array —\n // a header has no row of its own to carry a dataset position, so\n // `datasetIndex` takes the same renumbered value `rowIndex` does.\n rowIndex: -1,\n datasetIndex: -1,\n // Set by `withGroupAggregates`, downstream of grouping — empty here\n // since `groupRows` itself never knows about `AggregateState`.\n aggregates: new Map(),\n };\n out.push(header);\n if (expanded) {\n buildLevel(group.rows, rest, path, expansion, byId, out);\n }\n }\n}\n\n/**\n * `rows`, regrouped into `groupBy`'s stacked levels — a group header per\n * distinct value at each level, ordered by `compareValues` per that level's\n * own `direction`, followed depth-first by its nested groups and/or data\n * rows. A collapsed group (its `groupId` present in `expansion`) contributes\n * only its own header, nothing beneath it — but its `count` still reports\n * the group's full leaf-row count either way.\n *\n * Filter and sort are expected to have already run (see `resolveShownRows`):\n * this only reshapes their output, and never changes which rows appear or\n * their relative order within a group.\n *\n * `rowIndex` is renumbered across the whole flattened output, header rows\n * included — the same invariant `sortRows`/`filterRows` keep for\n * `ResolvedRow.rowIndex`, extended to `ResolvedGroupRow.rowIndex` since\n * `GridBody` addresses both kinds by the same flat position.\n *\n * Returns `rows` itself, untouched, when `groupBy` is empty or names only\n * columns absent from `columns` — a stored grouping tolerates a column\n * removed from the definition the same way a stored `ColumnSortState` does.\n */\nexport function groupRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n groupBy: GroupByState,\n expansion: GroupExpansionState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly DisplayRow<Row>[] {\n if (groupBy.length === 0) {\n return rows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const active = groupBy.filter((entry) => byId.has(entry.columnId));\n if (active.length === 0) {\n return rows;\n }\n\n const out: DisplayRow<Row>[] = [];\n buildLevel(rows, active, [], expansion, byId, out);\n\n return out.map((entry, index) => {\n if (\"kind\" in entry) {\n return entry.rowIndex === index && entry.datasetIndex === index\n ? entry\n : { ...entry, rowIndex: index, datasetIndex: index };\n }\n return entry.rowIndex === index ? entry : { ...entry, rowIndex: index };\n });\n}\n\n/**\n * The expansion with `groupId` collapsed if it was expanded, or expanded if\n * it was collapsed.\n */\nexport function toggleGroupExpansion(\n expansion: GroupExpansionState,\n groupId: string,\n): GroupExpansionState {\n return expansion.includes(groupId)\n ? expansion.filter((id) => id !== groupId)\n : [...expansion, groupId];\n}\n\n/** `expansion` reduced to empty — every group expanded. Mirrors `clearAllFilters`. */\nexport function expandAllGroups(\n expansion: GroupExpansionState,\n): GroupExpansionState {\n return expansion.length === 0 ? expansion : [];\n}\n\n/**\n * Every group id present in `rows` — for collapsing every group currently\n * shown in one call. Operates on the already-grouped display, not the raw\n * grouping, so a group hidden beneath an already-collapsed ancestor (and so\n * absent from `rows`) keeps whatever expansion it already had rather than\n * being forced closed too; expanding that ancestor again reveals it exactly\n * as it was left.\n */\nexport function collapseAllGroups<Row>(\n rows: readonly DisplayRow<Row>[],\n): GroupExpansionState {\n return rows\n .filter((entry): entry is ResolvedGroupRow => \"kind\" in entry)\n .map((entry) => entry.groupId);\n}\n\n/**\n * `groupBy` with `columnId` positioned in front of `beforeColumnId` — or at\n * the end when that is `null` — inserting a fresh entry if `columnId` isn't\n * already part of the stack, or lifting its existing entry out and back in\n * if it is. The one function both a header dropped on the group-by bar and\n * a chip dragged within it need: the former's `columnId` is usually new to\n * the stack, the latter's never is, and this doesn't need to be told which.\n *\n * An existing entry keeps its own object (and so its own `direction`)\n * rather than being rebuilt, so a level merely repositioned stays\n * reference-equal at its new index — the same reason `moveColumnBefore`\n * moves ids rather than rebuilding them.\n *\n * Returns `groupBy` itself, untouched, when the move changes nothing: a\n * drop on the entry's own id, or back into the gap it already occupies, or\n * naming a `beforeColumnId` absent from the stack. Inserting a column not\n * yet in the stack is never a no-op.\n */\nexport function moveGroupByBefore(\n groupBy: GroupByState,\n columnId: string,\n beforeColumnId: string | null,\n): GroupByState {\n if (columnId === beforeColumnId) {\n return groupBy;\n }\n\n const without = groupBy.filter((entry) => entry.columnId !== columnId);\n const moved = groupBy.find((entry) => entry.columnId === columnId) ?? {\n columnId,\n };\n const target =\n beforeColumnId === null\n ? without.length\n : without.findIndex((entry) => entry.columnId === beforeColumnId);\n if (target === -1) {\n return groupBy;\n }\n\n const next = [...without];\n next.splice(target, 0, moved);\n return next.length === groupBy.length &&\n next.every((entry, index) => entry === groupBy[index])\n ? groupBy\n : next;\n}\n\n/**\n * Whether moving `columnId` in front of `beforeColumnId` would change\n * `groupBy` at all, so that a drop indicator promises exactly the move that\n * happens. Defined against `moveGroupByBefore` rather than restating its\n * conditions, the same relationship `movesColumn` keeps with\n * `moveColumnBefore`.\n */\nexport function movesGroupBy(\n groupBy: GroupByState,\n columnId: string,\n beforeColumnId: string | null,\n): boolean {\n return moveGroupByBefore(groupBy, columnId, beforeColumnId) !== groupBy;\n}\n","import type {\n ColumnSortState,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n SelectedCell,\n SelectedCellRef,\n SelectedColumn,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\nimport { filterRows } from \"./filtering\";\nimport { sortRows } from \"./sorting\";\n\n/**\n * `rows`, filtered then sorted — the one function `DataGrid.tsx` (and any\n * future framework binding) calls instead of composing `filterRows`/\n * `sortRows` itself.\n *\n * Filter always runs first: an O(n) pass that shrinks what the O(n log n)\n * sort has to touch whenever a filter is active, free when it isn't. This\n * is a performance choice, not a correctness one — filtering is an\n * order-preserving predicate and sort is a stable total order over whatever\n * it's handed, so the final order is identical either way.\n */\nexport function resolveShownRows<Row>(\n rows: readonly ResolvedRow<Row>[],\n filter: FilterState<Row>,\n sort: ColumnSortState,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): readonly ResolvedRow<Row>[] {\n return sortRows(filterRows(rows, filter, columns), sort, columns);\n}\n\n/**\n * `ids` resolved back to the rows behind them, keyed by `rowsById`.\n *\n * An id with no row behind it is dropped rather than left `undefined`: a\n * selection outlives a change to the data, so that filtering a row out and\n * back leaves it selected, and only what still exists can be handed to a\n * callback.\n */\nexport function resolveRows<Row>(\n rowsById: ReadonlyMap<string, ResolvedRow<Row>>,\n ids: readonly string[],\n): readonly ResolvedRow<Row>[] {\n return ids.map((id) => rowsById.get(id)).filter((row) => row !== undefined);\n}\n\n/**\n * `ids` resolved back to the columns behind them, paired with the position\n * each holds in `columns` — a stale id is dropped, matching `resolveRows`.\n */\nexport function resolveColumns<Row, Node>(\n columns: readonly ResolvedColumn<Row, Node>[],\n ids: readonly string[],\n): readonly SelectedColumn<Row, Node>[] {\n return ids\n .map((id) => {\n const columnIndex = columns.findIndex((entry) => entry.id === id);\n const column = columns[columnIndex];\n return column === undefined\n ? undefined\n : { columnId: id, column, columnIndex };\n })\n .filter((column) => column !== undefined);\n}\n\n/**\n * `cell` resolved down to the row, column and value it addresses — `null`\n * when there is no cell, or when either half of it names a row or column\n * that no longer exists.\n */\nexport function resolveCell<Row, Node>(\n rowsById: ReadonlyMap<string, ResolvedRow<Row>>,\n columns: readonly ResolvedColumn<Row, Node>[],\n cell: SelectedCellRef | null,\n): SelectedCell<Row, Node> | null {\n if (cell === null) {\n return null;\n }\n const entry = rowsById.get(cell.rowId);\n const columnIndex = columns.findIndex(\n (candidate) => candidate.id === cell.columnId,\n );\n const column = columns[columnIndex];\n if (entry === undefined || column === undefined) {\n return null;\n }\n return {\n rowId: cell.rowId,\n columnId: cell.columnId,\n row: entry.row,\n column,\n rowIndex: entry.rowIndex,\n columnIndex,\n value: accessDotted(entry.row, column.column.field),\n };\n}\n","import type { DisplayRow, PaginationState } from \"../types\";\n\nexport interface PaginatedRows<Row> {\n readonly rows: readonly DisplayRow<Row>[];\n readonly pageCount: number;\n /**\n * `pagination.pageIndex` clamped into `[0, pageCount - 1]` (or `0` when\n * `pageCount` is `0`) — read this back rather than trusting the input\n * verbatim, the same way `sortRows`/`filterRows` tolerate a stale stored\n * state.\n */\n readonly pageIndex: number;\n}\n\n/** A contiguous run of `rows` that pagination treats as one unit — a top-level group's header plus every entry beneath it, or a single bare data row. */\ninterface Unit {\n readonly start: number;\n readonly end: number; // exclusive\n}\n\n/**\n * `rows` split into pagination units: a top-level (`level === 0`) group\n * header together with every entry up to (not including) the next unit\n * boundary, or a single bare data row of its own. A nested group (`level >\n * 0`) and the data rows beneath it contribute no boundary — they're carried\n * along inside their top-level ancestor's span, which is what keeps a page\n * from ever splitting a group. A bare data row *outside* any top-level\n * group's span starts (and immediately ends) its own unit, which is what\n * makes an entirely ungrouped `rows` produce one unit per row rather than\n * collapsing into a single unit for the whole array.\n *\n * A `\"group-summary\"` row (`groupAggregateDisplay: \"row\"`) never opens a\n * boundary of its own, at any level — including a top-level one, despite\n * carrying that same `level: 0` a top-level header does. It always belongs\n * to the unit its own group already opened, the same way a nested header\n * and the data rows beneath it do.\n *\n * `openedByGroup` tracks whether the unit currently being built started at a\n * top-level header (in which case everything until the next one, headers and\n * bare rows alike, belongs to it) or at a bare row (in which case it holds\n * exactly that one row).\n */\nfunction unitsOf<Row>(rows: readonly DisplayRow<Row>[]): readonly Unit[] {\n const units: Unit[] = [];\n let start = 0;\n let openedByGroup = false;\n for (let index = 0; index < rows.length; index++) {\n const entry = rows[index];\n if (entry === undefined) {\n continue;\n }\n const isTopLevelHeader =\n \"kind\" in entry && entry.kind === \"group\" && entry.level === 0;\n const isBoundary = index !== start && (isTopLevelHeader || !openedByGroup);\n if (isBoundary) {\n units.push({ start, end: index });\n start = index;\n openedByGroup = isTopLevelHeader;\n } else if (index === start) {\n openedByGroup = isTopLevelHeader;\n }\n }\n if (start < rows.length) {\n units.push({ start, end: rows.length });\n }\n return units;\n}\n\n/**\n * `rows` sliced into pages of `pagination.pageSize` units — see `unitsOf`\n * for what a unit is. `pageIndex` is clamped into range, `pageSize <= 0` is\n * clamped to `1` (this codebase's general tolerance for stale/odd stored\n * state, matching `sortRows`/`filterRows`), and the returned page's\n * `rowIndex` is renumbered to be page-relative — 0-based from the top of the\n * page, header rows included, mirroring `groupRows`'s own renumbering\n * invariant. `datasetIndex` is left untouched: it is assigned once, ahead of\n * filter/sort/group/paginate, and reports each row's position in the whole\n * dataset regardless of which page is showing.\n */\nexport function paginateRows<Row>(\n rows: readonly DisplayRow<Row>[],\n pagination: PaginationState,\n): PaginatedRows<Row> {\n const pageSize = Math.max(1, pagination.pageSize);\n const units = unitsOf(rows);\n const pageCount = Math.ceil(units.length / pageSize);\n\n if (pageCount === 0) {\n return { rows: [], pageCount: 0, pageIndex: 0 };\n }\n\n const pageIndex = Math.min(Math.max(pagination.pageIndex, 0), pageCount - 1);\n const firstUnit = units[pageIndex * pageSize];\n const lastUnit =\n units[Math.min(pageIndex * pageSize + pageSize, units.length) - 1];\n if (firstUnit === undefined || lastUnit === undefined) {\n return { rows: [], pageCount, pageIndex };\n }\n\n const page = rows.slice(firstUnit.start, lastUnit.end);\n const renumbered = page.map((entry, index) =>\n entry.rowIndex === index ? entry : { ...entry, rowIndex: index },\n );\n\n return { rows: renumbered, pageCount, pageIndex };\n}\n","export type PaginationWindowEntry = number | \"ellipsis\";\n\n/**\n * Which page numbers a numbered pager should show for `currentPage` of\n * `pageCount`, collapsing any gap wider than one page into a single\n * `\"ellipsis\"` entry — the boundary + sibling windowing MUI's\n * `usePagination` and antd's `Pagination` both use. Always includes the\n * first/last `boundaryCount` pages and `currentPage`'s `siblingCount`\n * neighbors on each side; a gap of exactly one page is filled in rather than\n * collapsed, since an ellipsis standing in for a single page saves no space.\n */\nexport function paginationWindow(\n currentPage: number,\n pageCount: number,\n options?: {\n boundaryCount?: number | undefined;\n siblingCount?: number | undefined;\n },\n): readonly PaginationWindowEntry[] {\n if (pageCount <= 0) {\n return [];\n }\n\n const boundaryCount = Math.max(0, options?.boundaryCount ?? 1);\n const siblingCount = Math.max(0, options?.siblingCount ?? 1);\n const current = Math.min(Math.max(currentPage, 1), pageCount);\n\n const pages = new Set<number>();\n for (let page = 1; page <= Math.min(boundaryCount, pageCount); page++) {\n pages.add(page);\n }\n for (\n let page = Math.max(pageCount - boundaryCount + 1, 1);\n page <= pageCount;\n page++\n ) {\n pages.add(page);\n }\n for (\n let page = Math.max(current - siblingCount, 1);\n page <= Math.min(current + siblingCount, pageCount);\n page++\n ) {\n pages.add(page);\n }\n\n const sorted = Array.from(pages).sort((a, b) => a - b);\n const entries: PaginationWindowEntry[] = [];\n let previous: number | undefined;\n for (const page of sorted) {\n if (previous !== undefined) {\n if (page - previous === 2) {\n entries.push(previous + 1);\n } else if (page - previous > 2) {\n entries.push(\"ellipsis\");\n }\n }\n entries.push(page);\n previous = page;\n }\n\n return entries;\n}\n","import type {\n AggregateResults,\n AggregateSpec,\n AggregateState,\n BuiltInAggregate,\n ColumnDefinition,\n DisplayRow,\n GroupAggregateDisplay,\n GroupByState,\n ResolvedColumn,\n ResolvedGroupRow,\n ResolvedGroupSummaryRow,\n ResolvedRow,\n} from \"../types\";\nimport { accessDotted } from \"./grid\";\nimport { bucketKey } from \"./grouping\";\nimport { compareValues } from \"./sorting\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined;\n}\n\n/** A value's numeric reading for `sum`/`avg` — `NaN` is treated as absent, same as an empty value. */\nfunction toNumber(value: unknown): number {\n return Number(value);\n}\n\n/**\n * A key distinguishing `countDistinct`'s buckets — reuses `bucketKey`'s\n * approach in `grouping.ts` (distinct values bucketed by kind and content,\n * two `Date`s for the same instant collapsing together) rather than a\n * second implementation of the same idea.\n */\nfunction distinctKey(value: unknown): string {\n if (isEmpty(value)) {\n return \"\\0empty\";\n }\n if (value instanceof Date) {\n return `\\0date:${String(value.getTime())}`;\n }\n switch (typeof value) {\n case \"string\":\n case \"number\":\n case \"boolean\":\n case \"bigint\":\n case \"symbol\":\n return `\\0${typeof value}:${String(value)}`;\n case \"function\":\n return \"\\0function\";\n default:\n return `\\0object:${JSON.stringify(value)}`;\n }\n}\n\n/**\n * One built-in reducer over the raw values read off `rows` at `column`'s\n * field. `min`/`max` compare via `compareValues` so a `date`/`dateTime`\n * column orders chronologically rather than numerically; `sum`/`avg`\n * coerce numerically regardless of `column.type`, matching how a\n * `currency`/`decimal`/`percent`/`number` column is expected to be used.\n *\n * Null/undefined handling: `min`/`max` ignore empty entries entirely rather\n * than letting them win by `compareValues`'s own \"empty sorts last\"\n * convention — an aggregate that returned `undefined` merely because every\n * value happened to be null would be a worse answer than skipping them.\n * `count` counts every row in scope regardless of whether its value at this\n * column is empty (it answers \"how many rows\", not \"how many non-null\n * values\") — `countDistinct` excludes empty values from its distinct set,\n * since \"blank\" is not a value to count as one of the distinct ones.\n */\nfunction reduceBuiltIn<Row>(\n rows: readonly Row[],\n fn: BuiltInAggregate,\n column: ResolvedColumn<Row, unknown> | undefined,\n): unknown {\n if (fn === \"count\") {\n return rows.length;\n }\n\n const field = column?.column.field;\n const values =\n field === undefined ? [] : rows.map((row) => accessDotted(row, field));\n\n switch (fn) {\n case \"sum\": {\n let total = 0;\n for (const value of values) {\n if (isEmpty(value)) continue;\n total += toNumber(value);\n }\n return total;\n }\n case \"avg\": {\n const present = values.filter((value) => !isEmpty(value));\n if (present.length === 0) return undefined;\n const total = present.reduce(\n (acc: number, value) => acc + toNumber(value),\n 0,\n );\n return total / present.length;\n }\n case \"min\":\n case \"max\": {\n const type = column?.column.type;\n let result: unknown;\n for (const value of values) {\n if (isEmpty(value)) continue;\n if (result === undefined) {\n result = value;\n continue;\n }\n const comparison = compareValues(value, result, type);\n if (fn === \"min\" ? comparison < 0 : comparison > 0) {\n result = value;\n }\n }\n return result;\n }\n case \"countDistinct\": {\n const seen = new Set<string>();\n for (const value of values) {\n if (isEmpty(value)) continue;\n seen.add(distinctKey(value));\n }\n return seen.size;\n }\n }\n}\n\n/** This spec's result key — `id` when given, `columnId` otherwise. */\nfunction keyOf<Row>(spec: AggregateSpec<Row>): string {\n return spec.id ?? spec.columnId;\n}\n\n/**\n * `rows` reduced through every spec in `specs`, keyed by `spec.id ??\n * spec.columnId`. A custom `fn` (a plain function, not a `BuiltInAggregate`\n * name) is called once with the raw `rows` — no wrapping, no\n * pre-processing, so it sees exactly the same leaf set a built-in reducer\n * does.\n *\n * Two specs resolving to the same key: the later one wins, matching\n * `setColumnFilter`'s own silent-replace precedent for a key collision\n * rather than throwing.\n *\n * Returns a fresh empty `Map` for empty `rows` or empty `specs` — never a\n * shared singleton — matching this codebase's general avoidance of shared\n * mutable module state.\n */\nexport function computeAggregates<Row>(\n rows: readonly Row[],\n specs: AggregateState<Row>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n): AggregateResults {\n const results = new Map<string, unknown>();\n if (specs.length === 0) {\n return results;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry]));\n for (const spec of specs) {\n const value =\n typeof spec.fn === \"function\"\n ? spec.fn(rows)\n : reduceBuiltIn(rows, spec.fn, byId.get(spec.columnId));\n results.set(keyOf(spec), value);\n }\n return results;\n}\n\n/**\n * A group header's full leaf-row descendant set, found by re-filtering\n * `flatRows` (the same pre-group, filtered/sorted rows `groupRows` itself\n * partitioned) against `header.path` — each ancestor level's value matched\n * via `bucketKey`, the identical notion of \"same value\" `groupRows` uses to\n * bucket in the first place.\n *\n * Deliberately independent of `displayRows`' own flattened shape: a\n * collapsed group's descendants are never emitted into `displayRows` at\n * all (see `buildLevel`'s `if (expanded)` guard), so a header's subtotal\n * has to be recomputed from the original row list rather than walked out\n * of the grouped-and-collapsed output — the only way a collapsed group's\n * header still gets a correct count regardless of its own collapse state.\n */\nfunction leafDescendantsOf<Row>(\n flatRows: readonly ResolvedRow<Row>[],\n path: readonly unknown[],\n groupBy: GroupByState,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n): Row[] {\n const keys = path.map((value) => bucketKey(value));\n return flatRows\n .filter((row) =>\n keys.every((key, level) => {\n const columnId = groupBy[level]?.columnId;\n const column = columnId === undefined ? undefined : byId.get(columnId);\n if (column === undefined) return false;\n return bucketKey(accessDotted(row.row, column.field)) === key;\n }),\n )\n .map((row) => row.row);\n}\n\n/**\n * `displayRows` (already regrouped by `groupBy`, per `groupRows`) with\n * every group header's `aggregates` field set to `specs` computed over\n * that group's full leaf-row descendants, re-derived from `flatRows` (the\n * same pre-group rows `groupRows` was given) — regardless of the group's\n * own collapse state, since a collapsed group's header still needs a\n * correct subtotal and its descendants are otherwise absent from\n * `displayRows` entirely. Each level's `aggregates` comes from its own\n * full descendant set, computed independently rather than combined from\n * its children's already-computed results, so a non-associative custom\n * aggregate (median, distinct count) is never silently wrong at a nested\n * level.\n *\n * `display: \"row\"` additionally emits a `ResolvedGroupSummaryRow` right\n * after each group's last visible entry (its last nested group/data row,\n * or immediately after its own header when collapsed) — every level gets\n * its own, the same way every level already gets its own `aggregates`.\n * `display: \"inline\"` (the default) emits none; a header's `aggregates`\n * field is populated either way, since `GridGroupRow`'s own inline\n * rendering and a summary row both read from it.\n *\n * `rowIndex`/`datasetIndex` are renumbered across the whole output when a\n * row was inserted, the same invariant `groupRows` itself keeps.\n *\n * Returns `displayRows` itself, untouched, when `specs` is empty — the\n * same reference-equality no-op every other transform across\n * grouping/pagination uses.\n */\n/**\n * One pass over a contiguous run of `displayRows` sharing one parent — the\n * top-level call covers the whole array, and each group header found along\n * the way recurses into its own span (every entry up to the next one at\n * its level or shallower) the same way, so a group at any nesting depth\n * gets its own `aggregates` computed and, under `display: \"row\"`, its own\n * summary row appended right after everything beneath it.\n *\n * Returns the index just past the run this call consumed, so the caller\n * (itself, one level up, or `withGroupAggregates`) knows where to resume.\n */\nfunction appendAggregatedSpan<Row>(\n displayRows: readonly DisplayRow<Row>[],\n start: number,\n levelFloor: number | null,\n flatRows: readonly ResolvedRow<Row>[],\n groupBy: GroupByState,\n byId: ReadonlyMap<string, ColumnDefinition<Row, unknown>>,\n specs: AggregateState<Row>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n display: GroupAggregateDisplay,\n out: DisplayRow<Row>[],\n): number {\n let index = start;\n while (index < displayRows.length) {\n const entry = displayRows[index];\n if (entry === undefined) {\n index++;\n continue;\n }\n if (\n levelFloor !== null &&\n \"kind\" in entry &&\n entry.kind === \"group\" &&\n entry.level <= levelFloor\n ) {\n // The next entry at this level or shallower ends the current span —\n // handled by the caller's own iteration, not this one.\n break;\n }\n index++;\n if (!(\"kind\" in entry) || entry.kind !== \"group\") {\n out.push(entry);\n continue;\n }\n\n const leaves = leafDescendantsOf(flatRows, entry.path, groupBy, byId);\n const aggregates = computeAggregates(leaves, specs, columns);\n const withAggregates: ResolvedGroupRow = { ...entry, aggregates };\n out.push(withAggregates);\n\n index = appendAggregatedSpan(\n displayRows,\n index,\n entry.level,\n flatRows,\n groupBy,\n byId,\n specs,\n columns,\n display,\n out,\n );\n\n if (display === \"row\") {\n const summary: ResolvedGroupSummaryRow = {\n kind: \"group-summary\",\n groupId: entry.groupId,\n level: entry.level,\n rowIndex: -1,\n datasetIndex: -1,\n aggregates,\n };\n out.push(summary);\n }\n }\n return index;\n}\n\nexport function withGroupAggregates<Row>(\n displayRows: readonly DisplayRow<Row>[],\n flatRows: readonly ResolvedRow<Row>[],\n groupBy: GroupByState,\n specs: AggregateState<Row>,\n columns: readonly ResolvedColumn<Row, unknown>[],\n display: GroupAggregateDisplay = \"inline\",\n): readonly DisplayRow<Row>[] {\n if (specs.length === 0) {\n return displayRows;\n }\n\n const byId = new Map(columns.map((entry) => [entry.id, entry.column]));\n const out: DisplayRow<Row>[] = [];\n appendAggregatedSpan(\n displayRows,\n 0,\n null,\n flatRows,\n groupBy,\n byId,\n specs,\n columns,\n display,\n out,\n );\n\n return out.map((entry, position) => {\n if (\"kind\" in entry) {\n return entry.rowIndex === position && entry.datasetIndex === position\n ? entry\n : { ...entry, rowIndex: position, datasetIndex: position };\n }\n return entry.rowIndex === position\n ? entry\n : { ...entry, rowIndex: position };\n });\n}\n"],"mappings":"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","names":["total","test","columnId","entry","isEmpty"]}
|