@jielga/tmdatagrid 2.1.4 → 2.2.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.
Files changed (48) hide show
  1. package/dist/index.d.ts +38 -2
  2. package/dist/index.js +1329 -1246
  3. package/dist/index.js.map +1 -1
  4. package/docs/cell-selection.md +1 -1
  5. package/docs/column-layout.md +13 -8
  6. package/docs/columns.md +57 -2
  7. package/docs/export.md +2 -2
  8. package/docs/filtering.md +105 -15
  9. package/docs/getting-started.md +40 -0
  10. package/docs/grouping.md +9 -6
  11. package/docs/loading-and-empty.md +29 -9
  12. package/docs/localization.md +130 -3
  13. package/docs/menu.md +1 -1
  14. package/docs/pagination.md +26 -7
  15. package/docs/row-details.md +36 -4
  16. package/docs/row-interaction.md +1 -1
  17. package/docs/row-selection.md +6 -7
  18. package/docs/scrolling.md +1 -1
  19. package/docs/server-side.md +3 -1
  20. package/docs/testing.md +18 -0
  21. package/docs/use-tm-data-grid.md +69 -34
  22. package/package.json +1 -1
  23. package/skills/appearance/SKILL.md +6 -4
  24. package/skills/appearance/references/labels.md +126 -0
  25. package/skills/cell-selection/SKILL.md +2 -2
  26. package/skills/columns/SKILL.md +42 -41
  27. package/skills/columns/references/columns-api.md +15 -1
  28. package/skills/data/SKILL.md +77 -50
  29. package/skills/editing/SKILL.md +1 -1
  30. package/skills/filtering/SKILL.md +71 -49
  31. package/skills/filtering/references/custom-filters.md +118 -0
  32. package/skills/getting-started/SKILL.md +37 -2
  33. package/skills/grouping/SKILL.md +16 -9
  34. package/skills/migrating-to-2/SKILL.md +1 -1
  35. package/skills/options/SKILL.md +131 -51
  36. package/skills/rows/SKILL.md +53 -31
  37. package/skills/server-side/SKILL.md +1 -1
  38. package/skills/testing/SKILL.md +12 -1
  39. package/skills/testing-components/SKILL.md +1 -1
  40. package/skills/testing-editing/SKILL.md +1 -1
  41. package/src/components/TMDataGridTable.tsx +241 -41
  42. package/src/core/autosize.ts +19 -7
  43. package/src/core/cellRange.ts +11 -2
  44. package/src/core/colSpan.ts +78 -0
  45. package/src/core/columnUtils.ts +7 -0
  46. package/src/core/filterOperators.ts +16 -1
  47. package/src/index.ts +4 -0
  48. package/src/useTMDataGrid.tsx +38 -0
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '2.1.4'
17
+ library_version: '2.2.0'
18
18
  sources:
19
19
  - 'Jielga/react:packages/tmdatagrid/docs/grouping.md'
20
20
  - 'Jielga/react:packages/tmdatagrid/docs/summary-row.md'
@@ -96,8 +96,8 @@ would pass a row that looks real but is the wrong one. Group rows therefore:
96
96
  A group row's checkbox selects every record under it at any depth, including
97
97
  records inside collapsed sub-groups, showing a tick once all are selected and a
98
98
  dash while only some are. Only the records reach `rowSelection`; a group row is
99
- never in it, so `getSelectedRowModel()` and the toolbar count do not depend on
100
- how the tree is arranged. Under `enableMultiRowSelection: false` group rows carry
99
+ never in it, so `getSelectedRowModel()` and `onRowSelectionChange` do not depend
100
+ on how the tree is arranged. Under `enableMultiRowSelection: false` group rows carry
101
101
  no checkbox.
102
102
 
103
103
  `getGroupDataRows(row)` returns every record under a group row, at any depth.
@@ -116,8 +116,11 @@ page. Rendering the whole tree is the grid's default mode in any case, so only
116
116
  the pager is lost.
117
117
 
118
118
  `isPagingActive(table, features)` is exported so a custom pager can grey itself
119
- out the same way. To have both, page on the server: group there and feed the
120
- grid one page of a tree at a time with `manualPagination` and `manualGrouping`.
119
+ out the same way. There is no option to keep the built-in pager active while
120
+ grouped, and `manualGrouping` does not change that: the pager greys out whenever
121
+ `grouping` is non-empty. To have both, page on the server with
122
+ `manualPagination` and `manualGrouping`, and render the pager through the
123
+ Footer's `renderPagination` slot, ignoring `isPagingActive`.
121
124
 
122
125
  ## The summary row
123
126
 
@@ -197,7 +200,7 @@ out instead of paging the tree, so a footer count wired to `getPageCount()`
197
200
  reports a number nobody can navigate to. Read
198
201
  `isPagingActive(table, features)` before trusting the pager state.
199
202
 
200
- Source: `packages/tmdatagrid/docs/grouping.md` (Grouping suspends pagination).
203
+ Source: `packages/tmdatagrid/docs/grouping.md` (Grouping and pagination).
201
204
 
202
205
  ### HIGH Handing a group row to a row callback
203
206
 
@@ -224,7 +227,7 @@ const records = getGroupDataRows(groupRow).map((row) => row.original);
224
227
  />;
225
228
  ```
226
229
 
227
- Source: `packages/tmdatagrid/docs/grouping.md` (Group rows are not data rows).
230
+ Source: `packages/tmdatagrid/docs/grouping.md` (Group rows).
228
231
 
229
232
  ### MEDIUM Totalling the page instead of the data
230
233
 
@@ -252,18 +255,22 @@ useTMDataGrid({
252
255
  });
253
256
  ```
254
257
 
258
+ The built-in pager still greys out while `grouping` is set, so such a grid
259
+ renders its pager through `TMDataGrid.Footer`'s `renderPagination` slot and
260
+ ignores `state.isPagingActive`.
261
+
255
262
  Source: `packages/tmdatagrid/docs/grouping.md` (Server-side grids).
256
263
 
257
264
  ## Reference
258
265
 
259
266
  | Name | Kind | Type | Default | What it does |
260
267
  | --- | --- | --- | --- | --- |
261
- | `enableGrouping` | Table option | `boolean` | `true` | Group by and Ungroup menu items. Also a column option. |
268
+ | `enableGrouping` | Table option | `boolean` | `true`; `false` under `manualPagination` | Group by and Ungroup menu items, and the tree lane. Also a column option. |
262
269
  | `groupedColumnMode` | Table option | `"reorder" \| "remove" \| false` | `"remove"` | Whether a grouped column leaves the grid or moves to the front. |
263
270
  | `manualGrouping` | Table option | `boolean` | `false` | The rows arrive grouped. Required to group a server-paged grid. |
264
271
  | `initialState.grouping` | Table option | `string[]` | `[]` | Column ids grouped at mount. A settings slice, so it persists. |
265
272
  | `aggregationFn` | Column option | `TMDataGridAggregationName \| fn` | – | How a column fills in its group cells. Unset leaves them blank. |
266
- | `aggregatedCell` | Column option | `(ctx) => ReactNode` | The `cell` renderer | Renders a group row's value differently. |
273
+ | `aggregatedCell` | Column option | `(ctx) => ReactNode` | `cell` over the aggregate when `aggregationFn` is set, else blank | Renders a group row's value differently from a data row's. |
267
274
  | `footer` | Column option | `(ctx) => ReactNode` | – | Renders this column's summary cell. Defining one adds the row. |
268
275
  | `aggregateColumn` | Export | `({ table, columnId, fn }) => unknown` | `fn: "sum"` | Aggregates over every filtered row, all pages. |
269
276
  | `TMDataGridAggregationName` | Export | type | – | The registered function names. |
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: lifecycle
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '2.1.4'
18
+ library_version: '2.2.0'
19
19
  sources:
20
20
  - 'Jielga/react:packages/tmdatagrid/docs/migrating-to-2.md'
21
21
  - 'Jielga/react:packages/tmdatagrid/src/index.ts'
@@ -2,19 +2,23 @@
2
2
  name: options
3
3
  description: >
4
4
  useTMDataGrid options, return value and state persistence. Covers passthrough
5
- of TanStack TableOptions, the default initialState (pagination, columnPinning,
6
- globalFilterFn), the meta object (loading, noResultsLabel, rowHeight,
7
- totalRowCount), the grid's own persist, enableColumnOrdering,
8
- enablePagination, selectionMode and showSelectedBackground options, the
9
- persist option with dataKey/settingsKey slice selection and storageMode, the
5
+ of TanStack TableOptions, every default the grid changes (globalFilterFn,
6
+ groupedColumnMode, autoResetExpanded, autoResetPageIndex,
7
+ defaultColumn.minSize), the default initialState, the meta object (loading,
8
+ noResultsLabel, rowHeight, totalRowCount), the grid's own options (persist,
9
+ labels, editing, filters, exportOptions, enableColumnOrdering,
10
+ enablePagination, resetPageOnQueryChange, enableRowNumbers, selectionMode,
11
+ showSelectedBackground, cellSelection, renderDetails, overscan,
12
+ quickSearchMode), persist with dataKey/settingsKey slices and storageMode, the
10
13
  returned api (table, ui, edit, features, labels, resetSettings, scrollToRow),
11
- ui panel and column drag actions, and reading grid state with useSelector.
12
- Load when configuring the hook, persisting column layout or filters, or
13
- reacting to grid state from a parent.
14
+ ui actions, and reading grid state with useSelector over table.store, since
15
+ table.getState() does not exist in v9. Load when configuring the hook, looking
16
+ up an option's default, persisting layout or filters, or reacting to grid
17
+ state from a parent.
14
18
  metadata:
15
19
  type: core
16
20
  library: '@jielga/tmdatagrid'
17
- library_version: '2.1.4'
21
+ library_version: '2.2.0'
18
22
  sources:
19
23
  - 'Jielga/react:packages/tmdatagrid/docs/use-tm-data-grid.md'
20
24
  - 'Jielga/react:packages/tmdatagrid/docs/persistence.md'
@@ -28,7 +32,8 @@ Creates the table instance and the state used by the grid interface.
28
32
 
29
33
  ```tsx
30
34
  const grid = useTMDataGrid<TData>(options);
31
- // { table, ui, edit, features, labels, resetSettings, scrollToRow }
35
+ // { table, ui, edit, features, labels, filters, exportOptions, renderDetails,
36
+ // renderDetailsEstHeight, overscan, resetSettings, scrollToRow }
32
37
  ```
33
38
 
34
39
  The signature is
@@ -42,43 +47,77 @@ All TanStack `TableOptions` are supported and passed through unchanged, includin
42
47
  flags and `rowCount`. The `features` option is supplied internally and cannot be
43
48
  overridden.
44
49
 
45
- `persist`, `enableColumnOrdering`, `enablePagination`, `selectionMode` and
46
- `showSelectedBackground` are the grid's own options and are consumed by the hook
47
- rather than forwarded to TanStack.
50
+ The grid defines these options itself: `persist`, `labels`, `editing`,
51
+ `filters`, `exportOptions`, `enableColumnOrdering`, `enablePagination`,
52
+ `resetPageOnQueryChange`, `enableRowNumbers`, `selectionMode`,
53
+ `showSelectedBackground`, `defaultHighlightedRowId`, `onHighlightedRowChange`,
54
+ `cellSelection`, `onFocusedCellChange`, `renderDetails`,
55
+ `renderDetailsEstHeight`, `detailsColumnPosition`, `overscan`,
56
+ `quickSearchMode` and `enableMatchHighlighting`. The table below marks them
57
+ "Defined by the grid"; every other row is a TanStack option, listed where the
58
+ grid changes its default or where it decides something visible. A TanStack
59
+ default the grid changes reads, for example, `"remove"` (grid default;
60
+ TanStack: `"reorder"`). A TanStack option that is not listed passes through
61
+ with TanStack's own default.
48
62
 
49
63
  | Option | Type | Default | Description |
50
64
  | --- | --- | --- | --- |
51
- | `data` | `TData[]` | – | Row data. Keep the reference stable with `useMemo`. |
52
- | `columns` | `ColumnDef[]` | – | Created with `createTMDataGridColumnHelper`. |
53
- | `getRowId` | `(row, index) => string` | Row index | Used by row selection and virtualization. Required once `editing` is set. |
65
+ | `data` | `TData[]` | – | Row data. When the reference changes the table reprocesses the row models, so keep it stable with `useMemo`; a fresh `[]` for the empty case belongs in a module-scope constant. |
66
+ | `columns` | `ColumnDef[]` | – | Created with `createTMDataGridColumnHelper`. Define them outside the component. |
67
+ | `getRowId` | `(row, index, parent?) => string` | Row index | The id that row selection, expansion, row pinning and editing are keyed by. The index points at another record after a sort or a refetch, so return the entity id. Required once `editing` is set. |
68
+ | `defaultColumn` | `Partial<ColumnDef>` | `{ minSize: 80 }` (grid default; TanStack: `{}`) | Defaults merged under every column. The grid sets `filterFn: "tmDataGrid"` and clears `aggregationFn` and `aggregatedCell` on every column, so those three keys have no effect here; a value on the column wins. |
54
69
  | `enableRowSelection` | `boolean \| (row) => boolean` | `true` | `false` removes row selection and its checkbox column. |
55
- | `selectionMode` | `"checkbox" \| "row" \| "checkboxAndHighlight" \| "highlight"` | `"checkbox"` | What selecting looks like and what a row click does. Defined by the grid - see the `rows` skill. |
56
- | `showSelectedBackground` | `boolean` | Follows the mode | Background tint on selected rows: off for `"checkbox"`, on for `"row"`. Colour is `--dg-row-selected-bg`. Defined by the grid. |
57
- | `enableSorting` | `boolean` | `true` | Enables sorting for the table. |
58
- | `enableColumnFilters` | `boolean` | `true` | Enables filtering for the table. |
59
- | `enableHiding` | `boolean` | `true` | Enables column visibility for the table. |
70
+ | `enableMultiRowSelection` | `boolean \| (row) => boolean` | `true` | `false` removes the select-all checkbox and limits the selection to one row. |
71
+ | `selectionMode` | `"checkbox" \| "row" \| "checkboxAndHighlight" \| "highlight"` | `"checkbox"` | What selecting looks like and what a bare row click does. Defined by the grid, see the `rows` skill. |
72
+ | `showSelectedBackground` | `boolean` | On for `"row"`, off for `"checkbox"` | Determines whether selected rows take a background tint, coloured by `--dg-row-selected-bg`. Defined by the grid, see the `rows` skill. |
73
+ | `defaultHighlightedRowId` | `string \| null` | – | Row highlighted on mount, under a mode with a highlight. Read once, like `initialState`. Defined by the grid. |
74
+ | `onHighlightedRowChange` | `(rowId: string \| null) => void` | – | Follows the highlighted row, from clicks and from `ui.actions.setHighlightedRow`. Defined by the grid. |
75
+ | `enableSorting` | `boolean` | `true` | `false` removes the sort indicator, the sort menu items and click-to-sort. Also a column option. See the `columns` skill. |
76
+ | `enableMultiSort` | `boolean` | `true` for accessor columns | Shift+click on a header adds a sort instead of replacing it. Also a column option. See the `columns` skill. |
77
+ | `maxMultiSortColCount` | `number` | `Number.MAX_SAFE_INTEGER` | How many sorts are kept; past it the oldest is dropped. |
78
+ | `enableColumnFilters` | `boolean` | `true` | `false` removes the filter menu item, the `FilterButton`, the header filter row and the panel's column list, and disables the panel's "Add filter" button. Filters already in state still apply. See the `filtering` skill. |
79
+ | `enableGlobalFilter` | `boolean` | `true` | `false` removes `Search`. Also a column option, removing one column from the search. See the `filtering` skill. |
80
+ | `globalFilterFn` | filter fn \| name | `"tmDataGridFuzzy"`; `"includesString"` under `quickSearchMode: "contains"` (grid default; TanStack: `"auto"`) | How the quick search matches. An explicit value also switches the fuzzy rank ordering off. |
81
+ | `enableHiding` | `boolean` | `true` | `false` removes the Hide column item, the Manage columns submenu and the column chooser. Also a column option. |
60
82
  | `enableColumnPinning` | `boolean` | `true` | Enables pinning for the table. `false` removes the pin menu items; the generated lanes stay pinned. |
61
- | `enableColumnResizing` | `boolean` | `true` | Enables resizing for the table. |
62
- | `enableColumnOrdering` | `boolean` | `true` | Enables header dragging and the move menu items. Defined by the grid. |
63
- | `enablePagination` | `boolean` | `false` | Enables client-side paging and the `Footer` pager. Implied by `manualPagination`. Defined by the grid. |
64
- | `cellSelection` | `"none" \| "single" \| "range"` | `"none"` | Cell cursor; `"range"` adds a drag-selectable rectangle, Ctrl+C and CSV export. Defined by the grid. |
65
- | `onFocusedCellChange` | `(cell \| null) => void` | – | Fires whenever the focused cell moves. |
66
- | `overscan` | `number` | `6` | Rows the virtualizer keeps mounted above and below the viewport. Defined by the grid. |
67
- | `columnResizeMode` | `"onChange" \| "onEnd"` | `"onChange"` | Resize update strategy. |
68
- | `initialState` | `Partial<TableState>` | See below | The state the grid starts from, read once on mount. Merged over the grid defaults. |
69
- | `state` | `Partial<TableState>` | – | Controlled state. Each slice requires its `onXChange` - see below. |
70
- | `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration, see below. |
71
- | `persist` | `TMDataGridPersistence` | – | State persistence, see below. |
72
- | `editing` | `TMDataGridEditingOptions` | off | Turns editing on. Two axes: `mode` (`"cell" \| "cellConfirm" \| "row"`) picks what counts as a commit, `draft` picks whether it parks in the draft store; the object also holds `onCommit`, `onSaveDrafts`, `rowValidators`, `isRowEditable`, `newRowDefaults`, `newRowsSticky` (keeps committed new rows in the sticky entry block instead of sorting them in with the body), `onRowAdd` and `onRowDelete` - see the `editing` skill. |
73
- | `labels` | `TMDataGridLabelsOverride` | English | Overrides for the grid's strings and `aria-label`s. |
83
+ | `enableColumnResizing` | `boolean` | `true` | With `false` the divider is displayed but cannot be dragged, and double-click autosize and the Autosize menu item are removed. Also a column option, as `enableResizing`. |
84
+ | `enableColumnOrdering` | `boolean` | `true` | Enables header dragging and the move menu items. Defined by the grid, see the `columns` skill. |
85
+ | `enableGrouping` | `boolean` | `true`; `false` under `manualPagination` (grid default; TanStack: `true`) | `false` removes the Group by and Ungroup menu items and the tree lane. Also a column option. See the `grouping` skill. |
86
+ | `groupedColumnMode` | `"remove" \| "reorder" \| false` | `"remove"` (grid default; TanStack: `"reorder"`) | What happens to a column the rows are grouped by: `"remove"` takes it out of the grid, `"reorder"` moves it to the front, `false` leaves it in place. See the `grouping` skill. |
87
+ | `enablePagination` | `boolean` | `false` | Enables client-side paging and the `Footer` pager. Implied by `manualPagination`. Defined by the grid, see the `data` skill. |
88
+ | `resetPageOnQueryChange` | `boolean` | `true` | Returns to the first page when a column filter, the quick search, the sorting or the grouping changes through the table. A `data` change does not count. Defined by the grid, see the `data` skill. |
89
+ | `autoResetPageIndex` | `boolean` | `false` (grid default; TanStack: `!manualPagination`) | TanStack's return to the first page on every `data` change. Off, since `resetPageOnQueryChange` covers the query. |
90
+ | `autoResetExpanded` | `boolean` | `false` (grid default; TanStack: `!manualExpanding`) | TanStack's collapse of every expanded row on a `data` change. Off, so open details panels survive a refetch. |
91
+ | `manualFiltering`, `manualSorting`, `manualGrouping`, `manualPagination` | `boolean` | `false` | The server does that step and the grid shows `data` as given. `manualPagination` also implies `enablePagination`. See the `server-side` skill. |
92
+ | `rowCount` | `number` | – | The total row count under `manualPagination`, read by the pager. |
93
+ | `enableRowNumbers` | `boolean` | `false` | The row-number gutter, outermost left. Defined by the grid, see the `rows` skill. |
94
+ | `enableRowPinning` | `boolean \| (row) => boolean` | `false` (grid default; TanStack: `true`) | Rows can be pinned to sticky edge blocks with `row.pin()`. See the `rows` skill. |
95
+ | `quickSearchMode` | `"fuzzy" \| "contains"` | `"fuzzy"` | How `Search` matches. Fuzzy forgives typos and orders unsorted results by match quality; `"contains"` is plain substring matching. Defined by the grid, see the `filtering` skill. |
96
+ | `enableMatchHighlighting` | `boolean` | `false` | Cells mark the matched text while a contains-family filter or the quick search is active. Defined by the grid, see the `filtering` skill. |
97
+ | `renderDetails` | `({ row, table }) => ReactNode` | – | Panel rendered under an expanded row, spanning every column. Setting it turns row details on and adds the pinned chevron lane. Defined by the grid, see the `rows` skill. |
98
+ | `renderDetailsEstHeight` | `number` | `160` | What the virtualizer assumes for a panel it has not measured yet. Panels are measured once mounted, so an approximation is enough. Defined by the grid. |
99
+ | `detailsColumnPosition` | `"left" \| "right"` | `"left"` | Which edge the details lane is pinned to. `"right"` places it after every column, inside the edit lane. Defined by the grid, see the `rows` skill. |
100
+ | `cellSelection` | `"none" \| "single" \| "range"` | `"none"` | Cell cursor and, under `"range"`, a selectable rectangle with Ctrl+C and CSV export. Defined by the grid, see the `cell-selection` skill. |
101
+ | `onFocusedCellChange` | `(cell: TMDataGridCellPosition \| null) => void` | – | Called whenever the focused cell moves, by key, click or `setFocusedCell`. Defined by the grid. |
102
+ | `overscan` | `number` | `6` | Rows the virtualizer keeps mounted above and below the viewport. Raise it if fast scrolling flashes blank rows, lower it when rows are expensive to render. Defined by the grid. |
103
+ | `columnResizeMode` | `"onChange" \| "onEnd"` | `"onEnd"` | When a resize drag writes `columnSizing`. The grid paints a running drag itself either way; `"onChange"` also publishes a width on every pointer move, which re-renders the grid with each of them. |
104
+ | `initialState` | `Partial<TableState>` | – | Starting state, read once on mount. Merged over the grid defaults below. |
105
+ | `state` | `Partial<TableState>` | – | Controlled state. Each slice requires its `onXChange`. See below. |
106
+ | `atoms` | `Partial<Record<slice, Atom>>` | – | External atoms owning state slices. No callback required. Takes precedence over `state`. |
107
+ | `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration. See below. |
108
+ | `filters` | `TMDataGridFiltersOptions` | `{ surface: "popup" }` | Where the filter controls go - a popup, a sidebar, the column headers, or nowhere. Defined by the grid, see the `filtering` skill. |
109
+ | `exportOptions` | `TMDataGridExportOptions` | `csvExcelFormat()`, `"export"`, headers on, `"visible"` columns | The export's `format`, `fileName`, `includeHeaders` and `columns`. Defined by the grid, see the `data` skill. |
110
+ | `editing` | `TMDataGridEditingOptions` | off | Turns editing on. `mode` picks what counts as a commit and `draft` whether edits collect in the draft store. Requires `getRowId`. Defined by the grid, see the `editing` skill. |
111
+ | `persist` | `TMDataGridPersistence` | – | State persistence. Defined by the grid, see below. |
112
+ | `labels` | `TMDataGridLabelsOverride` | English | Overrides for the grid's strings and `aria-label`s. Defined by the grid, see the `appearance` skill. |
74
113
 
75
114
  ### Default initial state
76
115
 
77
116
  | Slice | Default |
78
117
  | --- | --- |
79
- | `pagination` | `{ pageIndex: 0, pageSize: 25 }` - inert until pagination is enabled |
80
- | `columnPinning.start` | The checkbox column, followed by any columns you provide |
81
- | `globalFilterFn` | `"includesString"` |
118
+ | `pagination` | `{ pageIndex: 0, pageSize: 25 }`. Inert until pagination is enabled |
119
+ | `columnPinning.start` | The row number, checkbox, tree and details columns, followed by any columns you provide. The details column moves to `end` under `detailsColumnPosition: "right"` |
120
+ | `columnPinning.end` | Any columns you provide, then the details column under `detailsColumnPosition: "right"`, then the edit column |
82
121
 
83
122
  ### Controlled state
84
123
 
@@ -113,10 +152,10 @@ const grid = useTMDataGrid({
113
152
 
114
153
  | Field | Type | Default | Description |
115
154
  | --- | --- | --- | --- |
116
- | `loading` | `boolean` | – | Displays a loader instead of the empty state. |
117
- | `noResultsLabel` | `string` | `"No rows match your filters"` | Empty state text. |
155
+ | `loading` | `boolean` | `false` | While `true` and the view is empty, a loader replaces the empty state; `renderEmptyState` is not called. Rows on screen stay. See the `data` skill. |
156
+ | `noResultsLabel` | `string` | `labels.noResults` | The text shown when a filter or the search leaves no rows. |
118
157
  | `rowHeight` | `number` | From `size` | Row height in px. Overrides the size scale. |
119
- | `totalRowCount` | `number` | – | Unfiltered total used by `SummaryCount`. Required for server-side data. |
158
+ | `totalRowCount` | `number` | – | Unfiltered total used by `SummaryCount`. Without it a `manualFiltering` or `manualPagination` grid shows the shown count alone. |
120
159
 
121
160
  ## persist
122
161
 
@@ -194,32 +233,58 @@ Source: `packages/tmdatagrid/docs/persistence.md` (Behaviour).
194
233
  | --- | --- | --- |
195
234
  | `table` | `Table<TMDataGridFeatures, TData>` | The TanStack table instance. |
196
235
  | `ui` | `TMDataGridUiStore`: `Store<TMDataGridUiState, TMDataGridUiActions>` | State of the filter and column panels. |
236
+ | `filters` | `TMDataGridFiltersSettings` | The `filters` option with its defaults filled in. |
237
+ | `exportOptions` | `TMDataGridExportSettings` | The `exportOptions` option with its defaults filled in. |
197
238
  | `edit` | `TMDataGridEditApi` | The edit engine, inert until `editing` is set. See the `editing` skill. |
198
239
  | `features` | `TMDataGridFeatureFlags` | Table-level feature switches, re-read on each render. |
199
240
  | `labels` | `TMDataGridLabels` | The resolved label set, overrides merged over English. |
241
+ | `renderDetails` | `TMDataGridDetailsRenderer<TData> \| undefined` | The detail renderer, passed through for `TMDataGrid.Table` to call. |
242
+ | `renderDetailsEstHeight` | `number` | The estimate, resolved to its default when the option was not set. |
243
+ | `overscan` | `number` | The overscan, resolved to its default when the option was not set. |
200
244
  | `resetSettings` | `() => void` | Puts the settings state back to a clean first visit. |
201
245
  | `scrollToRow` | `({ rowId, align? }) => boolean` | Scrolls a row into view through the virtualizer. `false` when the row is not in the current view. |
202
246
 
203
- Both stores are subscribable, which is how a parent reacts to grid state without
204
- owning it:
247
+ `table` is `TMDataGridTable<TData>`, TanStack's core `Table` type. It has no
248
+ `getState()` (that method does not exist in TanStack Table v9) and `table.state`
249
+ is not part of the type; state lives in `table.store`.
250
+
251
+ ### Reading state
252
+
253
+ `table.store` and `ui` are TanStack stores. Read a slice with `useSelector`
254
+ from `@tanstack/react-store`, which subscribes the component to what the
255
+ selector returns:
205
256
 
206
257
  ```tsx
207
258
  import { useSelector } from "@tanstack/react-store";
208
259
 
209
260
  const selectedCount = useSelector(
210
261
  grid.table.store,
211
- (state) => Object.keys(state.rowSelection).length,
262
+ () => grid.table.getSelectedRowModel().rows.length,
212
263
  );
213
-
264
+ const isGrouped = useSelector(grid.table.store, (state) =>
265
+ state.grouping.includes("department"),
266
+ );
267
+ const pageIndex = useSelector(grid.table.store, (state) => state.pagination.pageIndex);
214
268
  const filterPanelOpen = useSelector(grid.ui, (state) => state.filterPanelOpen);
215
269
  ```
216
270
 
271
+ `grid.table.store.state` is a plain read and does not subscribe the component.
272
+ A bare read is stale in any component that does not call `useTMDataGrid`
273
+ itself, and under the React Compiler also in the one that does; `useSelector`
274
+ subscribes either way. A table or column getter such as
275
+ `table.getSelectedRowModel()` or `column.getIsGrouped()` is a plain read as
276
+ well; call it inside a selector, as above. `getTMDataGridPaginationApi(table)`
277
+ is a snapshot too; wrap it in `useSelector` or use the `state` the Footer's
278
+ `renderPagination` slot receives.
279
+
217
280
  ### ui actions
218
281
 
219
282
  | Action | Signature |
220
283
  | --- | --- |
221
284
  | `openFilterPanel` | `(columnId?: string \| null) => void` |
222
285
  | `closeFilterPanel` | `() => void` |
286
+ | `openExportPicker` | `(request: TMDataGridExportPickerRequest) => void` |
287
+ | `closeExportPicker` | `() => void` |
223
288
  | `focusPanelFilter` | `(columnId: string \| null) => void` |
224
289
  | `focusHeaderFilter` | `(columnId: string \| null) => void` |
225
290
  | `startColumnDrag` | `(columnId: string) => void` |
@@ -232,29 +297,41 @@ const filterPanelOpen = useSelector(grid.ui, (state) => state.filterPanelOpen);
232
297
  The last two move the cell cursor and the selected rectangle under
233
298
  `cellSelection`; DOM focus follows `focusedCell` and scrolls its row into view.
234
299
 
235
- `startColumnDrag` / `endColumnDrag` are called by the header cells while a column is being dragged, and
236
- `ui.draggedColumnId` holds the column being moved - browsers keep `dataTransfer`
237
- unreadable until the drop.
300
+ `startColumnDrag` / `endColumnDrag` are called by the header cells while a
301
+ column is being dragged, and `ui.state.draggedColumnId` holds the column being
302
+ moved - browsers keep `dataTransfer` unreadable until the drop.
238
303
 
239
304
  `openColumnFilter(grid, columnId)` combines the two steps the column menu uses:
240
- add an empty filter row for the column if none exists, then open the panel.
305
+ add an empty filter row for the column if none exists, then open the panel on
306
+ it, or focus the column's header control under `filters.inHeader`.
241
307
 
242
308
  ## Common mistakes
243
309
 
244
310
  ### Reading store state during render
245
311
 
246
- `grid.table.store.state` is a plain read - it does not subscribe the component,
247
- so the value is correct on first render and then never updates.
312
+ `grid.table.store.state` is a plain read - it does not subscribe the component.
313
+ A bare read is stale in any component that does not call `useTMDataGrid`
314
+ itself, and under the React Compiler also in the one that does; `useSelector`
315
+ subscribes either way. The same holds for every table and column getter called
316
+ outside a selector.
248
317
 
249
318
  ```tsx
250
319
  // Wrong - never re-renders when the selection changes.
251
320
  const count = Object.keys(grid.table.store.state.rowSelection).length;
321
+ // Wrong - a getter outside a selector is a snapshot.
322
+ const grouped = grid.table.getColumn("department")!.getIsGrouped();
323
+ // Wrong - `getState()` does not exist in TanStack Table v9.
324
+ const sorting = grid.table.getState().sorting;
252
325
 
253
326
  // Right.
254
327
  const count = useSelector(
255
328
  grid.table.store,
256
329
  (state) => Object.keys(state.rowSelection).length,
257
330
  );
331
+ const grouped = useSelector(grid.table.store, (state) =>
332
+ state.grouping.includes("department"),
333
+ );
334
+ const sorting = useSelector(grid.table.store, (state) => state.sorting);
258
335
  ```
259
336
 
260
337
  ### persist object built during render
@@ -273,4 +350,7 @@ Include a tenant or user identifier in the key.
273
350
 
274
351
  A new array identity on each render re-runs the row model. `data={rows ?? []}`
275
352
  allocates a fresh `[]` every render when `rows` is undefined - hoist the empty
276
- array to a module-scope constant or `useMemo` the expression.
353
+ array to a module-scope constant or `useMemo` the expression. Under `editing`
354
+ the cost is higher: every new `data` identity drops the open drafts of the rows
355
+ it no longer contains, so a transient `[]` while refetching discards every
356
+ draft.
@@ -17,7 +17,7 @@ description: >
17
17
  metadata:
18
18
  type: core
19
19
  library: '@jielga/tmdatagrid'
20
- library_version: '2.1.4'
20
+ library_version: '2.2.0'
21
21
  sources:
22
22
  - 'Jielga/react:packages/tmdatagrid/docs/row-selection.md'
23
23
  - 'Jielga/react:packages/tmdatagrid/docs/row-interaction.md'
@@ -37,8 +37,8 @@ cell cursors and ranges are `cell-selection`.
37
37
  ## Selection
38
38
 
39
39
  `selectionMode` sets what selecting looks like **and** what a bare row click
40
- does. It is one option rather than two because a click can toggle a
41
- multi-selection or move a highlight, but not both.
40
+ does. A bare click either toggles the selection or moves the highlight, never
41
+ both.
42
42
 
43
43
  | Mode | Checkbox column | Row click |
44
44
  | --- | --- | --- |
@@ -51,8 +51,8 @@ multi-selection or move a highlight, but not both.
51
51
  const grid = useTMDataGrid({ data, columns, selectionMode: "row" });
52
52
  ```
53
53
 
54
- The first two write to TanStack's `rowSelection`, so the toolbar count,
55
- `getSelectedRowModel()` and persistence all behave the same either way. Under
54
+ The first two write to TanStack's `rowSelection`, so `getSelectedRowModel()`
55
+ and `onRowSelectionChange` behave the same either way. Under
56
56
  `"row"` the click follows desktop-list conventions: a plain click
57
57
  makes the selection this row, Ctrl/Cmd toggles it and leaves the rest, Shift
58
58
  selects the range from the anchor, Ctrl+Shift adds that range. Rows are
@@ -61,7 +61,7 @@ focusable in this mode, and Space or Enter toggles the focused row.
61
61
  `enableRowSelection: false` removes the checkbox column and row-click selection
62
62
  in any mode.
63
63
 
64
- ### The highlight is not a selection
64
+ ### The highlighted row
65
65
 
66
66
  The highlight is state of its own, not a slice of `rowSelection`. That is what
67
67
  lets `"checkboxAndHighlight"` run both at once: tick rows for a bulk action,
@@ -81,8 +81,9 @@ const grid = useTMDataGrid({
81
81
  ### Reading a selection
82
82
 
83
83
  Read it through the table store rather than by calling the method directly.
84
- `table` keeps one identity across renders, so the React Compiler caches a bare
85
- `getSelectedRowModel()` and the component stops updating.
84
+ A bare read is stale in any component that does not call `useTMDataGrid`
85
+ itself, and under the React Compiler also in the one that does; `useSelector`
86
+ subscribes either way.
86
87
 
87
88
  ```tsx
88
89
  import { useSelector } from "@tanstack/react-store";
@@ -160,9 +161,10 @@ the row with `data-context-menu` while its menu is open, so an action meant for
160
161
  a multi-selection reads `table` and falls back to the clicked row.
161
162
 
162
163
  Under `cellSelection: "range"` the grid has its own items for this menu: copy,
163
- export and include headers. `internalItems` is those items, and **using it takes
164
- over the composition**: place it and the menu is exactly what you returned; omit
165
- it and the grid keeps its own items above a divider and yours below.
164
+ export and include headers. `internalItems` holds those items, and **using it
165
+ takes over the composition**. Place it, and the menu is exactly what you
166
+ returned. Omit it, and the grid renders its items above a divider and yours
167
+ below.
166
168
 
167
169
  ```tsx
168
170
  renderRowContextMenu={({ row, internalItems }) => (
@@ -232,12 +234,14 @@ const grid = useTMDataGrid({
232
234
  });
233
235
  ```
234
236
 
235
- Each panel is measured, so heights need not be uniform.
236
- `renderDetailsEstHeight` (default `160`) is what the virtualizer assumes for one
237
- it has not measured yet. A generated chevron column, `DETAILS_COLUMN_ID`, is
238
- pinned left after the checkbox and tree lanes, or right, inside the edit lane,
239
- under `detailsColumnPosition: "right"`; it cannot be hidden, moved, resized or
240
- unpinned, and its header opens and closes every panel.
237
+ The panel opens below the row's cells, which stay where they are; there is no
238
+ mode in which a panel replaces the row. Each panel is measured, so heights need
239
+ not be uniform. `renderDetailsEstHeight` (default `160`) is what the virtualizer
240
+ assumes for one it has not measured yet; set it to the typical panel height.
241
+ `DETAILS_COLUMN_ID` is a generated chevron lane. It is pinned left, after the
242
+ checkbox and tree lanes; under `detailsColumnPosition: "right"` it is pinned
243
+ right, inside the edit lane. It cannot be hidden, moved, resized or unpinned.
244
+ Its header opens and closes every panel.
241
245
 
242
246
  Which rows are open is TanStack's own `expanded` state, so anything can open
243
247
  one - `row.toggleExpanded()` from a menu item, `initialState.expanded`,
@@ -246,6 +250,13 @@ is a cell spanning the row, not a row: `aria-rowcount` still counts records, and
246
250
  a click inside it does not select the row underneath. Group rows have no panel;
247
251
  expanding one opens its children.
248
252
 
253
+ Rows are virtualized. A row that leaves the viewport and the `overscan`
254
+ unmounts, and its panel unmounts with it, so state held inside the panel is
255
+ lost; keep such state outside the grid, keyed by `row.id`. `renderDetails` runs
256
+ on every render of the body, so give a heavy panel a `memo` component and pass
257
+ it `row.original`, not the row. Key a stateful child by `row.id`; that is stable
258
+ only when `getRowId` returns the entity id.
259
+
249
260
  ## Pinning and numbering
250
261
 
251
262
  Two independent lanes at the edges.
@@ -264,7 +275,7 @@ There is no built-in pin gesture and no pin icon. Build one from TanStack's own
264
275
  either as a display column whose cell is a pin button or in the row context
265
276
  menu. Read the pinned state through a subscription
266
277
  (`useSelector(row.table.store, () => row.getIsPinned())`) rather than calling it
267
- in a component body: the `row` identity survives a pin, so the React Compiler
278
+ in a component body. The `row` identity survives a pin, so the React Compiler
268
279
  would cache the call and the icon would never change. Whichever control you
269
280
  build, include unpin in it. Pinned rows leave the scrolling order and render in
270
281
  sticky blocks: top under the header, bottom above the summary row.
@@ -300,13 +311,13 @@ Correct:
300
311
  rowStyle={() => ({ "--row-bg": "pink" })}
301
312
  ```
302
313
 
303
- Source: `packages/tmdatagrid/docs/row-styling.md` (Set `--row-bg`, not `background`).
314
+ Source: `packages/tmdatagrid/docs/row-styling.md` (Set the row background).
304
315
 
305
316
  ### CRITICAL Reading the selection without subscribing
306
317
 
307
- `table` keeps one identity for the life of the grid, so the React Compiler
308
- memoizes a bare `getSelectedRowModel()` against it. The count renders once and
309
- then never changes.
318
+ A bare read is stale in any component that does not call `useTMDataGrid`
319
+ itself, and under the React Compiler also in the one that does; `useSelector`
320
+ subscribes either way. A stale count renders once and then never changes.
310
321
 
311
322
  Wrong:
312
323
 
@@ -327,8 +338,8 @@ Source: `packages/tmdatagrid/docs/row-selection.md` (Acting on a selection).
327
338
  ### HIGH Looking for the highlight in `rowSelection`
328
339
 
329
340
  The highlight is separate state, which is what lets `"checkboxAndHighlight"`
330
- run both at once. It never appears in `rowSelection`, `getSelectedRowModel()` or
331
- the persisted selection slice.
341
+ run both at once. It never appears in `rowSelection` or
342
+ `getSelectedRowModel()`.
332
343
 
333
344
  Wrong:
334
345
 
@@ -343,7 +354,7 @@ Correct:
343
354
  const current = useSelector(grid.ui, (state) => state.highlightedRowId);
344
355
  ```
345
356
 
346
- Source: `packages/tmdatagrid/docs/row-selection.md` (The highlight is not a selection).
357
+ Source: `packages/tmdatagrid/docs/row-selection.md` (The highlighted row).
347
358
 
348
359
  ### HIGH Expecting a click handler to replace the built-in behaviour
349
360
 
@@ -382,18 +393,29 @@ written as though every row reaches it silently skips them.
382
393
 
383
394
  Source: `packages/tmdatagrid/docs/row-interaction.md`, `packages/tmdatagrid/docs/row-pinning.md`.
384
395
 
385
- ### MEDIUM Open panels closing when `data` is replaced
396
+ ### MEDIUM Keeping state inside a details panel
386
397
 
387
- TanStack resets `expanded` when the row structure changes, so a refetch collapses
388
- every open panel.
398
+ A panel unmounts with its row when the row leaves the virtual window, so a form
399
+ or an accordion inside `renderDetails` loses its state on a long scroll and
400
+ comes back reset.
389
401
 
390
- Correct:
402
+ Correct: hold the state outside the grid, keyed by `row.id`, and pass it in.
391
403
 
392
404
  ```tsx
393
- useTMDataGrid({ data, columns, renderDetails, autoResetExpanded: false });
405
+ const [drafts, setDrafts] = useState<Record<string, Draft>>({});
406
+ useTMDataGrid({
407
+ data,
408
+ columns,
409
+ renderDetails: ({ row }) => (
410
+ <DetailsForm draft={drafts[row.id]} onChange={(d) => setDrafts((all) => ({ ...all, [row.id]: d }))} />
411
+ ),
412
+ });
394
413
  ```
395
414
 
396
- Source: `packages/tmdatagrid/docs/row-details.md`.
415
+ `autoResetExpanded` is already `false` by default, so a refetch does not close
416
+ the panels; pass `autoResetExpanded: true` to get TanStack's collapse back.
417
+
418
+ Source: `packages/tmdatagrid/docs/row-details.md` (Panel behaviour).
397
419
 
398
420
  ### MEDIUM Expecting pinned rows to persist
399
421
 
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: core
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '2.1.4'
18
+ library_version: '2.2.0'
19
19
  sources:
20
20
  - 'Jielga/react:packages/tmdatagrid/docs/server-side.md'
21
21
  - 'Jielga/react: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.4'
16
+ library_version: '2.2.0'
17
17
  sources:
18
18
  - 'Jielga/react:packages/tmdatagrid/docs/testing.md'
19
19
  - 'Jielga/react:playwright/support/DataGrid.ts'
@@ -104,6 +104,17 @@ match the same rows, and the negative is `:not([data-new])` in CSS and
104
104
 
105
105
  **Keyed by both**: `editor`.
106
106
 
107
+ **The generated lanes** are columns with exported ids: `ROW_NUMBER_COLUMN_ID`
108
+ (`__rowNumber__`), `SELECT_COLUMN_ID` (`__select__`), `GROUP_COLUMN_ID`
109
+ (`__group__`), `DETAILS_COLUMN_ID` (`__details__`) and `EDIT_COLUMN_ID`
110
+ (`__edit__`). A lane's header is
111
+ `[data-dg-part="header"][data-column-id="__select__"]`, a lane cell
112
+ `[data-row-id="42"][data-column-id="__select__"]:not([data-dg-part])`, and the
113
+ control inside it has a row-keyed part: `select-row`, `details-toggle`,
114
+ `group-toggle`, the edit lane's buttons. `select-all` and `details-toggle-all`
115
+ sit in the lane headers. The checkbox parts are on the `<input>` itself, so
116
+ `check()` works on them directly. There is no `data-testid`.
117
+
107
118
  Inside a `filter-row` the controls are `filter-column`, `filter-operator` and
108
119
  `filter-value` - or `filter-value-from` / `filter-value-to` for `between` - and
109
120
  `filter-remove` is its ✕. None of them carries `data-column-id`; scope through
@@ -12,7 +12,7 @@ description: >
12
12
  metadata:
13
13
  type: core
14
14
  library: '@jielga/tmdatagrid'
15
- library_version: '2.1.4'
15
+ library_version: '2.2.0'
16
16
  sources:
17
17
  - 'Jielga/react:packages/tmdatagrid/docs/testing.md'
18
18
  - 'Jielga/react: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.4'
15
+ library_version: '2.2.0'
16
16
  sources:
17
17
  - 'Jielga/react:packages/tmdatagrid/docs/testing.md'
18
18
  - 'Jielga/react:packages/tmdatagrid/docs/editing.md'