@jielga/tmdatagrid 2.0.0-beta.8 → 2.0.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 (155) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1323 -796
  3. package/dist/index.js +4719 -3193
  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 +268 -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 +69 -78
  47. package/skills/columns/SKILL.md +90 -34
  48. package/skills/data/SKILL.md +86 -16
  49. package/skills/editing/SKILL.md +83 -50
  50. package/skills/editing/references/common-mistakes.md +77 -69
  51. package/skills/editing/references/editing-api.md +31 -23
  52. package/skills/editing/references/editors-and-validation.md +24 -17
  53. package/skills/filtering/SKILL.md +148 -40
  54. package/skills/getting-started/SKILL.md +17 -15
  55. package/skills/grouping/SKILL.md +31 -16
  56. package/skills/options/SKILL.md +8 -8
  57. package/skills/rows/SKILL.md +22 -18
  58. package/skills/server-side/SKILL.md +170 -17
  59. package/skills/testing/SKILL.md +150 -32
  60. package/skills/testing-components/SKILL.md +230 -0
  61. package/skills/testing-editing/SKILL.md +240 -0
  62. package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
  63. package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
  64. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  65. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +73 -7
  66. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  67. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  68. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +9 -55
  69. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  70. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +23 -63
  71. package/src/components/TMDataGridEntryRows.tsx +354 -0
  72. package/src/components/TMDataGridExportPicker.module.css +77 -0
  73. package/src/components/TMDataGridExportPicker.tsx +234 -0
  74. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  75. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  76. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  77. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  78. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  79. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  80. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +9 -72
  81. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  82. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  83. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  84. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  85. package/src/components/TMDataGridMenu.tsx +357 -0
  86. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +15 -53
  87. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +88 -65
  88. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +579 -165
  89. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  90. package/src/components/TMDataGridToolbar.tsx +181 -0
  91. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  96. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  97. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  98. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  99. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  100. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  101. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  102. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  103. package/src/components/filters/controlLayout.ts +32 -0
  104. package/src/components/filters/filterControlFor.ts +65 -0
  105. package/src/components/generatedColumns.tsx +187 -0
  106. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  107. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  108. package/src/components/useHideableColumns.ts +52 -0
  109. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  110. package/src/{tmdatagrid/core → core}/capabilities.ts +5 -5
  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 +1172 -388
  118. package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
  119. package/src/core/export.ts +704 -0
  120. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  121. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  122. package/src/core/filterSurface.ts +99 -0
  123. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  124. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  125. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  126. package/src/core/pageReset.ts +120 -0
  127. package/src/core/pagination.ts +81 -0
  128. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  129. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  130. package/src/{tmdatagrid/index.ts → index.ts} +70 -36
  131. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +534 -123
  132. package/src/useTMDataGridExport.ts +78 -0
  133. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +0 -298
  134. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  135. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  136. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  137. package/src/tmdatagrid/core/cellExport.ts +0 -320
  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}/cellNavigation.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  155. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -10,17 +10,23 @@ description: >
10
10
  onReachEnd is better for loading more, the header and pinned-lane depth
11
11
  shadows, and the four empty states in precedence order with meta.loading,
12
12
  renderEmptyState, hasActiveFilters, TMDataGrid.LoadingIndicator and
13
- TMDataGrid.SummaryCount. Load when adding a pager, tuning scrolling, scrolling
14
- to a row, or deciding what an empty grid should say.
13
+ TMDataGrid.SummaryCount, and export: TMDataGrid.Menu.Export and
14
+ Menu.ExportSelected, useTMDataGridExport, exportGrid, exportOptions, the
15
+ csvExcel / csv / tsv / json formats, meta.exportValue and meta.enableExport.
16
+ Load when adding a pager, tuning scrolling, scrolling to a row, deciding what
17
+ an empty grid should say, or exporting rows to Excel or CSV.
15
18
  metadata:
16
19
  type: core
17
20
  library: '@jielga/tmdatagrid'
18
- library_version: '2.0.0-beta.8'
21
+ library_version: '2.0.0'
19
22
  sources:
20
- - 'Jielga/TMDataGrid:src/docs/pagination.md'
21
- - 'Jielga/TMDataGrid:src/docs/scrolling.md'
22
- - 'Jielga/TMDataGrid:src/docs/loading-and-empty.md'
23
- - 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGridFooter.tsx'
23
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/pagination.md'
24
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/scrolling.md'
25
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/loading-and-empty.md'
26
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/export.md'
27
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/components/TMDataGridFooter.tsx'
28
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/pagination.ts'
29
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/export.ts'
24
30
  ---
25
31
 
26
32
  # TMDataGrid - Pagination, scrolling and empty states
@@ -77,7 +83,10 @@ pieces, already wired.
77
83
  `Controls.PageSize`, `Controls.Range` and `Controls.Pager` are what the default
78
84
  footer renders, in that order, so a custom layout can keep the parts it wants
79
85
  instead of rebuilding them. They behave exactly as before, including greying out
80
- under a suspended pager.
86
+ under a suspended pager. `Controls.PageNumber` - the "Page 3 of 200" label a
87
+ server-paged grid usually shows in place of a row range - is a fourth control,
88
+ not in the default footer; put it in through the slot rather than writing it by
89
+ hand.
81
90
 
82
91
  `state` carries `pageIndex`, `pageSize`, `pageCount`, `rowCount`,
83
92
  `canPreviousPage`, `canNextPage`, `from`, `to` and `isPagingActive`. `actions`
@@ -178,6 +187,67 @@ server-driven grid refetching with rows still on screen keeps showing them.
178
187
  total, where the total is `meta.totalRowCount` when provided and the pre-filtered
179
188
  count otherwise.
180
189
 
190
+ ## Export
191
+
192
+ Every filtered and sorted row across every page, or the selected rows, as a
193
+ file. The built-in entry points are menu items; a button of your own uses the
194
+ hook.
195
+
196
+ ```tsx
197
+ const grid = useTMDataGrid({
198
+ data,
199
+ columns,
200
+ exportOptions: { format: csvFormat(), fileName: "employees" },
201
+ });
202
+
203
+ <TMDataGrid.Menu>
204
+ <TMDataGrid.Menu.Export />
205
+ <TMDataGrid.Menu.ExportSelected />
206
+ </TMDataGrid.Menu>
207
+ ```
208
+
209
+ ```tsx
210
+ function ExportButton() {
211
+ const { exportAll, exportSelected, selectedCount, canExportSelected } =
212
+ useTMDataGridExport();
213
+ return <Button onClick={() => void exportAll()}>Export</Button>;
214
+ }
215
+ ```
216
+
217
+ What is written: the data columns in render order (never the generated lanes,
218
+ never a column with `meta.enableExport: false`), every row after filtering and
219
+ sorting (a grouped grid writes the records under every group, never the group
220
+ rows), and each cell's **value** rather than what it renders -
221
+ `meta.exportValue: ({ value, row, column }) => unknown` substitutes one.
222
+ `columns` on `exportOptions`, the items and the functions is `"visible"` (the
223
+ default), `"all"` (hidden columns too) or a list of ids; `columns="custom"` on
224
+ a menu item opens a picker instead - every exportable column, the visible ones
225
+ ticked, Export and Cancel - driven by `ui.state.exportPicker` and
226
+ `ui.actions.openExportPicker`. `TMDataGrid.Menu.ExportSelected` counts and
227
+ writes the ticked rows of the current view, in grid order; it renders nothing
228
+ when row selection is off.
229
+
230
+ Formats, each a `TMDataGridExportFormat` from a factory:
231
+
232
+ | Factory | Writes |
233
+ | --- | --- |
234
+ | `csvExcelFormat()` (default) | BOM, `sep=;` line, CRLF, `;` fields, `,` decimal - opens straight into columns in Excel. `separator`, `decimalComma` for another locale. |
235
+ | `csvFormat()` | RFC 4180: `,` fields, `.` decimal, BOM, no `sep=` line - for Google Sheets, Numbers and tooling. |
236
+ | `tsvFormat()` | Tab-separated, the clipboard shape as a file. |
237
+ | `jsonFormat()` | One object per row keyed by column label, numbers as numbers, dates as ISO strings. |
238
+ | `xlsxFormat()` | Excel workbook, from the separate `@jielga/tmdatagrid-xlsx` package. |
239
+
240
+ The text formats guard against formula injection by default: a value starting
241
+ with `=`, `+`, `-` or `@` that is not a number is prefixed with `'`.
242
+ `escapeFormulas: false` on the format turns that off.
243
+
244
+ `exportGrid({ table, rows: "all" | "selected" | rows, options })` is the same
245
+ export for code outside a component; `buildExportData` is the step before the
246
+ file. A format of your own is `{ id, extension, mimeType, write(data, { includeHeaders }) }`
247
+ returning a string, a `Blob`, or a promise of either.
248
+
249
+ Source: `packages/tmdatagrid/docs/export.md`.
250
+
181
251
  ## Common mistakes
182
252
 
183
253
  ### CRITICAL Turning pagination on to make a large grid fast
@@ -186,7 +256,7 @@ Virtualization is already unconditional, so paging a 200 000-row grid changes
186
256
  nothing about rendering cost. It only changes how users navigate. Enable it when
187
257
  they should move page by page, not for performance.
188
258
 
189
- Source: `src/docs/pagination.md`, `src/docs/scrolling.md`.
259
+ Source: `packages/tmdatagrid/docs/pagination.md`, `packages/tmdatagrid/docs/scrolling.md`.
190
260
 
191
261
  ### CRITICAL A variable row height
192
262
 
@@ -207,7 +277,7 @@ Correct:
207
277
  useTMDataGrid({ data, columns, meta: { rowHeight: 64 } });
208
278
  ```
209
279
 
210
- Source: `src/docs/scrolling.md` (Row height).
280
+ Source: `packages/tmdatagrid/docs/scrolling.md` (Row height).
211
281
 
212
282
  ### HIGH `scrollIntoView` on a row that is not mounted
213
283
 
@@ -223,7 +293,7 @@ grid.scrollToRow({ rowId: "4000", align: "center" });
223
293
  `scrollToRow` returns `false` when the row is not in the current view (filtered
224
294
  out, on another page, or an id matching no row) and nothing scrolled.
225
295
 
226
- Source: `src/docs/scrolling.md` (Scrolling to a row).
296
+ Source: `packages/tmdatagrid/docs/scrolling.md` (Scrolling to a row).
227
297
 
228
298
  ### HIGH Loading more rows from `onScrollToBottom`
229
299
 
@@ -231,7 +301,7 @@ It fires at the very bottom, so the user waits at the end of the list for the
231
301
  fetch. `onReachEnd` fires a number of rows earlier and latches per row count, so
232
302
  a pending fetch is not requested twice.
233
303
 
234
- Source: `src/docs/scrolling.md` (Edge callbacks).
304
+ Source: `packages/tmdatagrid/docs/scrolling.md` (Edge callbacks).
235
305
 
236
306
  ### HIGH `SummaryCount` reporting the page under manual pagination
237
307
 
@@ -250,7 +320,7 @@ useTMDataGrid({
250
320
  });
251
321
  ```
252
322
 
253
- Source: `src/docs/loading-and-empty.md` (Counting what is there).
323
+ Source: `packages/tmdatagrid/docs/loading-and-empty.md` (Counting what is there).
254
324
 
255
325
  ### MEDIUM Rendering an empty message while data is loading
256
326
 
@@ -264,7 +334,7 @@ Correct:
264
334
  useTMDataGrid({ data, columns, meta: { loading: isFetching } });
265
335
  ```
266
336
 
267
- Source: `src/docs/loading-and-empty.md` (What wins).
337
+ Source: `packages/tmdatagrid/docs/loading-and-empty.md` (What wins).
268
338
 
269
339
  ### MEDIUM Trusting the pager while grouped
270
340
 
@@ -272,7 +342,7 @@ Source: `src/docs/loading-and-empty.md` (What wins).
272
342
  grid is rendering the whole tree. A custom pager must read
273
343
  `isPagingActive(table, features)` rather than the page count.
274
344
 
275
- Source: `src/docs/pagination.md` (Grouping suspends it).
345
+ Source: `packages/tmdatagrid/docs/pagination.md` (Grouping suspends it).
276
346
 
277
347
  ## Reference
278
348
 
@@ -283,7 +353,7 @@ Source: `src/docs/pagination.md` (Grouping suspends it).
283
353
  | `rowCount` | Table option | `number` | – | The true total, required under `manualPagination`. |
284
354
  | `initialState.pagination` | Table option | `{ pageIndex, pageSize }` | `{ 0, 25 }` | Where paging starts. A `data` slice, so it persists. |
285
355
  | `onPaginationChange` | Table option | `OnChangeFn` | – | Controls the pagination state. |
286
- | `TMDataGrid.Footer` | Component | `pageSizeOptions`, `pagination` | `[10, 25, 50, 100]` | The footer bar. Renders nothing when paging is off. |
356
+ | `TMDataGrid.Footer` | Component | `pageSizeOptions`, `renderPagination`, Mantine `BoxProps` | `[10, 25, 50, 100]` | The footer bar. Renders nothing when paging is off. Style props set on the bar. |
287
357
  | `Footer` `renderPagination` | Slot | `({ state, actions, Controls }) => ReactNode` | Built-in pager | Replaces the pager, and hands over its pieces. |
288
358
  | `getTMDataGridPaginationApi` | Export | `(table) => { state, actions }` | – | The pager API, outside the Footer. |
289
359
  | `TMDataGridPaginationState` · `TMDataGridPaginationActions` · `TMDataGridPaginationControls` | Exports | types | – | The three parts of the slot argument. |
@@ -18,13 +18,15 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.0.0-beta.8'
21
+ library_version: '2.0.0'
22
22
  sources:
23
- - 'Jielga/TMDataGrid:src/docs/editing.md'
24
- - 'Jielga/TMDataGrid:src/docs/query-builder.md'
25
- - 'Jielga/TMDataGrid:src/docs/editors.md'
26
- - 'Jielga/TMDataGrid:src/tmdatagrid/core/editEngine.ts'
27
- - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
23
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/editing.md'
24
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/draft-store.md'
25
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/adding-rows.md'
26
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/query-builder.md'
27
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/docs/editors.md'
28
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/editEngine.ts'
29
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/useTMDataGrid.tsx'
28
30
  ---
29
31
 
30
32
  # TMDataGrid - Editing
@@ -34,9 +36,10 @@ happen. Three facts decide every wiring question below:
34
36
 
35
37
  - **The grid never mutates `data`.** `editing.onCommit` applies the change
36
38
  wherever the data lives, and the updated rows arrive back through `data`.
37
- - **One row, one form.** Each editing row gets its own TanStack Form, keyed by
38
- row id and living outside the DOM, so a draft survives scrolling, sorting and
39
- filtering.
39
+ - **One row, one form, while it is open.** Each row being edited gets its own
40
+ TanStack Form, keyed by row id and living outside the DOM, so a draft
41
+ survives scrolling, sorting and filtering. A committed row holds values, not
42
+ a form.
40
43
  - **`getRowId` is required** once `editing` is set, and it must be the record's
41
44
  own identity. Drafts are keyed by it.
42
45
 
@@ -82,16 +85,18 @@ They are independent, and every pair is legal.
82
85
  | Mode | Commits on | Cancels on | Controls |
83
86
  | --- | --- | --- | --- |
84
87
  | `"cell"` | Enter, Tab, leaving the cell | Escape | none |
85
- | `"cellConfirm"` | ✓ or Enter; Tab and leaving keep the draft | ✕ or Escape | ✓ / ✕ beside the input |
88
+ | `"cellConfirm"` | ✓ or Enter; Tab walks input, ✓, ✕ and then leaves, keeping the draft | ✕ or Escape | ✓ / ✕ beside the input |
86
89
  | `"row"` | Save in the edit lane, or Enter | Cancel, or Escape | generated edit lane |
87
90
 
91
+ Leaving a cell commits it only once the value passes: a refused commit keeps the editor open, invalid, with the message in its tooltip, until the value is fixed or Escape drops it.
92
+
88
93
  | `editing.draft` | Where a commit goes |
89
94
  | --- | --- |
90
95
  | `false` (default) | Straight out: `onCommit`, `onRowAdd`, `onRowDelete` |
91
96
  | `true` | Into the grid's draft store, until `edit.saveDrafts()` sends the lot |
92
97
 
93
98
  Which to pick: `"cell"` for spreadsheet feel; `"cellConfirm"` when a stray click must not fire a request; `"row"` when the row is the unit of the save or a rule spans two columns.
94
- Add `draft: true` for many edits sent as one transaction - `{ mode: "row", draft: true }` parks a whole row from the lane's ✓, `{ mode: "cell", draft: true }` parks a row as the caret leaves it.
99
+ Add `draft: true` for many edits sent as one transaction - `{ mode: "row", draft: true }` commits a whole row from the lane's ✓, `{ mode: "cell", draft: true }` commits a row as the caret leaves it.
95
100
 
96
101
  An editor opens on double-click, or with the cell cursor on the cell: Enter, F2,
97
102
  or typing, where the first character replaces the value. Delete or Backspace
@@ -108,9 +113,12 @@ commit. Rows accumulate: opening a second row leaves the first open, and each
108
113
  row's ✓ and ✕ act on that row alone.
109
114
 
110
115
  Under `draft: true` nothing reaches a callback until `saveDrafts`.
111
- The mode's own commit gesture parks the row instead of sending it, Escape drops that one draft, and parked rows accumulate, surviving filters, sorts and scrolling.
112
- `edit.commit(rowId)` parks too, so there is no per-row escape hatch to the consumer.
113
- A parked row is displayed: the cell renders the draft value through the column's own `cell` renderer, with the blue corner marking it dirty and `data-dirty` on the row.
116
+ The mode's own commit gesture puts the row in the draft store instead of sending it, Escape drops that one draft, and committed rows accumulate.
117
+ `edit.commit(rowId)` goes to the draft store too, so there is no per-row escape hatch to the consumer.
118
+ A committed row is displayed: the cell renders the draft value through the column's own `cell` renderer, with the blue corner marking it dirty and `data-dirty` on the row.
119
+ It is a row like any other to the table: sorting, filtering, quick search, grouping, aggregates, export, selection, the row counts, `edit.getRows()` and `editing.tableValidators` all read its draft values, and the row callbacks receive it with the draft as `row.original`.
120
+ A committed row that stops matching a filter leaves the view, and the Save bar still counts it.
121
+ `data` itself is never modified, and only top-level rows are overlaid - `getSubRows` children keep their `data` values.
114
122
  An entry row is row-shaped in every mode - every editable cell open at once, the browser's Tab, and the lane's ✓ to enter it.
115
123
 
116
124
  The edit lane is the change indicator and the per-row undo: an edited row shows
@@ -128,15 +136,27 @@ a list of row ids. Rows failing validation stay open either way.
128
136
 
129
137
  `onSaveDrafts` decides how much of the store is cleared: returning nothing
130
138
  saves everything, throwing saves nothing, and returning
131
- `{ updated, created, deleted }` saves everything except the ids reported
132
- `false`. Each key takes `false` for the whole bucket or a map of id to result;
133
- an unnamed id saved. A kept row stays committed, so the next `saveDrafts()`
134
- retries it, and `saveDrafts()` resolves `false` when anything was kept.
139
+ `{ updated, created, deleted }` (a `TMDataGridSaveDraftsResponse`) saves
140
+ everything except the ids reported `false`. Each key takes `false` for the
141
+ whole bucket or a map of id to result; an unnamed id saved. A kept row stays
142
+ committed, so the next `saveDrafts()` retries it.
143
+
144
+ `saveDrafts()` resolves a `TMDataGridSaveDraftsResult`, `{ ok, saved, kept, reopened }`.
145
+ Every id the save took from the draft store is in exactly one list - row ids for edits and deletions, temp ids for new rows, all kinds mixed:
146
+
147
+ - `saved` - left the draft store; the consumer accepted it
148
+ - `kept` - still in the draft store, still committed, retried by the next save: an id `onSaveDrafts` returned as failed, every id it was sent when it threw, or, without `onSaveDrafts`, a deletion whose `onRowDelete` threw
149
+ - `reopened` - open again with an error: a table rule rejected it, or on the per-row path its `onCommit` / `onRowAdd` threw
150
+ - `ok` - `true` when `kept` and `reopened` are both empty
151
+
152
+ On the per-row path a deletion always leaves the store and is reported in `saved`.
153
+ An empty store resolves `{ ok: true, saved: [], kept: [], reopened: [] }`.
154
+ Rows still open are in no list.
135
155
 
136
156
  Rows carry `data-dirty` (values typed in), `data-draft` (committed, waiting for
137
- Save), `data-deleted` and, on entry rows, `data-new`. The grid paints none of
138
- them; use `rowStyle` / `rowClassName` or the attributes to highlight what is
139
- pending.
157
+ Save), `data-deleted` and `data-new` - a committed new row in the body, or an
158
+ entry row in the block. The grid paints none of them; use `rowStyle` /
159
+ `rowClassName` or the attributes to highlight what is pending.
140
160
 
141
161
  ## What a commit receives
142
162
 
@@ -213,16 +233,22 @@ rowValidators: {
213
233
  ```
214
234
 
215
235
  Pathed issues land on the matching cells, pathless ones on the row, and cell
216
- corners mark both: blue for a dirty draft, red for a validation error.
236
+ corners mark both: blue for a dirty draft, red for a validation error. A
237
+ field's message shows in a tooltip on the open editor, which the host renders
238
+ for a custom editor as much as a built-in one; the plain-function form of a
239
+ validator types `value` as `never`, so annotate the parameter -
240
+ `({ value }: { value: unknown })`.
217
241
 
218
242
  `editing.tableValidators` carries the rules that need the other rows - no
219
243
  duplicate keys, no overlapping ranges, shares summing to a total. Its
220
244
  `onSubmit` / `onSubmitAsync` receive `{ value, rowId, isNew, rows }`, where
221
245
  `rows` is the collection as it would stand if the commit landed: every draft
222
- overlaid, entry rows appended, deletion-marked rows removed. Same result
246
+ overlaid, committed new rows among them, the entry rows the table does not hold
247
+ appended, deletion-marked rows removed. Each row appears once. Same result
223
248
  vocabulary as `rowValidators`; errors land on the committing row. The rules
224
- re-run per parked row during `saveDrafts`, so a draft a later edit
225
- invalidated blocks the save.
249
+ re-run per committed row during `saveDrafts`, the only validation that runs
250
+ there: a committed row a later edit invalidated is reopened with its errors
251
+ and the save reports it in `reopened`.
226
252
 
227
253
  ```tsx
228
254
  tableValidators: {
@@ -251,16 +277,18 @@ from `editing.newRowDefaults`. `edit.addRow(values)` overrides that seed key by
251
277
  key, so `addRow()` opens the `newRowDefaults` row and `addRow(values)` opens it
252
278
  with those fields filled in - pass a whole row to duplicate it. Enter, or the
253
279
  lane's ✓, commits the add through `editing.onRowAdd`; under `draft: true` it
254
- parks the row in the draft store, validated, and
255
- `saveDrafts` reports it in `added`. Escape, or ✕, discards the entry. An entry
280
+ commits the row into the draft store, validated, and
281
+ `saveDrafts` reports it in `created`. Escape, or ✕, discards the entry. An entry
256
282
  row never OK'd is not part of a save - it stays open.
257
283
 
258
- Under `draft: true` an entered row renders as a value row with no inputs, marked
259
- `data-new` and `data-committed` and tinted with `--dg-row-new-bg`. By default it
260
- joins the scrolling flow above the body rows; `editing.newRowsSticky: true`
261
- keeps committed rows pinned in the entry block until the save. Double-click, or
262
- the lane's pencil, reopens it; ✕ removes it. To limit how many entry rows are
263
- open at once, gate the Add button on
284
+ Under `draft: true` a committed entry row leaves the entry block and becomes a
285
+ body row with no inputs, marked `data-new` and `data-draft`, tinted with
286
+ `--dg-row-new-bg`, and sorted, filtered and counted with the rest on the values
287
+ it was entered with. `editing.newRowsSticky: true` keeps committed rows in the
288
+ entry block until the save instead, out of the body's sort and out of the row
289
+ count. Double-click, or the lane's pencil, reopens it back into the entry block;
290
+ ✕ removes it. To limit how many entry rows are open at once, gate the Add
291
+ button on
264
292
  `useSelector(grid.edit.store, (s) => s.newRows.some((n) => !n.committed))`.
265
293
 
266
294
  ```tsx
@@ -291,13 +319,14 @@ const grid = useTMDataGrid({
291
319
  `edit.addRows(rows, options?)` opens a batch in one write. `{ commit: true }`
292
320
  submits each row as it lands - the import case: valid rows are committed,
293
321
  invalid ones stay open in the entry block with their errors, and the result
294
- (`{ committed, open }`) says which went which way. Column rules are enforced
295
- even though the rows never had an editor on screen, because the engine runs
296
- `meta.edit.validate` itself at commit.
322
+ (`{ ok, committed, open }`) says which went which way; `ok` is `true` when
323
+ `open` is empty. Column rules are enforced even though the rows never had an
324
+ editor on screen, because the engine runs `meta.edit.validate` itself at
325
+ commit.
297
326
 
298
327
  ```tsx
299
- const { committed, open } = await grid.edit.addRows(parsed, { commit: true });
300
- if (open.length > 0) notify(`${open.length} rows need attention`);
328
+ const { ok, open } = await grid.edit.addRows(parsed, { commit: true });
329
+ if (!ok) notify(`${open.length} rows need attention`);
301
330
  await grid.edit.saveDrafts();
302
331
  ```
303
332
 
@@ -312,8 +341,8 @@ it toggles a mark instead: the row renders struck through and inert
312
341
 
313
342
  The generated edit lane (`EDIT_COLUMN_ID`, pinned right) appears when `editing.mode` is `"row"`, when `editing.draft` is on, or when `editing.onRowDelete` is set.
314
343
  Nothing else adds it.
315
- It holds one thing per axis: the mode's own controls while a row is open - Save and Cancel under `"row"` - and, once a row is parked, the row-state marker with Revert or Restore.
316
- A parked row never offers a save.
344
+ It holds one thing per axis: the mode's own controls while a row is open - Save and Cancel under `"row"` - and, once a row is committed, the row-state marker with Revert or Restore.
345
+ A committed row never offers a save.
317
346
  The trash shows when the deletion has somewhere to report to: `onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is.
318
347
  If validation blocks a row, its marker - or the open row's ✓ - turns red with the message in the tooltip, which is where a pathless `rowValidators` message shows.
319
348
  Every control carries a tooltip from the labels.
@@ -347,10 +376,10 @@ while editing is off, and works under any mode, not only draft.
347
376
  ```
348
377
 
349
378
  `state` is
350
- `{ draftCount, openCount, openRowIds, pendingCount, isSubmitting }` -
351
- `pendingCount` deprecated, reading as `draftCount + openCount`. `actions` is
379
+ `{ draftCount, openCount, openRowIds, isSubmitting, isSaving }`. `actions` is
352
380
  `{ save, commitAll, discard, scrollToRow, scrollToFirstOpenRow }`, and
353
381
  `Controls` is `{ Save, Discard, OpenRowsNote }`.
382
+ `actions.save` and `actions.commitAll` resolve what `edit.saveDrafts()` and `edit.commitAll()` resolve.
354
383
 
355
384
  The grid is always virtualized, so an open row far down the list has no element
356
385
  to scroll to. `actions.scrollToFirstOpenRow(align?)` moves the virtualizer to
@@ -370,16 +399,20 @@ row it reaches.
370
399
  `setCellValue` / `setRowValues` / `clearCell`, `addRow` / `addRows` /
371
400
  `deleteRow`, `getForm`, and `store` for `useSelector` (an example is under
372
401
  [Submitting an outer form](#high-submitting-an-outer-form-while-the-grid-holds-a-draft)).
373
- `edit.store` publishes each open or parked row's drafted values as
374
- `rows[rowId].values`, which is what a computed cell or a cross-row check reads
375
- - `useTMDataGridContext()` reaches the engine from inside a cell renderer.
402
+ `commitAll()` resolves a `TMDataGridCommitAllResult`, `{ ok, committed, open }`: every row open at the call is in exactly one list, and `ok` is `true` when `open` is empty.
403
+ `commit(rowId)` alone resolves a plain boolean.
404
+ `edit.store` publishes each open or committed row's drafted values as
405
+ `rows[rowId].values`, which is what a cross-row check reads. A `cell`
406
+ renderer needs no lookup: its `row.original` is already the row as shown, and
407
+ `getRowValues(rowId)` is the same row for a handler with no cell context.
376
408
  Every member with its signature, the gates, `isColumnEditable`, `deactivate`,
377
409
  and the `edit.store` shape are in
378
410
  [references/editing-api.md](references/editing-api.md#the-edit-engine).
379
411
 
380
- `getForm` exposes the row's form: render it in a drawer and it shares values,
381
- dirty state and errors with the inline cells, because it is the same
382
- `FormApi`.
412
+ `getForm` exposes an open row's form: render it in a drawer and it shares
413
+ values, dirty state and errors with the inline cells, because it is the same
414
+ `FormApi`. It is `undefined` for a committed row, which holds values and no
415
+ form; `begin` reopens the row with a form seeded from them.
383
416
 
384
417
  `edit.setCellValue(rowId, columnId, value)` writes one cell and commits its row with no editor open, which is what a toolbar action or a bulk fill wants.
385
418
  The row need not be mounted, so a selected row inside a collapsed group takes the write like any other.
@@ -391,7 +424,7 @@ for (const row of grid.table.getSelectedRowModel().rows) {
391
424
  }
392
425
  ```
393
426
 
394
- Under `draft: true` each row parks in the draft store like any hand-made edit, with the same change markers and the same per-row revert, and the basket leaves through `saveDrafts`.
427
+ Under `draft: true` each row is committed into the draft store like any hand-made edit, with the same change markers and the same per-row revert, and the basket leaves through `saveDrafts`.
395
428
  `value` is the stored value: no editor runs, so `meta.edit.mapValue` does not run either, while `meta.edit.validate` does.
396
429
  Both resolve `false` when the cell takes no edit - no such row or column, `editing.columns` excludes it, `meta.edit.enabled` is off, or the row is not editable - and when validation refuses the value, which leaves the row open carrying its errors.
397
430
 
@@ -422,7 +455,6 @@ are in [references/common-mistakes.md](references/common-mistakes.md).
422
455
  | CRITICAL | Expecting the grid to write into `data` - without `editing.onCommit` the cell reverts |
423
456
  | CRITICAL | `getRowId` built from the row index - drafts follow the index, not the record |
424
457
  | HIGH | A cell editor defined inside the component - a new type per render unmounts the editor |
425
- | HIGH | A custom editor that binds no error text - a refused commit shows no message; bind `field.state.meta.errors` |
426
458
  | HIGH | A cross-field rule under `mode: "cell"` - `rowValidators` needs `"row"` |
427
459
  | HIGH | An `accessorFn` column with no `meta.edit.field` - it maps to nothing and stays read-only |
428
460
  | HIGH | Swallowing the error in `editing.onCommit` - a resolved catch drops the draft |
@@ -431,6 +463,7 @@ are in [references/common-mistakes.md](references/common-mistakes.md).
431
463
  | MEDIUM | A computed column frozen while a row is edited - `accessorFn` reads `data`; read the draft from `edit.store`'s `rows[rowId].values` |
432
464
  | MEDIUM | Reading a commit's result as the saved value - it is a `boolean` about the form |
433
465
  | MEDIUM | Expecting `editing.onRowDelete` to fire under draft - the mark waits for `saveDrafts` |
466
+ | MEDIUM | A custom editor that binds no invalid state - the host shows the message in a tooltip, but the control keeps its normal border; bind a boolean to `error` |
434
467
 
435
468
  ## References
436
469