@gridkitjs/core 0.6.0 → 0.8.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 CHANGED
@@ -99,6 +99,24 @@ interface ColumnDefinition<Row, Node = string> {
99
99
  reorderable?: boolean;
100
100
  /** Whether this column can be sorted, overriding the grid-level default. */
101
101
  sortable?: boolean;
102
+ /** Whether this column can be grouped by, overriding the grid-level default. */
103
+ groupable?: boolean;
104
+ /**
105
+ * Whether this column's header shows its group-toggle icon, overriding
106
+ * the grid-level default. Purely a rendering choice — it does not affect
107
+ * whether the column can be grouped by (`groupable`) or the
108
+ * `Alt+ArrowDown` shortcut, which keeps working with the icon hidden, the
109
+ * same way the resize handle stays `aria-hidden` while `Alt+ArrowLeft`/
110
+ * `ArrowRight` still resize.
111
+ */
112
+ groupToggleIcon?: boolean;
113
+ /**
114
+ * Whether this column's header may be dragged into the group-by bar to
115
+ * add it to the active grouping, overriding the grid-level default.
116
+ * Distinct from `groupable`: a column can be groupable via its header's
117
+ * click/`Alt+ArrowDown` toggle, via this drag, both, or neither.
118
+ */
119
+ groupByDraggable?: boolean;
102
120
  /**
103
121
  * Lets this column's header and/or cell text wrap onto multiple lines
104
122
  * instead of the grid's default single line with an ellipsis. Off by
@@ -153,6 +171,8 @@ interface ColumnResolveOptions {
153
171
  resizable?: boolean | undefined;
154
172
  /** Whether columns are reorderable, unless a column says otherwise. */
155
173
  reorderable?: boolean | undefined;
174
+ /** Whether columns may be dragged into the group-by bar, unless a column says otherwise. */
175
+ groupByDraggable?: boolean | undefined;
156
176
  }
157
177
  /**
158
178
  * A column paired with everything it takes to render it. Each field is a
@@ -179,6 +199,8 @@ interface ResolvedColumn<Row, Node = string> {
179
199
  resizable: boolean;
180
200
  /** Whether this column can be dragged, after the grid-level default. */
181
201
  reorderable: boolean;
202
+ /** Whether this column's header may be dragged into the group-by bar, after the grid-level default. */
203
+ groupByDraggable: boolean;
182
204
  /** How this column's cells align, after falling back to its type. */
183
205
  alignment: ColumnAlignment;
184
206
  }
@@ -242,6 +264,73 @@ interface ColumnSortEvent {
242
264
  readonly columnId: string;
243
265
  readonly sort: ColumnSortState;
244
266
  }
267
+ /** One level of a stacked group-by, outer to inner. Mirrors ColumnSortEntry. */
268
+ interface GroupByEntry {
269
+ readonly columnId: string;
270
+ /** Order of this level's own group values (not row order within a group). Defaults to "asc". */
271
+ readonly direction?: SortDirection;
272
+ }
273
+ /**
274
+ * The active grouping, outer to inner: index 0 groups first, each
275
+ * subsequent index nests inside it. Empty for an ungrouped grid, mirroring
276
+ * ColumnSortState's empty array for "no sort".
277
+ */
278
+ type GroupByState = readonly GroupByEntry[];
279
+ /**
280
+ * Reports a user group-by change whole — the stack to persist from. Mirrors
281
+ * ColumnSortEvent's `{columnId, sort}` shape.
282
+ */
283
+ interface GroupByEvent {
284
+ readonly columnId: string;
285
+ readonly groupBy: GroupByState;
286
+ }
287
+ /**
288
+ * Group keys collapsed by the user — holding only the exceptions, the same
289
+ * way ColumnSizingState holds only resized widths. A key absent from this
290
+ * set is expanded; there is no separate "collapsed by default" concept.
291
+ */
292
+ type GroupExpansionState = readonly string[];
293
+ /**
294
+ * Reports a user expansion change. `groupId` is the group toggled, or `null`
295
+ * for expandAllGroups/collapseAllGroups, which touch every group in one call
296
+ * rather than one at a time.
297
+ */
298
+ interface GroupExpansionEvent {
299
+ readonly groupId: string | null;
300
+ readonly expansion: GroupExpansionState;
301
+ }
302
+ /**
303
+ * One group header in a grouped result. `path` is every ancestor value
304
+ * down to and including this group's own, outermost first — what a header
305
+ * template needs to render "West / Enterprise", not just "Enterprise".
306
+ *
307
+ * Not parameterized by `Row`: nothing here holds a `Row`-typed value today.
308
+ * A future field that does (an aggregate) can add that parameter when it
309
+ * exists, rather than carrying an unused one now.
310
+ */
311
+ interface ResolvedGroupRow {
312
+ readonly kind: "group";
313
+ /** Stable id for this node: this plan's join of `path`, used as the React key and the GroupExpansionState entry. Opaque — build it with `groupRowId`, never construct one by hand. */
314
+ readonly groupId: string;
315
+ /** Nesting depth, 0 for a top-level group. */
316
+ readonly level: number;
317
+ readonly columnId: string;
318
+ /** This level's own value — the last entry of `path`. */
319
+ readonly value: unknown;
320
+ readonly path: readonly unknown[];
321
+ readonly expanded: boolean;
322
+ /** Leaf row count under this group, regardless of collapse state. */
323
+ readonly count: number;
324
+ /** Position among the display rows as rendered — same invariant ResolvedRow.rowIndex keeps. */
325
+ readonly rowIndex: number;
326
+ }
327
+ /**
328
+ * One entry of a grouped result: either an ordinary data row (no `kind`
329
+ * field at all — ResolvedRow is unchanged, so every existing consumer of
330
+ * plain ResolvedRow[] keeps compiling) or a group header. Discriminate with
331
+ * `"kind" in entry`.
332
+ */
333
+ type DisplayRow<Row> = ResolvedRow<Row> | ResolvedGroupRow;
245
334
  interface FilterEntryBase<Row> {
246
335
  readonly columnId?: FieldPath<Row> | (string & {});
247
336
  }
@@ -330,6 +419,11 @@ interface SelectableConfig {
330
419
  columns?: SelectionMode | undefined;
331
420
  cells?: CellSelectionMode | undefined;
332
421
  }
422
+ /**
423
+ * What an interaction means for the selection, read off its modifiers once at
424
+ * the boundary so that nothing downstream handles a raw event.
425
+ */
426
+ type SelectIntent = "replace" | "toggle" | "range";
333
427
  /** The address of one cell: which row, and which column within it. */
334
428
  interface SelectedCellRef {
335
429
  readonly rowId: string;
@@ -477,6 +571,19 @@ declare function resolveRowId<Row>(row: Row, index: number, getRowId?: (row: Row
477
571
  * reading as `"Application Id"`.
478
572
  */
479
573
  declare function resolveColumnLabel<Row, Node>(column: ColumnDefinition<Row, Node>): Node | string;
574
+ /** Which of a column header's keyboard interactions to announce. */
575
+ interface KeyShortcutCapabilities {
576
+ reorderable: boolean;
577
+ resizable: boolean;
578
+ sortable: boolean;
579
+ groupable: boolean;
580
+ }
581
+ /**
582
+ * The `aria-keyshortcuts` string for a column header, naming only the keys
583
+ * that column actually responds to — so the keys are discoverable, since
584
+ * none of them is a convention a user would try unprompted.
585
+ */
586
+ declare function buildKeyShortcuts(capabilities: KeyShortcutCapabilities): string;
480
587
  /**
481
588
  * Accesses a value at a dotted path, e.g. `foo.bar.baz`.
482
589
  *
@@ -486,6 +593,40 @@ declare function resolveColumnLabel<Row, Node>(column: ColumnDefinition<Row, Nod
486
593
  */
487
594
  declare function accessDotted(obj: unknown, path: string): unknown;
488
595
 
596
+ /**
597
+ * The header's row index. One coordinate space covers the header and the body
598
+ * so that arrowing up out of the first row reaches the header without a case
599
+ * of its own, and every other move is plain arithmetic.
600
+ */
601
+ declare const HEADER_ROW = -1;
602
+ /** The cell the grid's single tab stop sits on. */
603
+ interface GridFocus {
604
+ readonly rowIndex: number;
605
+ readonly columnIndex: number;
606
+ }
607
+ /** The modifier keys held with a navigation key press. */
608
+ interface NavigationModifiers {
609
+ readonly ctrlKey: boolean;
610
+ readonly metaKey: boolean;
611
+ readonly altKey: boolean;
612
+ readonly shiftKey: boolean;
613
+ }
614
+ /**
615
+ * Holds a coordinate inside the grid, so that a focus surviving a column
616
+ * removal or a shorter page never leaves the grid with no tab stop at all —
617
+ * which is a grid a keyboard cannot reach.
618
+ */
619
+ declare function clampFocus(focus: GridFocus, rowCount: number, columnCount: number): GridFocus;
620
+ /**
621
+ * The focus a navigation key press moves to, or `null` when the key isn't
622
+ * this grid's to handle — left for the browser or another handler.
623
+ *
624
+ * `pageSize` is a pre-measured row count rather than a callback: this
625
+ * function takes plain data only, so a caller measures the viewport once and
626
+ * hands over the number.
627
+ */
628
+ declare function nextFocusForKey(key: string, modifiers: NavigationModifiers, focus: GridFocus, rowCount: number, columnCount: number, pageSize: number): GridFocus | null;
629
+
489
630
  /**
490
631
  * Puts `columns` in the order `order` gives. Ids it lists come first, in that
491
632
  * order; columns it omits keep their position among the definitions and
@@ -519,6 +660,16 @@ declare function movesColumn(orderedIds: ColumnOrderState, movedId: string, befo
519
660
  * keeps a drop target a single nullable string.
520
661
  */
521
662
  declare function resolveDropBefore(orderedIds: ColumnOrderState, targetId: string, side: DropSide): string | null;
663
+ /**
664
+ * The `beforeId` a keyboard nudge on `columnId` produces, for `onDrop` to
665
+ * apply the same way a pointer drop does — or `undefined` when the nudge is a
666
+ * no-op: `columnId` is not in `orderedIds`, or the nudge would move it left
667
+ * of the start.
668
+ *
669
+ * `null` is a real answer here, not "no-op": it means append to the end, the
670
+ * same convention `moveColumnBefore` uses.
671
+ */
672
+ declare function resolveKeyboardDropTarget(orderedIds: ColumnOrderState, columnId: string, direction: -1 | 1): string | null | undefined;
522
673
 
523
674
  /**
524
675
  * The selection with `id` added if absent and removed if present — a
@@ -574,6 +725,18 @@ declare function selectCell(selection: CellSelectionState, cell: SelectedCellRef
574
725
  * Ctrl-click, matching `toggleSelection`.
575
726
  */
576
727
  declare function toggleCellSelection(selection: CellSelectionState, cell: SelectedCellRef, mode: CellSelectionMode): CellSelectionState;
728
+ /** The intent a click or key press carries, by the modifiers held with it. */
729
+ declare function intentOf(event: {
730
+ ctrlKey: boolean;
731
+ metaKey: boolean;
732
+ shiftKey: boolean;
733
+ }): SelectIntent;
734
+ /**
735
+ * The transform an intent asks for. A function rather than a branch inside
736
+ * each caller, so a row and a column cannot come to mean different things by
737
+ * the same click.
738
+ */
739
+ declare function applySelectionIntent(selection: SelectionState, orderedIds: readonly string[], anchorId: string | null, id: string, intent: SelectIntent, mode: SelectionMode): SelectionState;
577
740
 
578
741
  /**
579
742
  * Applied to any column that does not size itself. The minimum is a width a
@@ -634,6 +797,15 @@ declare function fitColumnsToWidth<Row, Node>(resolved: readonly ResolvedColumn<
634
797
  * exactly its content can still round down into an ellipsis.
635
798
  */
636
799
  declare function sizeColumnToContent(measuredWidth: number, constraints: ColumnConstraints, padding?: number): number;
800
+ /**
801
+ * `current` with `columnId` reverted to its width in `base` — or omitted
802
+ * entirely if `base` had none — for Escape to restore a resize in progress.
803
+ *
804
+ * A column with no stored width in `base` is correctly omitted by deleting
805
+ * the key rather than merging its `base` width back in, which would
806
+ * otherwise leave it pinned and hidden from auto-fit.
807
+ */
808
+ declare function revertColumnSize(current: ColumnSizingState, base: ColumnSizingState, columnId: string): ColumnSizingState;
637
809
 
638
810
  /**
639
811
  * Compares two cell values the way `type` implies — numerically for the
@@ -737,6 +909,82 @@ declare function clearAllFilters<Row>(filter: FilterState<Row>): FilterState<Row
737
909
  */
738
910
  declare function filterRows<Row>(rows: readonly ResolvedRow<Row>[], filter: FilterState<Row>, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
739
911
 
912
+ /**
913
+ * This group's id, from its path. Every group-id producer/consumer in this
914
+ * package goes through this — never join `path` by hand elsewhere.
915
+ *
916
+ * `JSON.stringify` rather than a delimiter join: a delimiter would have to be
917
+ * escaped in every segment to stay collision-free against a value that
918
+ * legitimately contains it, and `JSON.stringify` already draws that
919
+ * boundary between array entries for free.
920
+ */
921
+ declare function groupRowId(path: readonly unknown[]): string;
922
+ /**
923
+ * `rows`, regrouped into `groupBy`'s stacked levels — a group header per
924
+ * distinct value at each level, ordered by `compareValues` per that level's
925
+ * own `direction`, followed depth-first by its nested groups and/or data
926
+ * rows. A collapsed group (its `groupId` present in `expansion`) contributes
927
+ * only its own header, nothing beneath it — but its `count` still reports
928
+ * the group's full leaf-row count either way.
929
+ *
930
+ * Filter and sort are expected to have already run (see `resolveShownRows`):
931
+ * this only reshapes their output, and never changes which rows appear or
932
+ * their relative order within a group.
933
+ *
934
+ * `rowIndex` is renumbered across the whole flattened output, header rows
935
+ * included — the same invariant `sortRows`/`filterRows` keep for
936
+ * `ResolvedRow.rowIndex`, extended to `ResolvedGroupRow.rowIndex` since
937
+ * `GridBody` addresses both kinds by the same flat position.
938
+ *
939
+ * Returns `rows` itself, untouched, when `groupBy` is empty or names only
940
+ * columns absent from `columns` — a stored grouping tolerates a column
941
+ * removed from the definition the same way a stored `ColumnSortState` does.
942
+ */
943
+ declare function groupRows<Row>(rows: readonly ResolvedRow<Row>[], groupBy: GroupByState, expansion: GroupExpansionState, columns: readonly ResolvedColumn<Row, unknown>[]): readonly DisplayRow<Row>[];
944
+ /**
945
+ * The expansion with `groupId` collapsed if it was expanded, or expanded if
946
+ * it was collapsed.
947
+ */
948
+ declare function toggleGroupExpansion(expansion: GroupExpansionState, groupId: string): GroupExpansionState;
949
+ /** `expansion` reduced to empty — every group expanded. Mirrors `clearAllFilters`. */
950
+ declare function expandAllGroups(expansion: GroupExpansionState): GroupExpansionState;
951
+ /**
952
+ * Every group id present in `rows` — for collapsing every group currently
953
+ * shown in one call. Operates on the already-grouped display, not the raw
954
+ * grouping, so a group hidden beneath an already-collapsed ancestor (and so
955
+ * absent from `rows`) keeps whatever expansion it already had rather than
956
+ * being forced closed too; expanding that ancestor again reveals it exactly
957
+ * as it was left.
958
+ */
959
+ declare function collapseAllGroups<Row>(rows: readonly DisplayRow<Row>[]): GroupExpansionState;
960
+ /**
961
+ * `groupBy` with `columnId` positioned in front of `beforeColumnId` — or at
962
+ * the end when that is `null` — inserting a fresh entry if `columnId` isn't
963
+ * already part of the stack, or lifting its existing entry out and back in
964
+ * if it is. The one function both a header dropped on the group-by bar and
965
+ * a chip dragged within it need: the former's `columnId` is usually new to
966
+ * the stack, the latter's never is, and this doesn't need to be told which.
967
+ *
968
+ * An existing entry keeps its own object (and so its own `direction`)
969
+ * rather than being rebuilt, so a level merely repositioned stays
970
+ * reference-equal at its new index — the same reason `moveColumnBefore`
971
+ * moves ids rather than rebuilding them.
972
+ *
973
+ * Returns `groupBy` itself, untouched, when the move changes nothing: a
974
+ * drop on the entry's own id, or back into the gap it already occupies, or
975
+ * naming a `beforeColumnId` absent from the stack. Inserting a column not
976
+ * yet in the stack is never a no-op.
977
+ */
978
+ declare function moveGroupByBefore(groupBy: GroupByState, columnId: string, beforeColumnId: string | null): GroupByState;
979
+ /**
980
+ * Whether moving `columnId` in front of `beforeColumnId` would change
981
+ * `groupBy` at all, so that a drop indicator promises exactly the move that
982
+ * happens. Defined against `moveGroupByBefore` rather than restating its
983
+ * conditions, the same relationship `movesColumn` keeps with
984
+ * `moveColumnBefore`.
985
+ */
986
+ declare function movesGroupBy(groupBy: GroupByState, columnId: string, beforeColumnId: string | null): boolean;
987
+
740
988
  /**
741
989
  * `rows`, filtered then sorted — the one function `DataGrid.tsx` (and any
742
990
  * future framework binding) calls instead of composing `filterRows`/
@@ -749,5 +997,25 @@ declare function filterRows<Row>(rows: readonly ResolvedRow<Row>[], filter: Filt
749
997
  * it's handed, so the final order is identical either way.
750
998
  */
751
999
  declare function resolveShownRows<Row>(rows: readonly ResolvedRow<Row>[], filter: FilterState<Row>, sort: ColumnSortState, columns: readonly ResolvedColumn<Row, unknown>[]): readonly ResolvedRow<Row>[];
1000
+ /**
1001
+ * `ids` resolved back to the rows behind them, keyed by `rowsById`.
1002
+ *
1003
+ * An id with no row behind it is dropped rather than left `undefined`: a
1004
+ * selection outlives a change to the data, so that filtering a row out and
1005
+ * back leaves it selected, and only what still exists can be handed to a
1006
+ * callback.
1007
+ */
1008
+ declare function resolveRows<Row>(rowsById: ReadonlyMap<string, ResolvedRow<Row>>, ids: readonly string[]): readonly ResolvedRow<Row>[];
1009
+ /**
1010
+ * `ids` resolved back to the columns behind them, paired with the position
1011
+ * each holds in `columns` — a stale id is dropped, matching `resolveRows`.
1012
+ */
1013
+ declare function resolveColumns<Row, Node>(columns: readonly ResolvedColumn<Row, Node>[], ids: readonly string[]): readonly SelectedColumn<Row, Node>[];
1014
+ /**
1015
+ * `cell` resolved down to the row, column and value it addresses — `null`
1016
+ * when there is no cell, or when either half of it names a row or column
1017
+ * that no longer exists.
1018
+ */
1019
+ declare function resolveCell<Row, Node>(rowsById: ReadonlyMap<string, ResolvedRow<Row>>, columns: readonly ResolvedColumn<Row, Node>[], cell: SelectedCellRef | null): SelectedCell<Row, Node> | null;
752
1020
 
753
- export { 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 DropSide, type FieldPath, type FilterEntry, type FilterPredicate, type FilterState, type GroupFilterEntry, KEYBOARD_STEP, type PredicateFilterEntry, type ResolvedColumn, type ResolvedRow, type RowSelectEvent, type RowSelectionChangeEvent, type RowsSelectEvent, type SelectableConfig, type SelectedCell, type SelectedCellRef, type SelectedColumn, type SelectionDiff, type SelectionMode, type SelectionState, type SortDirection, type TextFilterEntry, type ValueFilterEntry, accessDotted, alignmentForType, applyColumnOrder, applyColumnResize, beginColumnResize, clampColumnWidth, clearAllFilters, clearSelection, compareValues, defineColumnsFromRows, diffSelection, filterQueryFor, filterRows, fitColumnsToWidth, getColumnId, isSameCell, matchesQuery, moveColumnBefore, movesColumn, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, resolveDropBefore, resolveRowId, resolveShownRows, selectAll, selectCell, selectOnly, selectRange, setColumnFilter, sizeColumnToContent, sortDirectionFor, sortPriorityFor, sortRows, toggleCellSelection, toggleColumnSort, toggleSelection, totalColumnWidth };
1021
+ export { 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 GridFocus, type GroupByEntry, type GroupByEvent, type GroupByState, type GroupExpansionEvent, type GroupExpansionState, type GroupFilterEntry, HEADER_ROW, KEYBOARD_STEP, type KeyShortcutCapabilities, type NavigationModifiers, type PredicateFilterEntry, type ResolvedColumn, type ResolvedGroupRow, 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, defineColumnsFromRows, diffSelection, expandAllGroups, filterQueryFor, filterRows, fitColumnsToWidth, getColumnId, groupRowId, groupRows, intentOf, isSameCell, matchesQuery, moveColumnBefore, moveGroupByBefore, movesColumn, movesGroupBy, nextFocusForKey, 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 };
package/dist/index.js CHANGED
@@ -70,10 +70,65 @@ function resolveColumnLabel(column) {
70
70
  }
71
71
  return typeof headerTemplate === "function" ? headerTemplate() : headerTemplate;
72
72
  }
73
+ function buildKeyShortcuts(capabilities) {
74
+ return [
75
+ capabilities.reorderable ? "Control+ArrowLeft Control+ArrowRight" : "",
76
+ // Alt+Enter is the keyboard equivalent of double-clicking the resize
77
+ // handle to size the column to its content — same gate as the nudge
78
+ // shortcuts, since both are resize actions.
79
+ capabilities.resizable ? "Alt+ArrowLeft Alt+ArrowRight Alt+Enter" : "",
80
+ capabilities.sortable ? "Alt+ArrowUp" : "",
81
+ // Down rather than reusing Up: it reads as "drill into groups", and Up is
82
+ // already sort's own mnemonic on the same header.
83
+ capabilities.groupable ? "Alt+ArrowDown" : ""
84
+ ].filter(Boolean).join(" ");
85
+ }
73
86
  function accessDotted(obj, path) {
74
87
  return path.split(".").reduce((value, key) => isNested(value) ? value[key] : void 0, obj);
75
88
  }
76
89
 
90
+ // src/util/navigation.ts
91
+ var HEADER_ROW = -1;
92
+ function clampFocus(focus, rowCount, columnCount) {
93
+ return {
94
+ rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),
95
+ columnIndex: Math.min(
96
+ Math.max(focus.columnIndex, 0),
97
+ Math.max(columnCount - 1, 0)
98
+ )
99
+ };
100
+ }
101
+ function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize) {
102
+ if (modifiers.altKey) {
103
+ return null;
104
+ }
105
+ const { rowIndex, columnIndex } = focus;
106
+ switch (key) {
107
+ case "ArrowLeft":
108
+ if (modifiers.ctrlKey) return null;
109
+ return { rowIndex, columnIndex: columnIndex - 1 };
110
+ case "ArrowRight":
111
+ if (modifiers.ctrlKey) return null;
112
+ return { rowIndex, columnIndex: columnIndex + 1 };
113
+ case "ArrowUp":
114
+ return { rowIndex: rowIndex - 1, columnIndex };
115
+ case "ArrowDown":
116
+ return { rowIndex: rowIndex + 1, columnIndex };
117
+ // Ctrl takes Home and End to the grid's ends rather than the row's, which
118
+ // is the one place Ctrl is navigation rather than reorder.
119
+ case "Home":
120
+ return modifiers.ctrlKey ? { rowIndex: HEADER_ROW, columnIndex: 0 } : { rowIndex, columnIndex: 0 };
121
+ case "End":
122
+ return modifiers.ctrlKey ? { rowIndex: rowCount - 1, columnIndex: columnCount - 1 } : { rowIndex, columnIndex: columnCount - 1 };
123
+ case "PageUp":
124
+ return { rowIndex: rowIndex - pageSize, columnIndex };
125
+ case "PageDown":
126
+ return { rowIndex: rowIndex + pageSize, columnIndex };
127
+ default:
128
+ return null;
129
+ }
130
+ }
131
+
77
132
  // src/util/ordering.ts
78
133
  function applyColumnOrder(columns, order) {
79
134
  if (order.length === 0) {
@@ -110,6 +165,17 @@ function resolveDropBefore(orderedIds, targetId, side) {
110
165
  const index = orderedIds.indexOf(targetId);
111
166
  return index === -1 ? null : orderedIds[index + 1] ?? null;
112
167
  }
168
+ function resolveKeyboardDropTarget(orderedIds, columnId, direction) {
169
+ const index = orderedIds.indexOf(columnId);
170
+ if (index === -1) {
171
+ return void 0;
172
+ }
173
+ const targetIndex = direction === -1 ? index - 1 : index + 2;
174
+ if (targetIndex < 0) {
175
+ return void 0;
176
+ }
177
+ return orderedIds[targetIndex] ?? null;
178
+ }
113
179
 
114
180
  // src/util/selection.ts
115
181
  var NO_CHANGE = { added: [], removed: [] };
@@ -184,6 +250,24 @@ function toggleCellSelection(selection, cell, mode) {
184
250
  }
185
251
  return isSameCell(selection, cell) ? null : cell;
186
252
  }
253
+ function intentOf(event) {
254
+ if (event.shiftKey) {
255
+ return "range";
256
+ }
257
+ return event.ctrlKey || event.metaKey ? "toggle" : "replace";
258
+ }
259
+ function applySelectionIntent(selection, orderedIds, anchorId, id, intent, mode) {
260
+ switch (intent) {
261
+ case "toggle":
262
+ return toggleSelection(selection, id, mode);
263
+ // A range with no anchor yet spans from the id to itself, which is the
264
+ // plain click a first Shift-click may as well be.
265
+ case "range":
266
+ return selectRange(selection, orderedIds, anchorId ?? id, id, mode);
267
+ default:
268
+ return selectOnly(selection, id, mode);
269
+ }
270
+ }
187
271
 
188
272
  // src/util/sizing.ts
189
273
  var DEFAULT_COLUMN_SIZES = {
@@ -221,6 +305,7 @@ function resolveColumnWidths(columns, sizing, options) {
221
305
  label: resolveColumnLabel(column),
222
306
  resizable: column.resizable ?? options?.resizable ?? false,
223
307
  reorderable: column.reorderable ?? options?.reorderable ?? false,
308
+ groupByDraggable: column.groupByDraggable ?? options?.groupByDraggable ?? false,
224
309
  alignment: column.alignment ?? alignmentForType(column.type ?? "string")
225
310
  };
226
311
  });
@@ -314,6 +399,15 @@ function fitColumnsToWidth(resolved, availableWidth, defaults) {
314
399
  function sizeColumnToContent(measuredWidth, constraints, padding = 2) {
315
400
  return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);
316
401
  }
402
+ function revertColumnSize(current, base, columnId) {
403
+ const next = Object.fromEntries(
404
+ Object.entries(current).filter(([id]) => id !== columnId)
405
+ );
406
+ if (columnId in base) {
407
+ next[columnId] = base[columnId];
408
+ }
409
+ return next;
410
+ }
317
411
 
318
412
  // src/util/sorting.ts
319
413
  function isEmpty(value) {
@@ -565,38 +659,209 @@ function filterRows(rows, filter, columns) {
565
659
  );
566
660
  }
567
661
 
662
+ // src/util/grouping.ts
663
+ function groupRowId(path) {
664
+ return JSON.stringify(path);
665
+ }
666
+ function bucketKey(value) {
667
+ if (value === null || value === void 0) {
668
+ return "\0empty";
669
+ }
670
+ if (value instanceof Date) {
671
+ return `\0date:${String(value.getTime())}`;
672
+ }
673
+ switch (typeof value) {
674
+ case "string":
675
+ case "number":
676
+ case "boolean":
677
+ case "bigint":
678
+ case "symbol":
679
+ return `\0${typeof value}:${String(value)}`;
680
+ case "function":
681
+ return "\0function";
682
+ default:
683
+ return `\0object:${JSON.stringify(value)}`;
684
+ }
685
+ }
686
+ function partitionByValue(rows, column, direction) {
687
+ const buckets = /* @__PURE__ */ new Map();
688
+ for (const row of rows) {
689
+ const value = accessDotted(row.row, column.field);
690
+ const key = bucketKey(value);
691
+ const bucket = buckets.get(key);
692
+ if (bucket === void 0) {
693
+ buckets.set(key, { value, rows: [row] });
694
+ } else {
695
+ bucket.rows.push(row);
696
+ }
697
+ }
698
+ const groups = [...buckets.values()];
699
+ groups.sort((a, b) => {
700
+ const result = compareValues(a.value, b.value, column.type);
701
+ return direction === "asc" ? result : -result;
702
+ });
703
+ return groups;
704
+ }
705
+ function buildLevel(rows, levels, parentPath, expansion, byId, out) {
706
+ const level = levels[0];
707
+ if (level === void 0) {
708
+ out.push(...rows);
709
+ return;
710
+ }
711
+ const column = byId.get(level.columnId);
712
+ if (column === void 0) {
713
+ out.push(...rows);
714
+ return;
715
+ }
716
+ const rest = levels.slice(1);
717
+ const groups = partitionByValue(rows, column, level.direction ?? "asc");
718
+ for (const group of groups) {
719
+ const path = [...parentPath, group.value];
720
+ const groupId = groupRowId(path);
721
+ const expanded = !expansion.includes(groupId);
722
+ const header = {
723
+ kind: "group",
724
+ groupId,
725
+ level: parentPath.length,
726
+ columnId: level.columnId,
727
+ value: group.value,
728
+ path,
729
+ expanded,
730
+ count: group.rows.length,
731
+ // Renumbered globally once `groupRows` has the whole flattened array.
732
+ rowIndex: -1
733
+ };
734
+ out.push(header);
735
+ if (expanded) {
736
+ buildLevel(group.rows, rest, path, expansion, byId, out);
737
+ }
738
+ }
739
+ }
740
+ function groupRows(rows, groupBy, expansion, columns) {
741
+ if (groupBy.length === 0) {
742
+ return rows;
743
+ }
744
+ const byId = new Map(columns.map((entry) => [entry.id, entry.column]));
745
+ const active = groupBy.filter((entry) => byId.has(entry.columnId));
746
+ if (active.length === 0) {
747
+ return rows;
748
+ }
749
+ const out = [];
750
+ buildLevel(rows, active, [], expansion, byId, out);
751
+ return out.map(
752
+ (entry, index) => entry.rowIndex === index ? entry : { ...entry, rowIndex: index }
753
+ );
754
+ }
755
+ function toggleGroupExpansion(expansion, groupId) {
756
+ return expansion.includes(groupId) ? expansion.filter((id) => id !== groupId) : [...expansion, groupId];
757
+ }
758
+ function expandAllGroups(expansion) {
759
+ return expansion.length === 0 ? expansion : [];
760
+ }
761
+ function collapseAllGroups(rows) {
762
+ return rows.filter((entry) => "kind" in entry).map((entry) => entry.groupId);
763
+ }
764
+ function moveGroupByBefore(groupBy, columnId, beforeColumnId) {
765
+ if (columnId === beforeColumnId) {
766
+ return groupBy;
767
+ }
768
+ const without = groupBy.filter((entry) => entry.columnId !== columnId);
769
+ const moved = groupBy.find((entry) => entry.columnId === columnId) ?? {
770
+ columnId
771
+ };
772
+ const target = beforeColumnId === null ? without.length : without.findIndex((entry) => entry.columnId === beforeColumnId);
773
+ if (target === -1) {
774
+ return groupBy;
775
+ }
776
+ const next = [...without];
777
+ next.splice(target, 0, moved);
778
+ return next.length === groupBy.length && next.every((entry, index) => entry === groupBy[index]) ? groupBy : next;
779
+ }
780
+ function movesGroupBy(groupBy, columnId, beforeColumnId) {
781
+ return moveGroupByBefore(groupBy, columnId, beforeColumnId) !== groupBy;
782
+ }
783
+
568
784
  // src/util/rows.ts
569
785
  function resolveShownRows(rows, filter, sort, columns) {
570
786
  return sortRows(filterRows(rows, filter, columns), sort, columns);
571
787
  }
788
+ function resolveRows(rowsById, ids) {
789
+ return ids.map((id) => rowsById.get(id)).filter((row) => row !== void 0);
790
+ }
791
+ function resolveColumns(columns, ids) {
792
+ return ids.map((id) => {
793
+ const columnIndex = columns.findIndex((entry) => entry.id === id);
794
+ const column = columns[columnIndex];
795
+ return column === void 0 ? void 0 : { columnId: id, column, columnIndex };
796
+ }).filter((column) => column !== void 0);
797
+ }
798
+ function resolveCell(rowsById, columns, cell) {
799
+ if (cell === null) {
800
+ return null;
801
+ }
802
+ const entry = rowsById.get(cell.rowId);
803
+ const columnIndex = columns.findIndex(
804
+ (candidate) => candidate.id === cell.columnId
805
+ );
806
+ const column = columns[columnIndex];
807
+ if (entry === void 0 || column === void 0) {
808
+ return null;
809
+ }
810
+ return {
811
+ rowId: cell.rowId,
812
+ columnId: cell.columnId,
813
+ row: entry.row,
814
+ column,
815
+ rowIndex: entry.rowIndex,
816
+ columnIndex,
817
+ value: accessDotted(entry.row, column.column.field)
818
+ };
819
+ }
572
820
  export {
573
821
  DEFAULT_COLUMN_SIZES,
822
+ HEADER_ROW,
574
823
  KEYBOARD_STEP,
575
824
  accessDotted,
576
825
  alignmentForType,
577
826
  applyColumnOrder,
578
827
  applyColumnResize,
828
+ applySelectionIntent,
579
829
  beginColumnResize,
830
+ buildKeyShortcuts,
580
831
  clampColumnWidth,
832
+ clampFocus,
581
833
  clearAllFilters,
582
834
  clearSelection,
835
+ collapseAllGroups,
583
836
  compareValues,
584
837
  defineColumnsFromRows,
585
838
  diffSelection,
839
+ expandAllGroups,
586
840
  filterQueryFor,
587
841
  filterRows,
588
842
  fitColumnsToWidth,
589
843
  getColumnId,
844
+ groupRowId,
845
+ groupRows,
846
+ intentOf,
590
847
  isSameCell,
591
848
  matchesQuery,
592
849
  moveColumnBefore,
850
+ moveGroupByBefore,
593
851
  movesColumn,
852
+ movesGroupBy,
853
+ nextFocusForKey,
854
+ resolveCell,
594
855
  resolveColumnConstraints,
595
856
  resolveColumnLabel,
596
857
  resolveColumnWidths,
858
+ resolveColumns,
597
859
  resolveDropBefore,
860
+ resolveKeyboardDropTarget,
598
861
  resolveRowId,
862
+ resolveRows,
599
863
  resolveShownRows,
864
+ revertColumnSize,
600
865
  selectAll,
601
866
  selectCell,
602
867
  selectOnly,
@@ -608,6 +873,7 @@ export {
608
873
  sortRows,
609
874
  toggleCellSelection,
610
875
  toggleColumnSort,
876
+ toggleGroupExpansion,
611
877
  toggleSelection,
612
878
  totalColumnWidth
613
879
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/util/grid.ts","../src/util/ordering.ts","../src/util/selection.ts","../src/util/sizing.ts","../src/util/sorting.ts","../src/util/filtering.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/**\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","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","import type {\n CellSelectionMode,\n CellSelectionState,\n SelectedCellRef,\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","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 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","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 ColumnSortState,\n FilterState,\n ResolvedColumn,\n ResolvedRow,\n} from \"../types\";\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"],"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"],"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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridkitjs/core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Framework-agnostic data-grid logic: column resolution, alignment and sizing.",
5
5
  "license": "MIT",
6
6
  "author": "Blagoja Blazhevski",