@jielga/tmdatagrid 2.0.0-beta.9 → 2.0.1

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 (157) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1281 -768
  3. package/dist/index.js +4607 -3250
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/adding-rows.md +132 -0
  7. package/docs/anatomy.md +119 -0
  8. package/docs/card-view.md +108 -0
  9. package/docs/cell-selection.md +194 -0
  10. package/docs/column-layout.md +182 -0
  11. package/docs/column-menu.md +66 -0
  12. package/docs/columns.md +269 -0
  13. package/docs/components.md +311 -0
  14. package/docs/draft-store.md +242 -0
  15. package/docs/editing.md +303 -0
  16. package/docs/editors.md +250 -0
  17. package/docs/export.md +319 -0
  18. package/docs/filtering.md +362 -0
  19. package/docs/getting-started.md +123 -0
  20. package/docs/grouping.md +165 -0
  21. package/docs/loading-and-empty.md +92 -0
  22. package/docs/localization.md +79 -0
  23. package/docs/menu.md +143 -0
  24. package/docs/migrating-to-2.md +163 -0
  25. package/docs/pagination.md +144 -0
  26. package/docs/persistence.md +114 -0
  27. package/docs/portfolio-rebalancer.md +94 -0
  28. package/docs/query-builder.md +179 -0
  29. package/docs/quick-search.md +84 -0
  30. package/docs/row-details.md +115 -0
  31. package/docs/row-interaction.md +149 -0
  32. package/docs/row-pinning.md +132 -0
  33. package/docs/row-selection.md +136 -0
  34. package/docs/row-styling.md +133 -0
  35. package/docs/scrolling.md +112 -0
  36. package/docs/server-query.md +246 -0
  37. package/docs/server-side.md +206 -0
  38. package/docs/sorting.md +101 -0
  39. package/docs/styling.md +126 -0
  40. package/docs/summary-row.md +76 -0
  41. package/docs/testing.md +744 -0
  42. package/docs/toolbar.md +161 -0
  43. package/docs/use-tm-data-grid.md +361 -0
  44. package/package.json +22 -46
  45. package/skills/appearance/SKILL.md +72 -19
  46. package/skills/cell-selection/SKILL.md +49 -48
  47. package/skills/columns/SKILL.md +125 -70
  48. package/skills/columns/references/columns-api.md +59 -0
  49. package/skills/data/SKILL.md +112 -18
  50. package/skills/editing/SKILL.md +76 -42
  51. package/skills/editing/references/common-mistakes.md +77 -69
  52. package/skills/editing/references/editing-api.md +25 -20
  53. package/skills/editing/references/editors-and-validation.md +80 -18
  54. package/skills/filtering/SKILL.md +155 -41
  55. package/skills/getting-started/SKILL.md +116 -16
  56. package/skills/grouping/SKILL.md +31 -16
  57. package/skills/migrating-to-2/SKILL.md +244 -0
  58. package/skills/options/SKILL.md +24 -12
  59. package/skills/rows/SKILL.md +22 -18
  60. package/skills/rows/references/rows-api.md +10 -6
  61. package/skills/server-side/SKILL.md +170 -17
  62. package/skills/testing/SKILL.md +150 -32
  63. package/skills/testing-components/SKILL.md +230 -0
  64. package/skills/testing-editing/SKILL.md +240 -0
  65. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  66. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
  67. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  68. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  69. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
  70. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  71. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
  72. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
  73. package/src/components/TMDataGridExportPicker.module.css +77 -0
  74. package/src/components/TMDataGridExportPicker.tsx +234 -0
  75. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  76. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  77. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  78. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  79. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  80. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  81. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
  82. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  83. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  84. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  85. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  86. package/src/components/TMDataGridMenu.tsx +357 -0
  87. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
  88. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
  89. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
  90. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  91. package/src/components/TMDataGridToolbar.tsx +181 -0
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  96. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  97. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  98. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  99. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  100. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  101. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  102. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  103. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  104. package/src/components/filters/controlLayout.ts +32 -0
  105. package/src/components/filters/filterControlFor.ts +65 -0
  106. package/src/components/generatedColumns.tsx +187 -0
  107. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  108. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  109. package/src/components/useHideableColumns.ts +52 -0
  110. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  111. package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
  112. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  113. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  114. package/src/core/controlledStateSync.ts +108 -0
  115. package/src/core/deletedRows.ts +34 -0
  116. package/src/core/dom.ts +74 -0
  117. package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
  118. package/src/core/export.ts +704 -0
  119. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  120. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  121. package/src/core/filterSurface.ts +99 -0
  122. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  123. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  124. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  125. package/src/core/pageReset.ts +120 -0
  126. package/src/core/pagination.ts +81 -0
  127. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  128. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  129. package/src/{tmdatagrid/index.ts → index.ts} +69 -35
  130. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
  131. package/src/useTMDataGridExport.ts +78 -0
  132. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  133. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  134. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  135. package/src/tmdatagrid/core/cellExport.ts +0 -320
  136. /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  141. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  142. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  143. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  144. /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  155. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  156. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  157. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -63,11 +63,11 @@ Bind any control to `field` exactly as inside any TanStack Form:
63
63
  `field.state.value`, `field.state.meta.errors`, `field.handleChange`,
64
64
  `field.handleBlur`.
65
65
 
66
- Binding `field.state.meta.errors` is what shows a refused commit: the built-in
67
- editors pass the first error to the input's `error` prop, and an editor that
68
- binds nothing leaves a blocked save as `data-invalid` on the cell with no
69
- message on screen. An entry is a string from a function validator, or an issue
70
- carrying a `message` from a schema.
66
+ The message itself is the host's: it shows in a tooltip on the editor, opened
67
+ by focus and by hover, for a custom editor as much as a built-in one. What an
68
+ editor binds is the invalid state - the built-ins pass a boolean to the input's
69
+ `error` prop for the border alone, and an editor that binds nothing looks
70
+ unchanged while the commit is refused.
71
71
 
72
72
  ```tsx
73
73
  import { Slider } from "@mantine/core";
@@ -157,6 +157,10 @@ meta: {
157
157
  }
158
158
  ```
159
159
 
160
+ The map receives `TMDataGridEditValueMapArgs`: `{ value, previous, row, column, table }`.
161
+ Unlike `meta.edit.enabled`, its `row` is `Row<TMDataGridFeatures, TMDataGridRowData>`
162
+ with or without the column helper.
163
+
160
164
  The grid applies it in the editor host, around the field every editor writes
161
165
  through, so it covers the six built-ins, a custom `meta.edit.editor`, and the
162
166
  type-to-edit seed character.
@@ -193,8 +197,13 @@ meta: { edit: { validate: z.string().min(2, "At least two characters") } }
193
197
  // Object form: pick the trigger.
194
198
  meta: { edit: { validate: { onBlur: z.string().email("Not an email address") } } }
195
199
 
196
- // A plain function works too.
197
- meta: { edit: { validate: ({ value }) => (value > 0 ? undefined : "Must be positive") } }
200
+ // A plain function works too. `value` is typed `never`, so annotate the parameter.
201
+ meta: {
202
+ edit: {
203
+ validate: ({ value }: { value: unknown }) =>
204
+ typeof value === "number" && value > 0 ? undefined : "Must be positive",
205
+ },
206
+ }
198
207
  ```
199
208
 
200
209
  `normalizeFieldValidate(validate)` is exported for consumers building their own
@@ -224,8 +233,8 @@ const grid = useTMDataGrid({
224
233
  ```
225
234
 
226
235
  Issues with a path land on the matching column's cell; pathless issues land on
227
- the row, where the message shows in the edit lane's tooltip - on the open
228
- row's ✓, and on the parked row's marker. A nested schema's issues follow the
236
+ the row, where the message shows in the edit lane's tooltip on the open
237
+ row's ✓. A nested schema's issues follow the
229
238
  same rule, so a `address.city` issue lands on the column whose `editField` is
230
239
  `"address.city"`.
231
240
 
@@ -236,7 +245,8 @@ unedited value and cannot pass. Use `"row"`.
236
245
  ## Cross-row rules
237
246
 
238
247
  `editing.tableValidators` holds the rules that need the other rows. Its
239
- `onSubmit` / `onSubmitAsync` receive `{ value, rowId, isNew, rows }`:
248
+ `onSubmit` / `onSubmitAsync` receive `TMDataGridTableValidateArgs`,
249
+ `{ value, rowId, isNew, rows }`:
240
250
  `value` is the committing row as drafted, and `rows` is
241
251
  `Array<{ rowId, value }>` - the collection as it would stand if the commit
242
252
  landed, with every draft overlaid, entry rows appended and deletion-marked
@@ -261,9 +271,59 @@ row's cells. `onSubmit` runs first, and its failure stands without
261
271
  `onSubmitAsync` running. Errors land on the committing row only.
262
272
 
263
273
  The rules run at every commit - typed, ✓, `edit.setCellValue`, an entry
264
- row's - after the row's own validators, and again for every parked row during
265
- `saveDrafts`: a draft that a later edit invalidated fails there, keeps its
266
- markers, and the save resolves `false`.
274
+ row's - after the row's own validators, and again for every committed row during
275
+ `saveDrafts`, the only rules that run there: a committed row that a later edit
276
+ invalidated is reopened with its errors, and the save reports it in `reopened`.
277
+
278
+ ## Derived columns and a table-wide rule
279
+
280
+ A column whose value depends on the other rows - a weight as a share of the
281
+ total - cannot be an `accessorFn`, which is handed one row. Derive the whole
282
+ collection with `useMemo` and pass the finished rows as `data`;
283
+ `editing.onCommit` writes back to the source array, and the derived rows arrive
284
+ on the next render. Under `mode: "cell"` with no draft store, every dependent
285
+ column follows the commit.
286
+
287
+ ```tsx
288
+ const positions = useMemo(() => {
289
+ const valued = holdings.map((h) => ({ ...h, marketValue: h.price * h.shares }));
290
+ const total = valued.reduce((sum, h) => sum + h.marketValue, 0);
291
+ return valued.map((h) => ({
292
+ ...h,
293
+ currentPct: (h.marketValue / total) * 100,
294
+ drift: h.targetPct - (h.marketValue / total) * 100,
295
+ }));
296
+ }, [holdings]);
297
+ ```
298
+
299
+ A rule over the whole collection, such as targets that may not total more than
300
+ 100%, is a `tableValidators` rule, while a bound on one cell (between 0 and 100)
301
+ stays on `meta.edit.validate`. `editing.columns` keeps every other column
302
+ read-only:
303
+
304
+ ```tsx
305
+ editing: {
306
+ mode: "cell",
307
+ // Only the target weight takes edits; everything else is market data.
308
+ columns: ["targetPct"],
309
+ onCommit: ({ rowId, value }) =>
310
+ setHoldings((previous) =>
311
+ previous.map((h) => (h.id === rowId ? { ...h, targetPct: value.targetPct } : h)),
312
+ ),
313
+ tableValidators: {
314
+ // `rows` already holds the committing row's drafted value.
315
+ onSubmit: ({ rows }) => {
316
+ const total = rows.reduce((sum, r) => sum + Number(r.value.targetPct ?? 0), 0);
317
+ return total > 100.005
318
+ ? { fields: { targetPct: `Targets would total ${pct(total)}` } }
319
+ : undefined;
320
+ },
321
+ },
322
+ }
323
+ ```
324
+
325
+ Source: `packages/tmdatagrid/docs/portfolio-rebalancer.md`, and the demo
326
+ `apps/docs/src/examples/demos/recipes/PortfolioRebalancer.tsx`.
267
327
 
268
328
  ## Server-side errors
269
329
 
@@ -283,9 +343,9 @@ rowValidators: {
283
343
  },
284
344
  ```
285
345
 
286
- A commit blocked by validation keeps the editor open with the message on the
287
- input. A rejected `editing.onCommit` keeps the draft too, with the error on the
288
- row.
346
+ A commit blocked by validation keeps the editor open, invalid, with the message
347
+ in its tooltip. A rejected `editing.onCommit` keeps the draft too, with the
348
+ error on the row.
289
349
 
290
350
  ## Where the state shows
291
351
 
@@ -294,11 +354,13 @@ row.
294
354
  | The cell's own value | A held draft is displayed: the cell renders the draft value through the column's `cell` renderer, in every mode |
295
355
  | Blue cell corner | The field is dirty against its original value |
296
356
  | Red cell corner | The field carries a validation error |
297
- | Row error text | A pathless rule failed, or a commit was rejected. In the lane's tooltip: the open row's ✓, or the parked row's marker |
357
+ | Field error message | In a tooltip on the open editor, shown while the input has focus and on hover |
358
+ | Row error text | A pathless rule failed, or a commit was rejected. In the lane's tooltip, on the open row's ✓ |
298
359
  | `data-dirty` on the row | The row holds a dirty draft |
299
360
 
300
361
  The same information is readable from `edit.store`: `rows[rowId].dirtyFields`,
301
362
  `rows[rowId].errorFields`, `rows[rowId].errorMessages` (`{ field, message }`
302
363
  pairs), `rows[rowId].hasRowError`, `rows[rowId].isSubmitting`, and
303
364
  `rows[rowId].values` for the draft itself. The pathless message's text is not
304
- in the store; read it from `edit.getForm(rowId)?.state.errors`.
365
+ in the store; read it from `edit.getForm(rowId)?.state.errors` on the open
366
+ row that carries it.
@@ -3,24 +3,27 @@ name: filtering
3
3
  description: >
4
4
  Narrow the rows a TMDataGrid shows. Covers the shared tmDataGrid filter
5
5
  function and its {operator, value} model, the eighteen operators and which
6
- meta.type offers each, meta.filter.defaultOperator, isFilterActive and the
7
- half-typed filter, the filter panel, TMDataGrid.FilterButton,
8
- TMDataGrid.FilterPills with its api prop, openColumnFilter, replacing a value
9
- control with DgRangeSliderFilter / DgDateRangeFilter / DgAutocompleteFilter /
10
- DgTriStateFilter or a meta.filter.control component, per-column filterFn, and
11
- the quick search: TMDataGrid.Search, quickSearchMode fuzzy or contains,
12
- fuzzyGlobalFilterFn, enableMatchHighlighting and enableGlobalFilter. Load when
13
- adding filters, choosing operators, building a filter control, showing active
14
- filters outside the grid, or wiring a search box.
6
+ meta.type offers each, meta.filter.operators to offer a column only a subset
7
+ of them, meta.filter.defaultOperator, isFilterActive and the half-typed
8
+ filter, the filters option and its surfaces (popup, sidebar, none, plus
9
+ inHeader for header filters), TMDataGrid.FilterPanel and its layout prop,
10
+ TMDataGrid.FilterButton, TMDataGrid.FilterPills with its api prop,
11
+ openColumnFilter, replacing a value control with DgRangeSliderFilter /
12
+ DgDateRangeFilter / DgAutocompleteFilter / DgTriStateFilter or a
13
+ meta.filter.control component, per-column filterFn, and the quick search:
14
+ TMDataGrid.Search, quickSearchMode fuzzy or contains, fuzzyGlobalFilterFn,
15
+ enableMatchHighlighting and enableGlobalFilter. Load when adding filters,
16
+ choosing operators, building a filter control, showing active filters outside
17
+ the grid, or wiring a search box.
15
18
  metadata:
16
19
  type: core
17
20
  library: '@jielga/tmdatagrid'
18
- library_version: '2.0.0-beta.9'
21
+ library_version: '2.0.1'
19
22
  sources:
20
- - 'Jielga/TMDataGrid:src/docs/filtering.md'
21
- - 'Jielga/TMDataGrid:src/docs/quick-search.md'
22
- - 'Jielga/TMDataGrid:src/tmdatagrid/core/filterOperators.ts'
23
- - 'Jielga/TMDataGrid:src/tmdatagrid/core/quickSearch.ts'
23
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/filtering.md'
24
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/quick-search.md'
25
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/filterOperators.ts'
26
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/quickSearch.ts'
24
27
  ---
25
28
 
26
29
  # TMDataGrid - Filtering
@@ -48,7 +51,9 @@ leaving that end open).
48
51
 
49
52
  A filter with an empty value **stays in state** so the panel keeps its row while
50
53
  the user types. It matches every row, does not set the header indicator and
51
- produces no pill. `isFilterActive(value)` tests for that state; presence in
54
+ produces no pill. `isFilterActive(value)` tests for that state, and
55
+ `activeColumnFilters(columnFilters | table)` applies it across the slice and
56
+ types what it hands back (entries in any other value shape are dropped); presence in
52
57
  `columnFilters` does not.
53
58
 
54
59
  ## Operators
@@ -80,16 +85,105 @@ columnHelper.accessor("salary", {
80
85
  });
81
86
  ```
82
87
 
83
- ## The panel and the pills
88
+ `meta.filter.operators` narrows the list a column offers to a subset of its
89
+ type's - for a column whose backend answers only some operators, so the user is
90
+ never offered one the query cannot express. The panel dropdown and the header
91
+ funnel show only those, in the type's order. An operator the type does not
92
+ offer is ignored; a list that leaves nothing falls back to the type's full set.
93
+ Without `defaultOperator`, a fresh filter opens on the type's default when it is
94
+ offered, else on the first offered operator.
84
95
 
85
- `TMDataGrid.FilterPanel` is column, operator and value rows under a "Filters"
86
- header, above "Add filter" and "Clear all". It is rendered by
87
- `TMDataGrid.Table`; Escape and a click outside close it, with `FilterButton`
88
- exempt from the click-away so it stays a toggle. Closing only hides it; the
89
- filters stay. **Clear all** drops every filter, half-typed ones included.
96
+ ```tsx
97
+ columnHelper.accessor("customer", {
98
+ header: "Customer",
99
+ meta: { filter: { operators: ["contains", "equals", "isEmpty", "isNotEmpty"] } },
100
+ });
101
+ ```
102
+
103
+ `getColumnOperators(column)` returns the resolved list and
104
+ `getColumnDefaultOperator(column)` the operator a fresh filter opens on. The
105
+ `server-side` skill shows the list typed together with the API mapping table.
106
+
107
+ ## The filters option
108
+
109
+ `filters` on `useTMDataGrid` picks the surface. It is read field by field, so a
110
+ literal is fine.
111
+
112
+ | Option | Type | Default | What it does |
113
+ | --- | --- | --- | --- |
114
+ | `surface` | `"popup" \| "sidebar" \| "none"` | `"popup"` | What `TMDataGrid.Table` renders and `FilterButton` toggles. |
115
+ | `sidebarSide` | `"left" \| "right"` | `"right"` | Which side the sidebar sits on. Ignored by the other surfaces. |
116
+ | `sidebarWidth` | `string` | `"280px"` | Sidebar width, any CSS length. Ignored by the other surfaces. |
117
+ | `defaultOpen` | `boolean` | `true` under `"sidebar"`, else `false` | Whether the surface starts open. Read once, at mount. Under `"none"`, the starting value of `ui.state.filterPanelOpen`. |
118
+ | `inHeader` | `boolean` | `false` | A second header row of per-column controls. Independent of `surface`. |
119
+
120
+ `surface` and `inHeader` are separate choices: `{ surface: "none", inHeader: true }`
121
+ is header filters alone, `{ inHeader: true }` keeps the popup as well.
122
+
123
+ ```tsx
124
+ useTMDataGrid({ data, columns, filters: { surface: "sidebar", inHeader: true } });
125
+ ```
126
+
127
+ **Popup** - the default. Floats over the first body rows. A pointerdown
128
+ outside, Escape, and emptying it (the last row removed, or **Clear all**) all
129
+ close it; `FilterButton` is exempt from the click-away so it stays a toggle.
130
+
131
+ **Sidebar** - the same panel beside the rows, inside the grid frame and under
132
+ the toolbar. The rows give up the width rather than being covered, a click in
133
+ the table does not dismiss it, and clearing the filters leaves it standing.
134
+ Escape closes it. It starts open, being a layout choice; its rows are
135
+ `layout="stacked"`, since 280px has no room for the triple.
136
+
137
+ **Header filters** - `inHeader: true` adds a header row of value controls, one
138
+ per filterable column, on the same column tracks as everything else. The
139
+ column and operator dropdowns are not there: the column is the one the cell
140
+ sits over, and the operator is a funnel button beside the input, tinted off its
141
+ default. The column menu's Filter item and the funnel indicator both come off,
142
+ having nothing left to reveal; the filtered column's tinted title stays. A
143
+ narrow column clips its control - give it a `minSize`.
90
144
 
91
- `TMDataGrid.FilterPills` takes the grid as an `api` prop instead of reading
92
- context, so active filters can live in a page header or anywhere else:
145
+ Clearing a header control removes the column's `columnFilters` entry rather
146
+ than leaving an empty one, unless the user also picked a non-default operator,
147
+ which is kept. Panel rows still keep their empty filters.
148
+
149
+ `FilterButton` still toggles whatever `surface` names. `openColumnFilter` does
150
+ not: under `inHeader` it always focuses the header control and leaves the
151
+ surface closed.
152
+
153
+ **None** - `surface: "none"` renders no panel and no `FilterButton`, so a
154
+ hand-placed `TMDataGrid.FilterPanel` is the only one. Read
155
+ `ui.state.filterPanelOpen` if it belongs behind a control of your own.
156
+
157
+ ## TMDataGrid.FilterPanel
158
+
159
+ The panel of filter rows - one column / operator / value triple per filter, over "Add filter" and "Clear all".
160
+ A plain block with no title, no close button and no open state: it renders wherever it is mounted, and the popup and sidebar surfaces are wrappers around it.
161
+ It must be inside `<TMDataGrid>` (it reads the grid from context) and it only reads and writes `columnFilters`, so a `manualFiltering` grid gets it for free.
162
+ Closing a surface only hides it; the filters stay.
163
+ **Clear all** drops every filter, half-typed ones included.
164
+
165
+ | Prop | Type | Default | Description |
166
+ | --- | --- | --- | --- |
167
+ | `layout` | `"row" \| "stacked"` | `"row"` | `"row"` is side by side and wants about 550px; `"stacked"` fills a narrow host. Passed to every value control as its `layout`. |
168
+
169
+ ```tsx
170
+ <Drawer opened={open} onClose={close}>
171
+ <TMDataGrid.FilterPanel layout="stacked" />
172
+ </Drawer>
173
+ ```
174
+
175
+ ## TMDataGrid.FilterButton
176
+
177
+ The toolbar button that toggles the filter surface, tinted with the count of active filters.
178
+ Opening an empty panel seeds a filter row on the first filterable column; with filters already in state it opens on those.
179
+ Renders nothing when no column can be filtered, and nothing under `surface: "none"`.
180
+ No props.
181
+
182
+ ## TMDataGrid.FilterPills
183
+
184
+ One pill per **active** filter, `First name: Sofia ✕`, where the ✕ clears it and a click on the label calls `openColumnFilter`.
185
+ The label spells the operator out unless it is the type's default: `Age is greater than 30`, but `First name: Sofia`.
186
+ It takes the grid as an `api` prop instead of reading context, so it renders anywhere on the page.
93
187
 
94
188
  ```tsx
95
189
  import { TMDataGridFilterPills } from "@jielga/tmdatagrid";
@@ -97,11 +191,18 @@ import { TMDataGridFilterPills } from "@jielga/tmdatagrid";
97
191
  <TMDataGridFilterPills api={grid} onPillClick={(columnId) => focus(columnId)} />;
98
192
  ```
99
193
 
100
- One pill per **active** filter, `First name: Sofia ✕`, where ✕ clears it and a
101
- click on the label reopens the panel on its column. The label spells the
102
- operator out unless it is the type's default: `Age is greater than 30`, but
103
- `First name: Sofia`. `openColumnFilter(api, columnId)` does the same reopening
104
- from anywhere, seeding an empty row if the column has none.
194
+ | Prop | Type | Default | Description |
195
+ | --- | --- | --- | --- |
196
+ | `api` | `TMDataGridApi<TData>` | - | The object returned by `useTMDataGrid`. |
197
+ | `size` | `MantineSize` | `"sm"` | Pill size. |
198
+ | `showClearAll` | `boolean` | `true` | "Clear all", shown once two filters are active. |
199
+ | `onPillClick` | `(columnId: string) => void` | - | Replaces the default click behaviour. |
200
+ | `className` | `string` | - | Added to the wrapper class. |
201
+
202
+ ## openColumnFilter
203
+
204
+ `openColumnFilter(api, columnId)` seeds an empty filter if the column has none, then opens the surface on that column's panel row - or, under `inHeader`, scrolls the column's header control into view and focuses it, leaving the surface closed.
205
+ It is what a pill's label and the column menu's Filter item both call.
105
206
 
106
207
  ## Replacing the value control
107
208
 
@@ -220,12 +321,13 @@ const activeCount = columnFilters.length;
220
321
  Correct:
221
322
 
222
323
  ```tsx
223
- import { isFilterActive } from "@jielga/tmdatagrid";
324
+ import { activeColumnFilters } from "@jielga/tmdatagrid";
224
325
 
225
- const active = columnFilters.filter((filter) => isFilterActive(filter.value));
326
+ // The entries that narrow anything, with `value` typed rather than `unknown`.
327
+ const active = activeColumnFilters(columnFilters);
226
328
  ```
227
329
 
228
- Source: `src/docs/filtering.md` (How a filter is stored).
330
+ Source: `packages/tmdatagrid/docs/filtering.md` (How a filter is stored).
229
331
 
230
332
  ### HIGH Assuming `value` is always a string
231
333
 
@@ -246,7 +348,7 @@ if (operatorTakesRangeValue(operator)) {
246
348
  }
247
349
  ```
248
350
 
249
- Source: `src/tmdatagrid/core/filterOperators.ts`.
351
+ Source: `packages/tmdatagrid/src/core/filterOperators.ts`.
250
352
 
251
353
  ### HIGH A numeric column with no `meta.type`
252
354
 
@@ -255,7 +357,7 @@ Source: `src/tmdatagrid/core/filterOperators.ts`.
255
357
  `between` never appear in the panel, and comparisons run as text, where `"9"`
256
358
  sorts above `"10"`.
257
359
 
258
- Source: `src/docs/filtering.md` (Operators).
360
+ Source: `packages/tmdatagrid/docs/filtering.md` (Operators).
259
361
 
260
362
  ### HIGH A filter control defined inside the component
261
363
 
@@ -272,7 +374,7 @@ const StatusFilter: TMDataGridFilterControlComponent = (args) => { /* … */ };
272
374
  meta: { filter: { control: StatusFilter } }
273
375
  ```
274
376
 
275
- Source: `src/docs/filtering.md` (Writing your own).
377
+ Source: `packages/tmdatagrid/docs/filtering.md` (Writing your own).
276
378
 
277
379
  ### MEDIUM Writing the whole filter from a custom control
278
380
 
@@ -292,7 +394,7 @@ Correct:
292
394
  onChange(picked);
293
395
  ```
294
396
 
295
- Source: `src/docs/filtering.md` (Writing your own).
397
+ Source: `packages/tmdatagrid/docs/filtering.md` (Writing your own).
296
398
 
297
399
  ### MEDIUM Expecting match highlighting in a custom cell
298
400
 
@@ -301,7 +403,7 @@ does not modify a custom renderer's output. A column with a `cell` renderer
301
403
  shows no marks whatever `enableMatchHighlighting` is set to. Equality operators
302
404
  highlight nothing either.
303
405
 
304
- Source: `src/docs/quick-search.md` (Match highlighting).
406
+ Source: `packages/tmdatagrid/docs/quick-search.md` (Match highlighting).
305
407
 
306
408
  ### MEDIUM Expecting fuzzy ranking to survive a sort
307
409
 
@@ -309,7 +411,7 @@ The rank ordering applies only while the search is the sole narrowing. Any sort
309
411
  or grouping replaces it. The ordering is derived and never written into
310
412
  `sorting`, so there is nothing to clear afterwards.
311
413
 
312
- Source: `src/docs/quick-search.md` (Fuzzy by default).
414
+ Source: `packages/tmdatagrid/docs/quick-search.md` (Fuzzy by default).
313
415
 
314
416
  ## Reference
315
417
 
@@ -318,20 +420,32 @@ Source: `src/docs/quick-search.md` (Fuzzy by default).
318
420
  | `enableColumnFilters` | Table option | `boolean` | `true` | `false` removes the panel, the button and the menu item. |
319
421
  | `enableColumnFilter` | Column option | `boolean` | `true` | `false` takes one column out of filtering. |
320
422
  | `meta.type` | Column meta | `TMDataGridColumnType` | `"string"` | Selects the operators and the value control. |
321
- | `meta.filter.defaultOperator` | Column meta | `TMDataGridFilterOperator` | The type's default | The operator a fresh filter opens on. |
423
+ | `meta.filter.operators` | Column meta | `readonly TMDataGridFilterOperator[]` | The type's list | The operators this column offers, a subset of its type's. |
424
+ | `meta.filter.defaultOperator` | Column meta | `TMDataGridFilterOperator` | The type's default, else the first offered | The operator a fresh filter opens on. |
322
425
  | `meta.filter.control` | Column meta | `TMDataGridFilterControlComponent` | By type and operator | Replaces the value control. Module scope. |
323
426
  | `filterFn` | Column option | name or fn | `"tmDataGrid"` | Custom matching for one column. |
324
- | `quickSearchMode` | Option | `"fuzzy" \| "contains"` | `"fuzzy"` | How the quick search matches. |
427
+ | `quickSearchMode` | Option | `TMDataGridQuickSearchMode`: `"fuzzy" \| "contains"` | `"fuzzy"` | How the quick search matches. |
325
428
  | `enableMatchHighlighting` | Option | `boolean` | `false` | Mark matched text in default-rendered cells. |
326
429
  | `enableGlobalFilter` | Table option | `boolean` | `true` | Also a column option. Removes the input, or one column's participation. |
327
430
  | `globalFilterFn` | Table option | filter fn | fuzzy | Overrides the matching, and the ranking with it. |
328
- | `TMDataGrid.FilterPanel` | Component | – | – | The panel of filter rows. |
431
+ | `TMDataGrid.FilterPanel` | Component | `layout: "row" \| "stacked"`, Mantine `BoxProps` | `"row"` | The panel of filter rows, as a plain block. Style props set on it. |
432
+ | `TMDataGridFilterPanelProps` · `TMDataGridFilterPanelLayout` | Types | – · `"row" \| "stacked"` | – | The props of `TMDataGrid.FilterPanel`, and the type of its `layout`. For wrapping the panel in a component of your own. |
433
+ | `filters` | Table option | `TMDataGridFiltersOptions` | `{ surface: "popup" }` | Which surface holds the filter controls. |
434
+ | `TMDataGridFiltersSettings` | Type | `Required<TMDataGridFiltersOptions>` | – | The `filters` option with its defaults filled in, as `api.filters`. |
435
+ | `TMDataGridFilterSurface` · `TMDataGridFilterSidebarSide` | Types | `"popup" \| "sidebar" \| "none"` · `"left" \| "right"` | – | The types of `filters.surface` and `filters.sidebarSide`. |
436
+ | `TMDataGridFilterControlArgs.layout` | Type | `TMDataGridFilterControlLayout` | – | How much room a value control has, and whether it names itself. |
437
+ | `TMDataGridFilterControlLayout` | Type | `"row" \| "stacked" \| "header"` | – | The type of `layout` on `TMDataGridFilterControlArgs`. `"header"` is the cell of the `inHeader` row. |
438
+ | `filterValueShape` | Export | `(operator) => TMDataGridFilterValueShape` | – | Which shape an operator's value takes. |
439
+ | `TMDataGridFilterValueShape` | Type | `"scalar" \| "set" \| "range"` | – | What `filterValueShape` returns. |
329
440
  | `TMDataGrid.FilterButton` | Component | – | – | Toolbar button opening the panel, with an active count. |
330
- | `TMDataGrid.FilterPills` | Component | `api`, `size`, `showClearAll`, `onPillClick`, `className` | – | Active filters as removable pills, renderable anywhere. |
441
+ | `TMDataGrid.FilterPills` | Component | `api`, `size`, `showClearAll`, `onPillClick`, Mantine `BoxProps` | – | Active filters as removable pills, renderable anywhere. Style props set on the wrapper. |
331
442
  | `TMDataGrid.Search` | Component | `placeholder`, `debounce` (`250`), `w` (`220`) | – | The debounced quick-search input. |
332
443
  | `openColumnFilter` | Export | `(api, columnId) => void` | – | Opens the panel on a column. |
333
444
  | `isFilterActive` | Export | `(value) => boolean` | – | Whether a filter value narrows anything. |
445
+ | `activeColumnFilters` | Export | `(columnFilters \| table) => Array<TMDataGridColumnFilter>` | – | The filters in the grid's own value shape that narrow anything, typed. |
446
+ | `TMDataGridColumnFilter` | Type | `{ id, value }` | – | One entry of `columnFilters`, with `value` typed as `TMDataGridFilterValue`. |
334
447
  | `getOperatorsForType` | Export | `(type) => operators` | – | The operator list a type offers. |
448
+ | `getColumnOperators` · `getColumnDefaultOperator` | Exports | `(column) => operators` · `(column) => operator` | – | One column's list after `meta.filter.operators`, and the operator a fresh filter on it opens on. |
335
449
  | `FILTER_OPERATOR_LABELS` | Export | record | – | The label shown for each operator. |
336
450
  | `formatFilterLabel` | Export | `({ label, type, filter }) => string` | – | The one-line description used on the pills. |
337
451
  | `emptyValueForOperator` · `operatorNeedsValue` · `operatorTakesArrayValue` · `operatorTakesRangeValue` | Exports | – | – | What shape of value an operator expects. |
@@ -4,19 +4,23 @@ description: >
4
4
  Set up TMDataGrid, a compound React data grid built on TanStack Table v9 and
5
5
  Mantine. Covers useTMDataGrid, the TMDataGrid root, context, the component
6
6
  catalog (Table, Footer, Toolbar, Spacer, SummaryCount, Search,
7
- LoadingIndicator, DraftActions, FilterButton, ColumnsButton, FilterPanel,
8
- FilterPills, ColumnsPanel), the size scale and the bounded-height layout
9
- requirement. Load when adding a grid, choosing which parts to render, or when
10
- rows do not appear.
7
+ LoadingIndicator, DraftActions, FilterButton, Menu, FilterPanel, FilterPills,
8
+ ColumnsPanel) with their props types, the size scale, the bounded-height
9
+ layout requirement, and rendering rows without TMDataGrid.Table through
10
+ getDisplayedRows (a card view). Load when adding a grid, choosing which parts
11
+ to render, replacing the Table with a renderer of your own, or when rows do
12
+ not appear.
11
13
  metadata:
12
14
  type: core
13
15
  library: '@jielga/tmdatagrid'
14
- library_version: '2.0.0-beta.9'
16
+ library_version: '2.0.1'
15
17
  sources:
16
- - 'Jielga/TMDataGrid:src/docs/getting-started.md'
17
- - 'Jielga/TMDataGrid:src/docs/anatomy.md'
18
- - 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'
19
- - 'Jielga/TMDataGrid:src/tmdatagrid/core/sizes.ts'
18
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/getting-started.md'
19
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/anatomy.md'
20
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/components.md'
21
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/card-view.md'
22
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/components/TMDataGrid.tsx'
23
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/sizes.ts'
20
24
  ---
21
25
 
22
26
  # TMDataGrid - Getting started
@@ -80,7 +84,9 @@ export function Employees({ data }: { data: Employee[] }) {
80
84
  <TMDataGrid.SummaryCount />
81
85
  <TMDataGrid.Spacer />
82
86
  <TMDataGrid.FilterButton />
83
- <TMDataGrid.ColumnsButton />
87
+ <TMDataGrid.Menu>
88
+ <TMDataGrid.Menu.Columns />
89
+ </TMDataGrid.Menu>
84
90
  </TMDataGrid.Toolbar>
85
91
 
86
92
  <TMDataGrid.Table<Employee> />
@@ -129,6 +135,7 @@ TanStack ships state and APIs for both but no `enable` option.
129
135
  | Configure the hook, or persist state | `options` |
130
136
  | Theme, size, compose the toolbar, translate | `appearance` |
131
137
  | Write tests against a grid | `testing` |
138
+ | Upgrade code written against a 2.0 beta | `migrating-to-2` |
132
139
 
133
140
  ## Layout
134
141
 
@@ -149,20 +156,43 @@ flex column.
149
156
  | Component | Props | Notes |
150
157
  | --- | --- | --- |
151
158
  | `TMDataGrid` | `table`, `ui`, `features`, `size`, `className`, `style` | Root. Provides context. `style` also takes CSS variables: `--dg-row-selected-bg`, `--dg-row-height`, `--dg-header-height`, `--dg-font-size`, `--dg-padding`. |
152
- | `TMDataGrid.Table` | `onRowClick(row)`, `renderRowContextMenu`, `renderColumnMenuItems`, `rowContextMenuProps` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `selectionMode: "row"`. |
159
+ | `TMDataGrid.Table` | `onRowClick(row)`, `renderRowContextMenu`, `renderColumnMenuItems`, `rowContextMenuProps` | Header, virtualized body, and whichever filter surface the `filters` option asks for. `onRowClick` runs in addition to selection under `selectionMode: "row"`. |
153
160
  | `TMDataGrid.Footer` | `pageSizeOptions` (default `[10, 25, 50, 100]`), `pagination` render prop | Pagination controls. Renders nothing unless pagination is enabled. |
154
161
  | `TMDataGrid.Toolbar` | `children` | Flex row above the grid. |
155
162
  | `TMDataGrid.Spacer` | - | Pushes later toolbar items right. |
156
163
  | `TMDataGrid.SummaryCount` | `children` | Visible rows out of total. |
157
- | `TMDataGrid.Search` | `placeholder`, `debounce` (default `250`), `w` (default `220`) | Quick search over every column, debounced into `globalFilter`. Renders nothing under `enableGlobalFilter: false`. |
164
+ | `TMDataGrid.Search` | `placeholder`, `debounce` (default `250`), `w` (default `220`) | Quick search over every column, debounced into `globalFilter`. Renders nothing under `enableGlobalFilter: false`. Also exported as `TMDataGridSearch`. |
158
165
  | `TMDataGrid.LoadingIndicator` | - | Small spinner while `meta.loading` is `true` and rows stay on screen. |
159
166
  | `TMDataGrid.DraftActions` | `renderActions` | Save with the pending count, and Discard. Renders nothing while editing is off - see the `editing` skill. |
160
- | `TMDataGrid.FilterButton` | - | Toggles filter panel. Renders nothing if no column is filterable. |
161
- | `TMDataGrid.ColumnsButton` | - | Opens column manager. Renders nothing if no column is hideable. |
162
- | `TMDataGrid.FilterPanel` | - | Rendered by `.Table`; exported for custom layouts. Header close button, Escape, click-away, "Add filter" and "Clear all". |
163
- | `TMDataGrid.ColumnsPanel` | - | Rendered by `.ColumnsButton`; exported for custom layouts. |
167
+ | `TMDataGrid.FilterButton` | - | Toggles the filter surface, seeding a filter row on the first filterable column. Renders nothing if no column is filterable, or under `filters.surface: "none"`. |
168
+ | `TMDataGrid.Menu` | `children`, `icon`, `label`, Mantine `MenuProps` | The burger and its dropdown: your own `Menu.Item`s, and `TMDataGrid.Menu.Columns`, the column chooser as menu items (renders nothing if no column is hideable). See the `appearance` skill. |
169
+ | `TMDataGrid.FilterPanel` | `layout` (`"row"` \| `"stacked"`, default `"row"`) | Filter rows over "Add filter" / "Clear all", as a plain block. Rendered by `.Table` inside the popup and the sidebar; place it yourself under `filters.surface: "none"`. See the `filtering` skill. |
170
+ | `TMDataGrid.ColumnsPanel` | - | The column chooser as plain controls, for a Popover or a Drawer. |
164
171
  | `TMDataGrid.FilterPills` | `api`, `size` (default `"sm"`), `showClearAll` (default `true`), `onPillClick(columnId)`, `className` | One pill per active filter, ✕ to clear it. Takes the api as a prop, so it can be rendered outside the grid. Also exported as `TMDataGridFilterPills`. |
165
172
 
173
+ Each component's props type is exported, for wrapping a part in a component of
174
+ your own:
175
+
176
+ | Component | Props type |
177
+ | --- | --- |
178
+ | `TMDataGrid` | `TMDataGridProps<TData>`: the fields of `TMDataGridApi<TData>`, plus `children`, `size`, `className`, `style`, `id` and `data-testid` |
179
+ | `TMDataGrid.Table` | `TMDataGridTableProps<TData>` |
180
+ | `TMDataGrid.Toolbar` | `TMDataGridToolbarProps`: `children`, `withBottomBorder` (default `false`), Mantine `BoxProps` |
181
+ | `TMDataGrid.Search` · `TMDataGridSearch` | `TMDataGridSearchProps` |
182
+ | `TMDataGrid.Footer` | `TMDataGridFooterProps` |
183
+ | `TMDataGrid.Menu` | `TMDataGridMenuProps`: `children`, `icon`, `label`, Mantine `MenuProps` |
184
+ | `TMDataGrid.Menu.Columns` | `TMDataGridMenuColumnsProps`: `searchable` |
185
+ | `TMDataGrid.Menu.Export` · `.ExportSelected` | `TMDataGridMenuExportProps`: per-item `exportOptions` overrides, `columns` (which also takes `"custom"`), `label` |
186
+ | `TMDataGrid.ColumnsPanel` | `TMDataGridColumnsPanelProps`: `searchable`, Mantine `BoxProps` |
187
+ | `TMDataGrid.FilterPanel` | `TMDataGridFilterPanelProps`: `layout`, Mantine `BoxProps` |
188
+ | `TMDataGrid.FilterPills` · `TMDataGridFilterPills` | `TMDataGridFilterPillsProps<TData>` |
189
+ | `TMDataGrid.DraftActions` · `TMDataGridDraftActions` | `TMDataGridDraftActionsProps`: `renderActions` |
190
+
191
+ `renderActions` on `TMDataGrid.DraftActions` receives
192
+ `TMDataGridDraftActionsSlotArgs`, `{ state, actions, Controls }`, typed
193
+ `TMDataGridDraftActionsState`, `TMDataGridDraftActionsActions` and
194
+ `TMDataGridDraftActionsControls`. The fields are in the `editing` skill.
195
+
166
196
  Pass the row type so `onRowClick` stays typed:
167
197
 
168
198
  ```tsx
@@ -247,6 +277,76 @@ The virtualizer needs row height as a number, so it cannot come from CSS alone.
247
277
  `SIZE_ROW_HEIGHT` is the exported source of these values and the stylesheet
248
278
  mirrors them. Set `meta.rowHeight` for a height outside the scale.
249
279
 
280
+ ## Render rows without TMDataGrid.Table
281
+
282
+ To show the rows as something other than a table - cards, a list - keep
283
+ `useTMDataGrid` and `<TMDataGrid>`, and replace `TMDataGrid.Table` with a
284
+ renderer of your own. `TMDataGrid` renders no rows itself, and every other part
285
+ works without the Table, so the toolbar stays. Search, filters, sorting, column
286
+ visibility and row selection write the same table state the grid would.
287
+
288
+ ```tsx
289
+ const grid = useTMDataGrid({
290
+ data,
291
+ columns,
292
+ getRowId: (row) => String(row.id),
293
+ // The popup and the sidebar belong to TMDataGrid.Table.
294
+ filters: { surface: "none" },
295
+ });
296
+
297
+ <TMDataGrid {...grid} style={{ flex: 1, minHeight: 0 }}>
298
+ <TMDataGrid.Toolbar>
299
+ <TMDataGrid.Search />
300
+ <TMDataGrid.SummaryCount />
301
+ <TMDataGrid.Menu>
302
+ <TMDataGrid.Menu.Columns />
303
+ </TMDataGrid.Menu>
304
+ </TMDataGrid.Toolbar>
305
+ <TMDataGrid.FilterPanel layout="stacked" />
306
+ <CardList table={grid.table} features={grid.features} />
307
+ </TMDataGrid>;
308
+ ```
309
+
310
+ Read the rows with `getDisplayedRows(table, features)`: the rows the Table
311
+ would render, in render order - filtered, sorted, the current page when paging
312
+ is active, pinned rows left out. Call it inside a selector with a shallow
313
+ compare:
314
+
315
+ ```tsx
316
+ import { useSelector } from "@tanstack/react-store";
317
+ import { shallow } from "@tanstack/store";
318
+ import { getDisplayedRows } from "@jielga/tmdatagrid";
319
+
320
+ const rows = useSelector(table.store, () => getDisplayedRows(table, features), {
321
+ compare: shallow,
322
+ });
323
+ ```
324
+
325
+ The table identity never changes, so the React Compiler caches a bare
326
+ `getDisplayedRows(table, features)` call and the list stops following filters
327
+ and sorting. The shallow compare re-renders the list only when the rows change.
328
+ Read `row.getVisibleCells()` the same way, inside
329
+ `useSelector(table.store, () => row.getVisibleCells())`, and skip the generated
330
+ columns with `isGeneratedColumn(cell.column.id)` - the checkbox column is among
331
+ the visible cells while row selection is on. Render each value through the
332
+ column's own renderer: `flexRender(cell.column.columnDef.cell, cell.getContext())`.
333
+
334
+ The following belong to `TMDataGrid.Table` and are not available without it:
335
+
336
+ - the header, with click-to-sort, resizing, dragging and the column menus -
337
+ sort from a control of your own with `table.setSorting`
338
+ - the filter popup and sidebar - set `filters: { surface: "none" }` and place
339
+ `TMDataGrid.FilterPanel` yourself
340
+ - row details, row pinning, cell selection and editing in cells
341
+ - `scrollToRow`, which returns `false` while no Table is mounted
342
+
343
+ Virtualize the list yourself, for example with `useVirtualizer` from
344
+ `@tanstack/react-virtual`.
345
+
346
+ Source: `packages/tmdatagrid/docs/card-view.md`,
347
+ `packages/tmdatagrid/docs/anatomy.md` (Which rows it renders), and the demo
348
+ `apps/docs/src/examples/demos/recipes/CardView.tsx`.
349
+
250
350
  ## Helpers
251
351
 
252
352
  | Export | Description |