@jielga/tmdatagrid 2.1.1 → 2.1.2

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.
@@ -14,7 +14,7 @@ The menu shows each group only when the column supports it:
14
14
 
15
15
  | Item | Shown when |
16
16
  | --- | --- |
17
- | Sort by ASC · Sort by DESC | The column can sort. See [Sorting](/docs/sorting) |
17
+ | Sort ascending · Sort descending | The column can sort. See [Sorting](/docs/sorting) |
18
18
  | Filter | The column can filter and `filters.inHeader` is off. See [Filtering](/docs/filtering) |
19
19
  | Group by · Ungroup | The column can group. See [Grouping](/docs/grouping) |
20
20
  | Expand all groups · Collapse all groups | Grouping is active |
@@ -38,7 +38,7 @@ grammar requires:
38
38
 
39
39
  ```tsx
40
40
  labels: {
41
- groupBy: (column) => `Gruppera på ${column}`,
41
+ groupBy: (column) => `Gruppera efter ${column}`,
42
42
  pageRange: ({ from, to, total }) => `${from}–${to} av ${total}`,
43
43
  }
44
44
  ```
@@ -44,6 +44,20 @@ every panel at once.
44
44
 
45
45
  Group rows get no chevron: they expand into their children from the tree lane.
46
46
 
47
+ To give only some rows a panel, set `getRowCanExpand`:
48
+
49
+ ```tsx
50
+ const grid = useTMDataGrid({
51
+ data,
52
+ columns,
53
+ renderDetails: ({ row }) => <Notes notes={row.original.notes} />,
54
+ getRowCanExpand: (row) => row.original.notes.length > 0,
55
+ });
56
+ ```
57
+
58
+ A row it turns down gets no chevron, and the header control skips it.
59
+ It never applies to a group row.
60
+
47
61
  ## Opening a row from elsewhere
48
62
 
49
63
  Which rows are open is TanStack's own `expanded` state, so anything can open
@@ -113,6 +127,7 @@ Group rows have no panel. Expanding one opens its children.
113
127
  | --- | --- | --- | --- | --- |
114
128
  | `renderDetails` | Option | `({ row, table }) => ReactNode` | – | Contents of the panel. Setting it adds the lane. |
115
129
  | `TMDataGridDetailsArgs` | Type | `{ row, table }` | – | What `renderDetails` receives. |
130
+ | `getRowCanExpand` | Table option | `(row) => boolean` | – | Determines which data rows have a panel. If not set, every data row has one. Group rows always expand. |
116
131
  | `renderDetailsEstHeight` | Option | `number` | `160` | Height the virtualizer assumes for an unmeasured panel. |
117
132
  | `detailsColumnPosition` | Option | `"left" \| "right"` | `"left"` | Which edge the lane is pinned to. `"right"` places it after every column, inside the edit lane. |
118
133
  | `initialState.expanded` | Table option | `ExpandedState` | `{}` | Rows open at mount. A data slice, so it persists. |
@@ -40,6 +40,11 @@ focusable in this mode, and Space or Enter toggles the focused row.
40
40
  selection in any mode. It has no effect under `"highlight"`, which selects
41
41
  nothing.
42
42
 
43
+ As a predicate, `enableRowSelection` decides row by row:
44
+
45
+ - a row it turns down has a disabled checkbox, and the header checkbox does not tick it
46
+ - unticking the header checkbox clears every row the filters show, including a turned-down row ticked through `initialState` or `setRowSelection`
47
+
43
48
  ## The highlighted row
44
49
 
45
50
  The highlighted row is state of its own rather than a slice of `rowSelection`,
package/docs/testing.md CHANGED
@@ -310,7 +310,11 @@ export class DataGrid {
310
310
  await this.part("search").fill(text);
311
311
  }
312
312
 
313
- /** Clicks a sortable header once: unsorted, ascending, descending. */
313
+ /**
314
+ * Clicks a sortable header once. Each click steps the sort: off, first
315
+ * direction, the other direction, off. The first direction is ascending for
316
+ * text and descending for numbers, unless the column sets `sortDescFirst`.
317
+ */
314
318
  async sortBy(columnId: string): Promise<void> {
315
319
  // The header, not its `header-sort` arrow: the arrow is display: none
316
320
  // until the header is hovered, and a click on it also reaches the
@@ -134,7 +134,7 @@ where its grammar requires:
134
134
 
135
135
  ```tsx
136
136
  labels: {
137
- groupBy: (column) => `Gruppera på ${column}`,
137
+ groupBy: (column) => `Gruppera efter ${column}`,
138
138
  pageRange: ({ from, to, total }) => `${from}–${to} av ${total}`,
139
139
  }
140
140
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "A React data grid built on TanStack Table v9 and Mantine - always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,7 +18,7 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.1.1'
21
+ library_version: '2.1.2'
22
22
  sources:
23
23
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/styling.md'
24
24
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/toolbar.md'
@@ -242,7 +242,7 @@ where its grammar requires:
242
242
 
243
243
  ```tsx
244
244
  const labels = {
245
- groupBy: (column) => `Gruppera på ${column}`,
245
+ groupBy: (column) => `Gruppera efter ${column}`,
246
246
  pageRange: ({ from, to, total }) => `${from}–${to} av ${total}`,
247
247
  } satisfies TMDataGridLabelsOverride;
248
248
  ```
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '2.1.1'
17
+ library_version: '2.1.2'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/cell-selection.md'
20
20
  - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/cellNavigation.ts'
@@ -16,7 +16,7 @@ description: >
16
16
  metadata:
17
17
  type: core
18
18
  library: '@jielga/tmdatagrid'
19
- library_version: '2.1.1'
19
+ library_version: '2.1.2'
20
20
  sources:
21
21
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/columns.md'
22
22
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/column-layout.md'
@@ -18,7 +18,7 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.1.1'
21
+ library_version: '2.1.2'
22
22
  sources:
23
23
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/pagination.md'
24
24
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/scrolling.md'
@@ -18,7 +18,7 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.1.1'
21
+ library_version: '2.1.2'
22
22
  sources:
23
23
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/editing.md'
24
24
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/draft-store.md'
@@ -18,7 +18,7 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.1.1'
21
+ library_version: '2.1.2'
22
22
  sources:
23
23
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/filtering.md'
24
24
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/quick-search.md'
@@ -13,7 +13,7 @@ description: >
13
13
  metadata:
14
14
  type: core
15
15
  library: '@jielga/tmdatagrid'
16
- library_version: '2.1.1'
16
+ library_version: '2.1.2'
17
17
  sources:
18
18
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/getting-started.md'
19
19
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/anatomy.md'
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '2.1.1'
17
+ library_version: '2.1.2'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/grouping.md'
20
20
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/summary-row.md'
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: lifecycle
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '2.1.1'
18
+ library_version: '2.1.2'
19
19
  sources:
20
20
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/migrating-to-2.md'
21
21
  - 'Jielga/TMDataGrid:packages/tmdatagrid/src/index.ts'
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '2.1.1'
17
+ library_version: '2.1.2'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/use-tm-data-grid.md'
20
20
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/persistence.md'
@@ -17,7 +17,7 @@ description: >
17
17
  metadata:
18
18
  type: core
19
19
  library: '@jielga/tmdatagrid'
20
- library_version: '2.1.1'
20
+ library_version: '2.1.2'
21
21
  sources:
22
22
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/row-selection.md'
23
23
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/row-interaction.md'
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: core
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '2.1.1'
18
+ library_version: '2.1.2'
19
19
  sources:
20
20
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/server-side.md'
21
21
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/server-query.md'
@@ -13,7 +13,7 @@ description: >
13
13
  metadata:
14
14
  type: core
15
15
  library: '@jielga/tmdatagrid'
16
- library_version: '2.1.1'
16
+ library_version: '2.1.2'
17
17
  sources:
18
18
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/testing.md'
19
19
  - 'Jielga/TMDataGrid:playwright/support/DataGrid.ts'
@@ -12,7 +12,7 @@ description: >
12
12
  metadata:
13
13
  type: core
14
14
  library: '@jielga/tmdatagrid'
15
- library_version: '2.1.1'
15
+ library_version: '2.1.2'
16
16
  sources:
17
17
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/testing.md'
18
18
  - 'Jielga/TMDataGrid:packages/tmdatagrid/test/gallery/main.tsx'
@@ -12,7 +12,7 @@ description: >
12
12
  metadata:
13
13
  type: core
14
14
  library: '@jielga/tmdatagrid'
15
- library_version: '2.1.1'
15
+ library_version: '2.1.2'
16
16
  sources:
17
17
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/testing.md'
18
18
  - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/editing.md'
@@ -33,6 +33,9 @@ export function DetailsCell<TData extends RowData>({
33
33
  // `showsDetails`. Their cells are aggregated, so this lane is already blank
34
34
  // on them; the guard is what makes that a decision rather than a side effect.
35
35
  if (row.getIsGrouped()) return null;
36
+ // A record the consumer's `getRowCanExpand` turns down has no panel, and
37
+ // TanStack refuses to expand it - a chevron here would do nothing.
38
+ if (!row.getCanExpand()) return null;
36
39
 
37
40
  return (
38
41
  <UnstyledButton
@@ -50,7 +50,21 @@ function SelectAllCheckbox<TData extends RowData>({
50
50
  data-dg-part="select-all"
51
51
  checked={allSelected}
52
52
  indeterminate={someSelected && !allSelected}
53
- onChange={table.getToggleAllRowsSelectedHandler()}
53
+ onChange={(event) => {
54
+ if (event.currentTarget.checked) {
55
+ table.toggleAllRowsSelected(true);
56
+ return;
57
+ }
58
+ // Not `toggleAllRowsSelected(false)`: since TanStack 9.2 it keeps a
59
+ // ticked row that `enableRowSelection` turns down, so the box could
60
+ // never clear. Every row the filters show is unticked; rows a filter
61
+ // hides keep their tick, as they always have.
62
+ const next = { ...table.store.state.rowSelection };
63
+ for (const row of table.getPreGroupedRowModel().flatRows) {
64
+ delete next[row.id];
65
+ }
66
+ table.setRowSelection(next);
67
+ }}
54
68
  />
55
69
  );
56
70
  }
@@ -1531,6 +1531,24 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
1531
1531
  const trailingGuardRef = useRef<HTMLDivElement>(null);
1532
1532
  const guardHopRef = useRef(false);
1533
1533
 
1534
+ /**
1535
+ * Keeps both guards at the scrollport's top-left corner as the body scrolls.
1536
+ *
1537
+ * A guard is out of flow, so left alone it sits at the top of the scrolled
1538
+ * content. Tab from outside focuses it without `preventScroll`, and the
1539
+ * browser then scrolls the body back to the top to show it - which unmounts
1540
+ * the cursor row the guard is about to hand the focus to, and drops the
1541
+ * focus on `<body>`. A guard already inside the scrollport is never
1542
+ * scrolled to. Written straight to the style, so a scroll costs no render.
1543
+ */
1544
+ const placeTabGuards = (container: HTMLElement) => {
1545
+ for (const guard of [leadingGuardRef.current, trailingGuardRef.current]) {
1546
+ if (guard === null) continue;
1547
+ guard.style.top = `${container.scrollTop}px`;
1548
+ guard.style.left = `${container.scrollLeft}px`;
1549
+ }
1550
+ };
1551
+
1534
1552
  // O(n) per keystroke and per render while a cell is focused. Fine at any row
1535
1553
  // count a grid actually holds, and the alternative - an id→index map rebuilt
1536
1554
  // whenever sorting, filtering or grouping changes - costs more than it saves.
@@ -2448,8 +2466,9 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
2448
2466
  * zoom, where a "fully scrolled" position can land at 799.5 of 800.
2449
2467
  */
2450
2468
  const handleScroll =
2451
- contextMenuTarget || hasEdgeCallbacks
2469
+ contextMenuTarget || hasEdgeCallbacks || cellSelection
2452
2470
  ? (event: UIEvent<HTMLDivElement>) => {
2471
+ if (cellSelection) placeTabGuards(event.currentTarget);
2453
2472
  if (contextMenuTarget) closeContextMenu();
2454
2473
  if (!hasEdgeCallbacks) return;
2455
2474
  const el = event.currentTarget;
@@ -20,7 +20,10 @@ function isTarget<TData extends RowData>(
20
20
  row: Row<TMDataGridFeatures, TData>,
21
21
  target: TMDataGridExpandTarget,
22
22
  ): boolean {
23
- return target === "groups" ? row.getIsGrouped() : !row.getIsGrouped();
23
+ // A record a consumer's `getRowCanExpand` turns down has no panel to open.
24
+ return target === "groups"
25
+ ? row.getIsGrouped()
26
+ : !row.getIsGrouped() && row.getCanExpand();
24
27
  }
25
28
 
26
29
  /**
@@ -236,8 +236,8 @@ export const TMDATAGRID_LABELS_EN: TMDataGridLabels = {
236
236
  activeFilters: "Active filters",
237
237
  clearFilter: (column) => `Clear ${column} filter`,
238
238
 
239
- sortAsc: "Sort by ASC",
240
- sortDesc: "Sort by DESC",
239
+ sortAsc: "Sort ascending",
240
+ sortDesc: "Sort descending",
241
241
  filterMenuItem: "Filter",
242
242
  groupBy: (column) => `Group by ${column}`,
243
243
  ungroup: (column) => `Ungroup ${column}`,
@@ -260,7 +260,7 @@ export const TMDATAGRID_LABELS_EN: TMDataGridLabels = {
260
260
  pageCount < 0 ? `Page ${page}` : `Page ${page} of ${pageCount}`,
261
261
  groupedAllRows: (total) => `Grouped · all ${total} rows`,
262
262
  pagingSuspendedHint:
263
- "Paging is off while the rows are grouped: the whole tree is rendered and virtualized. Ungroup to page again.",
263
+ "Paging is off while the rows are grouped, and every row is shown. Ungroup to page again.",
264
264
  previousPage: "Previous page",
265
265
  nextPage: "Next page",
266
266
 
@@ -45,7 +45,7 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
45
45
  columnsShowHideAll: "Visa/dölj alla",
46
46
  columnsReset: "Återställ layout",
47
47
  columnsResetHint:
48
- "Återställer kolumnernas synlighet, ordning, bredder, fästning och gruppering",
48
+ "Återställer kolumnernas synlighet, ordning, bredd, låsning och gruppering",
49
49
  rowNumberColumnLabel: "Radnummer",
50
50
  noRows: "Inga rader att visa",
51
51
 
@@ -70,16 +70,16 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
70
70
  sortAsc: "Sortera stigande",
71
71
  sortDesc: "Sortera fallande",
72
72
  filterMenuItem: "Filtrera",
73
- groupBy: (column) => `Gruppera på ${column}`,
74
- ungroup: (column) => `Avgruppera ${column}`,
73
+ groupBy: (column) => `Gruppera efter ${column}`,
74
+ ungroup: (column) => `Sluta gruppera efter ${column}`,
75
75
  expandAllGroups: "Expandera alla grupper",
76
76
  collapseAllGroups: "Fäll ihop alla grupper",
77
- pinLeft: "Fäst till vänster",
78
- pinRight: "Fäst till höger",
79
- unpin: "Lossa",
77
+ pinLeft: "Lås till vänster",
78
+ pinRight: "Lås till höger",
79
+ unpin: "Lås upp",
80
80
  moveLeft: "Flytta åt vänster",
81
81
  moveRight: "Flytta åt höger",
82
- autosizeColumn: "Anpassa kolumnbredd",
82
+ autosizeColumn: "Autopassa kolumnbredd",
83
83
  hideColumn: "Dölj kolumn",
84
84
  filterOn: (column) => `Filtrera på ${column}`,
85
85
  sortColumn: (column) => `Sortera ${column}`,
@@ -91,7 +91,7 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
91
91
  pageCount < 0 ? `Sida ${page}` : `Sida ${page} av ${pageCount}`,
92
92
  groupedAllRows: (total) => `Grupperad · alla ${total} rader`,
93
93
  pagingSuspendedHint:
94
- "Sidindelningen är avstängd medan raderna är grupperade: hela trädet renderas och virtualiseras. Avgruppera för att bläddra igen.",
94
+ "Sidindelningen är avstängd medan raderna är grupperade, och alla rader visas. Sluta gruppera för att bläddra igen.",
95
95
  previousPage: "Föregående sida",
96
96
  nextPage: "Nästa sida",
97
97
 
@@ -107,11 +107,11 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
107
107
  cancelRowEdit: "Avbryt redigering",
108
108
  editRowErrors: "Åtgärda de markerade cellerna",
109
109
  saveAllEdits: (rows) => (rows === 1 ? "Spara 1 rad" : `Spara ${rows} rader`),
110
- discardAllEdits: "Förkasta",
110
+ discardAllEdits: "Ångra alla",
111
111
  editRowsStillOpen: (rows) =>
112
112
  rows === 1 ? "1 rad redigeras fortfarande" : `${rows} rader redigeras fortfarande`,
113
113
  confirmNewRow: "Lägg till rad",
114
- discardNewRow: "Släng ny rad",
114
+ discardNewRow: "Avbryt ny rad",
115
115
  deleteRow: "Ta bort rad",
116
116
  restoreRow: "Återställ rad",
117
117
  revertRow: "Ångra ändringar",
@@ -122,37 +122,37 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
122
122
  cellCount: (count) => (count === 1 ? "1 cell" : `${count} celler`),
123
123
  copy: "Kopiera",
124
124
  exportCells: "Exportera celler",
125
- includeHeaders: "Inkludera rubriker",
125
+ includeHeaders: "Ta med rubriker",
126
126
 
127
127
  exportAll: "Exportera alla rader",
128
128
  exportSelected: (count) =>
129
- count === 1 ? "Exportera 1 vald rad" : `Exportera ${count} valda rader`,
129
+ count === 1 ? "Exportera 1 markerad rad" : `Exportera ${count} markerade rader`,
130
130
  exportPickerTitle: (format) => `Exportera som ${format}`,
131
131
  exportPickerHint: (selected) =>
132
132
  selected === null
133
133
  ? "Välj kolumner att exportera"
134
134
  : selected === 1
135
- ? "Välj kolumner att exportera för den valda raden"
136
- : `Välj kolumner att exportera för de ${selected} valda raderna`,
135
+ ? "Välj kolumner att exportera för den markerade raden"
136
+ : `Välj kolumner att exportera för de ${selected} markerade raderna`,
137
137
  exportPickerConfirm: "Exportera",
138
138
  exportPickerCancel: "Avbryt",
139
- exportPickerSelectAll: "Välj alla",
139
+ exportPickerSelectAll: "Markera alla",
140
140
  exportPickerCount: (checked, total) => `${checked} av ${total}`,
141
141
  exportPickerHidden: "Dold",
142
142
 
143
- selectColumnLabel: "Kryssrutemarkering",
144
- selectAllRows: "Välj alla rader",
145
- selectRow: "Välj rad",
146
- selectGroup: "Välj grupp",
143
+ selectColumnLabel: "Radmarkering",
144
+ selectAllRows: "Markera alla rader",
145
+ selectRow: "Markera rad",
146
+ selectGroup: "Markera grupp",
147
147
 
148
148
  detailsColumnLabel: "Detaljer",
149
149
  showDetails: "Visa detaljer",
150
150
  hideDetails: "Dölj detaljer",
151
- expandAllDetails: "Expandera alla detaljer",
152
- collapseAllDetails: "Fäll ihop alla detaljer",
151
+ expandAllDetails: "Visa alla detaljer",
152
+ collapseAllDetails: "Dölj alla detaljer",
153
153
 
154
154
  groupColumnLabel: "Grupp",
155
- blankGroupValue: "(Tom)",
155
+ blankGroupValue: "(Tomt)",
156
156
  expandGroup: (label) => `Expandera ${label}`,
157
157
  collapseGroup: (label) => `Fäll ihop ${label}`,
158
158
  };
@@ -5,6 +5,7 @@ import {
5
5
  } from "@tanstack/match-sorter-utils";
6
6
  import {
7
7
  createSortedRowModel,
8
+ type Column,
8
9
  type Row,
9
10
  type RowData,
10
11
  type RowModel,
@@ -22,6 +23,57 @@ export type TMDataGridQuickSearchMode = "fuzzy" | "contains";
22
23
  */
23
24
  export const FUZZY_FILTER_FN_NAME = "tmDataGridFuzzy";
24
25
 
26
+ /**
27
+ * Which columns the quick search reads, per core row model: the answer only
28
+ * changes with the data, and the model's identity changes with the data.
29
+ */
30
+ const searchableColumns = new WeakMap<object, Map<string, boolean>>();
31
+
32
+ /**
33
+ * The grid's `getColumnCanGlobalFilter`: TanStack's own rule - a column takes
34
+ * part when its first non-empty value is a string or a number, or when it
35
+ * says `enableGlobalFilter: true` - computed once per data change.
36
+ *
37
+ * TanStack's default scans the rows for that first value on every call,
38
+ * building every cell of every row it passes, and the grid asks on every
39
+ * header render. A column empty in every row therefore cost a full scan per
40
+ * header per render. Here the scan reads values only, and its answer is kept
41
+ * until the data changes.
42
+ */
43
+ export function columnCanQuickSearch<
44
+ TFeatures extends TableFeatures,
45
+ TData extends RowData,
46
+ TValue,
47
+ >(column: Column<TFeatures, TData, TValue>): boolean {
48
+ const { enableGlobalFilter } = column.columnDef as {
49
+ enableGlobalFilter?: boolean;
50
+ };
51
+ if (enableGlobalFilter === true) return true;
52
+ // A display column has no value to search.
53
+ if (!column.accessorFn) return false;
54
+ // Off the table as a plain record, for the same generic-features reason as
55
+ // the options read in isRankingActive.
56
+ const model = (
57
+ column.table as unknown as { getCoreRowModel: () => RowModel<TFeatures, TData> }
58
+ ).getCoreRowModel();
59
+ let byColumn = searchableColumns.get(model);
60
+ if (byColumn === undefined) {
61
+ byColumn = new Map();
62
+ searchableColumns.set(model, byColumn);
63
+ }
64
+ const known = byColumn.get(column.id);
65
+ if (known !== undefined) return known;
66
+ let searchable = false;
67
+ for (const row of model.flatRows) {
68
+ const value = row.getValue(column.id);
69
+ if (value == null) continue;
70
+ searchable = typeof value === "string" || typeof value === "number";
71
+ break;
72
+ }
73
+ byColumn.set(column.id, searchable);
74
+ return searchable;
75
+ }
76
+
25
77
  /** What {@link fuzzyGlobalFilterFn} records per column via `addMeta`. */
26
78
  type FuzzyFilterMeta = { fuzzyRank: RankingInfo };
27
79
 
@@ -90,6 +90,7 @@ import {
90
90
  type TMDataGridFiltersSettings,
91
91
  } from "./core/filterSurface";
92
92
  import {
93
+ columnCanQuickSearch,
93
94
  createFuzzyRankedSortedRowModel,
94
95
  fuzzyGlobalFilterFn,
95
96
  type TMDataGridQuickSearchMode,
@@ -1082,19 +1083,6 @@ type TMDataGridColumnDef<TData extends RowData> = ColumnDef<
1082
1083
  unknown
1083
1084
  >;
1084
1085
 
1085
- /**
1086
- * Point every column at the operator-dispatching filter function, and take
1087
- * grouping's aggregation defaults back off, unless the column opted into its
1088
- * own. Anything the consumer set wins - it is spread over these.
1089
- *
1090
- * The aggregation pair needs explaining. TanStack's grouping feature hands
1091
- * every column `aggregationFn: "auto"` and an `aggregatedCell` that stringifies
1092
- * whatever comes out, so merely registering the feature would have every
1093
- * numeric column silently sum itself and every date column show a range the
1094
- * moment anything is grouped. That is a summary table, and "group by" is not a
1095
- * request for one. Cleared here, so a grouped grid is a tree until a column
1096
- * says otherwise - `aggregationFn: "sum"` on the column that wants it.
1097
- */
1098
1086
  /**
1099
1087
  * Drops the control lanes' entries from a visibility map.
1100
1088
  *
@@ -1115,6 +1103,19 @@ function withoutControlColumnVisibility(
1115
1103
  return scrubbed;
1116
1104
  }
1117
1105
 
1106
+ /**
1107
+ * Point every column at the operator-dispatching filter function, and take
1108
+ * the aggregation defaults back off, unless the column opted into its own.
1109
+ * Anything the consumer set wins - it is spread over these.
1110
+ *
1111
+ * The aggregation pair needs explaining. TanStack's row aggregation feature
1112
+ * hands every column `aggregationFn: "auto"` and an `aggregatedCell` that
1113
+ * stringifies whatever comes out, so merely registering the feature would have
1114
+ * every numeric column silently sum itself and every date column show a range
1115
+ * the moment anything is grouped. That is a summary table, and "group by" is
1116
+ * not a request for one. Cleared here, so a grouped grid is a tree until a
1117
+ * column says otherwise - `aggregationFn: "sum"` on the column that wants it.
1118
+ */
1118
1119
  function withTMDataGridDefaults<TData extends RowData>(
1119
1120
  columns: ReadonlyArray<TMDataGridColumnDef<TData>>,
1120
1121
  ): Array<TMDataGridColumnDef<TData>> {
@@ -1477,6 +1478,11 @@ export function useTMDataGrid<TData extends RowData>({
1477
1478
  edit.store,
1478
1479
  (state) => state.committedValues,
1479
1480
  );
1481
+ // Subscribed for the re-render alone - the selection predicate below reads
1482
+ // the marks live. TanStack 9.2 caches a group row's "all / some selected"
1483
+ // on the predicate's identity, and only a render hands it a new one; without
1484
+ // this a mark would leave the group's checkbox on its old answer.
1485
+ useSelector(edit.store, (state) => state.deletedRowIds);
1480
1486
  const newRowsSticky = features.editNewRowsSticky;
1481
1487
  const editCreatedIds = useSelector(
1482
1488
  edit.store,
@@ -1578,6 +1584,9 @@ export function useTMDataGrid<TData extends RowData>({
1578
1584
  options.quickSearchMode === "contains"
1579
1585
  ? "includesString"
1580
1586
  : "tmDataGridFuzzy",
1587
+ // Which columns the quick search reads - TanStack's rule, computed once
1588
+ // per data change instead of once per call. See columnCanQuickSearch.
1589
+ getColumnCanGlobalFilter: columnCanQuickSearch,
1581
1590
  // Grouping by a column takes it out of the grid, the way AG Grid does it:
1582
1591
  // its values have moved into the tree column, so leaving it in place would
1583
1592
  // show every row the same value it was grouped under. Overridable - pass
@@ -1621,11 +1630,17 @@ export function useTMDataGrid<TData extends RowData>({
1621
1630
  getRowId: shownGetRowId,
1622
1631
  // Row details ride on `expanded`, the same state the tree uses - but a data
1623
1632
  // row answers `getCanExpand()` false, since TanStack's fallback is
1624
- // `subRows.length > 0`. `() => true` is the right answer for a group row
1625
- // too, so nothing here has to tell the two apart, and a consumer passing
1626
- // their own predicate still wins.
1627
- ...(renderDetails !== undefined
1628
- ? { getRowCanExpand: options.getRowCanExpand ?? (() => true) }
1633
+ // `subRows.length > 0`, so under `renderDetails` every row can expand.
1634
+ // A consumer's own predicate decides the data rows, never a group row:
1635
+ // TanStack builds a group row on its first child's record, and since 9.2
1636
+ // `toggleExpanded` refuses a row that cannot expand - so a predicate
1637
+ // rejecting that record would leave the group's chevron dead.
1638
+ ...(renderDetails !== undefined || options.getRowCanExpand !== undefined
1639
+ ? {
1640
+ getRowCanExpand: (row: Row<TMDataGridFeatures, TData>) =>
1641
+ row.getIsGrouped() ||
1642
+ (options.getRowCanExpand?.(row) ?? true),
1643
+ }
1629
1644
  : {}),
1630
1645
  // Always a predicate, never the passthrough: TanStack defaults the option
1631
1646
  // to `true` once the feature is registered, and the grid's default is off.