@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
@@ -18,13 +18,16 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.0.0-beta.9'
21
+ library_version: '2.0.1'
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/docs/portfolio-rebalancer.md'
29
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/core/editEngine.ts'
30
+ - 'Jielga/TMDataGrid:packages/tmdatagrid/src/useTMDataGrid.tsx'
28
31
  ---
29
32
 
30
33
  # TMDataGrid - Editing
@@ -34,9 +37,10 @@ happen. Three facts decide every wiring question below:
34
37
 
35
38
  - **The grid never mutates `data`.** `editing.onCommit` applies the change
36
39
  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.
40
+ - **One row, one form, while it is open.** Each row being edited gets its own
41
+ TanStack Form, keyed by row id and living outside the DOM, so a draft
42
+ survives scrolling, sorting and filtering. A committed row holds values, not
43
+ a form.
40
44
  - **`getRowId` is required** once `editing` is set, and it must be the record's
41
45
  own identity. Drafts are keyed by it.
42
46
 
@@ -85,13 +89,15 @@ They are independent, and every pair is legal.
85
89
  | `"cellConfirm"` | ✓ or Enter; Tab walks input, ✓, ✕ and then leaves, keeping the draft | ✕ or Escape | ✓ / ✕ beside the input |
86
90
  | `"row"` | Save in the edit lane, or Enter | Cancel, or Escape | generated edit lane |
87
91
 
92
+ 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.
93
+
88
94
  | `editing.draft` | Where a commit goes |
89
95
  | --- | --- |
90
96
  | `false` (default) | Straight out: `onCommit`, `onRowAdd`, `onRowDelete` |
91
97
  | `true` | Into the grid's draft store, until `edit.saveDrafts()` sends the lot |
92
98
 
93
99
  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.
100
+ 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
101
 
96
102
  An editor opens on double-click, or with the cell cursor on the cell: Enter, F2,
97
103
  or typing, where the first character replaces the value. Delete or Backspace
@@ -108,11 +114,11 @@ commit. Rows accumulate: opening a second row leaves the first open, and each
108
114
  row's ✓ and ✕ act on that row alone.
109
115
 
110
116
  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.
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.
117
+ 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.
118
+ `edit.commit(rowId)` goes to the draft store too, so there is no per-row escape hatch to the consumer.
119
+ 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.
114
120
  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`.
115
- A parked row that stops matching a filter leaves the view, and the Save bar still counts it.
121
+ A committed row that stops matching a filter leaves the view, and the Save bar still counts it.
116
122
  `data` itself is never modified, and only top-level rows are overlaid - `getSubRows` children keep their `data` values.
117
123
  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.
118
124
 
@@ -131,10 +137,22 @@ a list of row ids. Rows failing validation stay open either way.
131
137
 
132
138
  `onSaveDrafts` decides how much of the store is cleared: returning nothing
133
139
  saves everything, throwing saves nothing, and returning
134
- `{ updated, created, deleted }` saves everything except the ids reported
135
- `false`. Each key takes `false` for the whole bucket or a map of id to result;
136
- an unnamed id saved. A kept row stays committed, so the next `saveDrafts()`
137
- retries it, and `saveDrafts()` resolves `false` when anything was kept.
140
+ `{ updated, created, deleted }` (a `TMDataGridSaveDraftsResponse`) saves
141
+ everything except the ids reported `false`. Each key takes `false` for the
142
+ whole bucket or a map of id to result; an unnamed id saved. A kept row stays
143
+ committed, so the next `saveDrafts()` retries it.
144
+
145
+ `saveDrafts()` resolves a `TMDataGridSaveDraftsResult`, `{ ok, saved, kept, reopened }`.
146
+ 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:
147
+
148
+ - `saved` - left the draft store; the consumer accepted it
149
+ - `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
150
+ - `reopened` - open again with an error: a table rule rejected it, or on the per-row path its `onCommit` / `onRowAdd` threw
151
+ - `ok` - `true` when `kept` and `reopened` are both empty
152
+
153
+ On the per-row path a deletion always leaves the store and is reported in `saved`.
154
+ An empty store resolves `{ ok: true, saved: [], kept: [], reopened: [] }`.
155
+ Rows still open are in no list.
138
156
 
139
157
  Rows carry `data-dirty` (values typed in), `data-draft` (committed, waiting for
140
158
  Save), `data-deleted` and `data-new` - a committed new row in the body, or an
@@ -216,7 +234,11 @@ rowValidators: {
216
234
  ```
217
235
 
218
236
  Pathed issues land on the matching cells, pathless ones on the row, and cell
219
- corners mark both: blue for a dirty draft, red for a validation error.
237
+ corners mark both: blue for a dirty draft, red for a validation error. A
238
+ field's message shows in a tooltip on the open editor, which the host renders
239
+ for a custom editor as much as a built-in one; the plain-function form of a
240
+ validator types `value` as `never`, so annotate the parameter -
241
+ `({ value }: { value: unknown })`.
220
242
 
221
243
  `editing.tableValidators` carries the rules that need the other rows - no
222
244
  duplicate keys, no overlapping ranges, shares summing to a total. Its
@@ -225,8 +247,9 @@ duplicate keys, no overlapping ranges, shares summing to a total. Its
225
247
  overlaid, committed new rows among them, the entry rows the table does not hold
226
248
  appended, deletion-marked rows removed. Each row appears once. Same result
227
249
  vocabulary as `rowValidators`; errors land on the committing row. The rules
228
- re-run per parked row during `saveDrafts`, so a draft a later edit
229
- invalidated blocks the save.
250
+ re-run per committed row during `saveDrafts`, the only validation that runs
251
+ there: a committed row a later edit invalidated is reopened with its errors
252
+ and the save reports it in `reopened`.
230
253
 
231
254
  ```tsx
232
255
  tableValidators: {
@@ -237,6 +260,11 @@ tableValidators: {
237
260
  }
238
261
  ```
239
262
 
263
+ Columns derived from the other rows (a weight as a share of the total) plus a
264
+ table-wide rule (targets may not total more than 100%) are worked through in
265
+ [references/editors-and-validation.md](references/editors-and-validation.md),
266
+ from `packages/tmdatagrid/docs/portfolio-rebalancer.md`.
267
+
240
268
  `meta.edit.mapValue` rewrites a value instead of rejecting it: uppercase a code,
241
269
  strip spaces from an IBAN, clamp a number. It runs on every write an editor
242
270
  makes, so a text input maps per keystroke, and what it returns is what the
@@ -255,8 +283,8 @@ from `editing.newRowDefaults`. `edit.addRow(values)` overrides that seed key by
255
283
  key, so `addRow()` opens the `newRowDefaults` row and `addRow(values)` opens it
256
284
  with those fields filled in - pass a whole row to duplicate it. Enter, or the
257
285
  lane's ✓, commits the add through `editing.onRowAdd`; under `draft: true` it
258
- parks the row in the draft store, validated, and
259
- `saveDrafts` reports it in `added`. Escape, or ✕, discards the entry. An entry
286
+ commits the row into the draft store, validated, and
287
+ `saveDrafts` reports it in `created`. Escape, or ✕, discards the entry. An entry
260
288
  row never OK'd is not part of a save - it stays open.
261
289
 
262
290
  Under `draft: true` a committed entry row leaves the entry block and becomes a
@@ -297,13 +325,14 @@ const grid = useTMDataGrid({
297
325
  `edit.addRows(rows, options?)` opens a batch in one write. `{ commit: true }`
298
326
  submits each row as it lands - the import case: valid rows are committed,
299
327
  invalid ones stay open in the entry block with their errors, and the result
300
- (`{ committed, open }`) says which went which way. Column rules are enforced
301
- even though the rows never had an editor on screen, because the engine runs
302
- `meta.edit.validate` itself at commit.
328
+ (`{ ok, committed, open }`) says which went which way; `ok` is `true` when
329
+ `open` is empty. Column rules are enforced even though the rows never had an
330
+ editor on screen, because the engine runs `meta.edit.validate` itself at
331
+ commit.
303
332
 
304
333
  ```tsx
305
- const { committed, open } = await grid.edit.addRows(parsed, { commit: true });
306
- if (open.length > 0) notify(`${open.length} rows need attention`);
334
+ const { ok, open } = await grid.edit.addRows(parsed, { commit: true });
335
+ if (!ok) notify(`${open.length} rows need attention`);
307
336
  await grid.edit.saveDrafts();
308
337
  ```
309
338
 
@@ -318,8 +347,8 @@ it toggles a mark instead: the row renders struck through and inert
318
347
 
319
348
  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.
320
349
  Nothing else adds it.
321
- 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.
322
- A parked row never offers a save.
350
+ 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.
351
+ A committed row never offers a save.
323
352
  The trash shows when the deletion has somewhere to report to: `onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is.
324
353
  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.
325
354
  Every control carries a tooltip from the labels.
@@ -353,10 +382,10 @@ while editing is off, and works under any mode, not only draft.
353
382
  ```
354
383
 
355
384
  `state` is
356
- `{ draftCount, openCount, openRowIds, pendingCount, isSubmitting }` -
357
- `pendingCount` deprecated, reading as `draftCount + openCount`. `actions` is
385
+ `{ draftCount, openCount, openRowIds, isSubmitting, isSaving }`. `actions` is
358
386
  `{ save, commitAll, discard, scrollToRow, scrollToFirstOpenRow }`, and
359
387
  `Controls` is `{ Save, Discard, OpenRowsNote }`.
388
+ `actions.save` and `actions.commitAll` resolve what `edit.saveDrafts()` and `edit.commitAll()` resolve.
360
389
 
361
390
  The grid is always virtualized, so an open row far down the list has no element
362
391
  to scroll to. `actions.scrollToFirstOpenRow(align?)` moves the virtualizer to
@@ -376,16 +405,20 @@ row it reaches.
376
405
  `setCellValue` / `setRowValues` / `clearCell`, `addRow` / `addRows` /
377
406
  `deleteRow`, `getForm`, and `store` for `useSelector` (an example is under
378
407
  [Submitting an outer form](#high-submitting-an-outer-form-while-the-grid-holds-a-draft)).
379
- `edit.store` publishes each open or parked row's drafted values as
380
- `rows[rowId].values`, which is what a computed cell or a cross-row check reads
381
- - `useTMDataGridContext()` reaches the engine from inside a cell renderer.
408
+ `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.
409
+ `commit(rowId)` alone resolves a plain boolean.
410
+ `edit.store` publishes each open or committed row's drafted values as
411
+ `rows[rowId].values`, which is what a cross-row check reads. A `cell`
412
+ renderer needs no lookup: its `row.original` is already the row as shown, and
413
+ `getRowValues(rowId)` is the same row for a handler with no cell context.
382
414
  Every member with its signature, the gates, `isColumnEditable`, `deactivate`,
383
415
  and the `edit.store` shape are in
384
416
  [references/editing-api.md](references/editing-api.md#the-edit-engine).
385
417
 
386
- `getForm` exposes the row's form: render it in a drawer and it shares values,
387
- dirty state and errors with the inline cells, because it is the same
388
- `FormApi`.
418
+ `getForm` exposes an open row's form: render it in a drawer and it shares
419
+ values, dirty state and errors with the inline cells, because it is the same
420
+ `FormApi`. It is `undefined` for a committed row, which holds values and no
421
+ form; `begin` reopens the row with a form seeded from them.
389
422
 
390
423
  `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.
391
424
  The row need not be mounted, so a selected row inside a collapsed group takes the write like any other.
@@ -397,7 +430,7 @@ for (const row of grid.table.getSelectedRowModel().rows) {
397
430
  }
398
431
  ```
399
432
 
400
- 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`.
433
+ 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`.
401
434
  `value` is the stored value: no editor runs, so `meta.edit.mapValue` does not run either, while `meta.edit.validate` does.
402
435
  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.
403
436
 
@@ -428,7 +461,6 @@ are in [references/common-mistakes.md](references/common-mistakes.md).
428
461
  | CRITICAL | Expecting the grid to write into `data` - without `editing.onCommit` the cell reverts |
429
462
  | CRITICAL | `getRowId` built from the row index - drafts follow the index, not the record |
430
463
  | HIGH | A cell editor defined inside the component - a new type per render unmounts the editor |
431
- | HIGH | A custom editor that binds no error text - a refused commit shows no message; bind `field.state.meta.errors` |
432
464
  | HIGH | A cross-field rule under `mode: "cell"` - `rowValidators` needs `"row"` |
433
465
  | HIGH | An `accessorFn` column with no `meta.edit.field` - it maps to nothing and stays read-only |
434
466
  | HIGH | Swallowing the error in `editing.onCommit` - a resolved catch drops the draft |
@@ -437,12 +469,14 @@ are in [references/common-mistakes.md](references/common-mistakes.md).
437
469
  | MEDIUM | A computed column frozen while a row is edited - `accessorFn` reads `data`; read the draft from `edit.store`'s `rows[rowId].values` |
438
470
  | MEDIUM | Reading a commit's result as the saved value - it is a `boolean` about the form |
439
471
  | MEDIUM | Expecting `editing.onRowDelete` to fire under draft - the mark waits for `saveDrafts` |
472
+ | 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` |
440
473
 
441
474
  ## References
442
475
 
443
476
  - [Editors and validation](references/editors-and-validation.md) - the editor
444
- API, wrapping a built-in, what `mapValue` leaves alone, field and row
445
- validators, server-side errors.
477
+ API, wrapping a built-in, what `mapValue` leaves alone, field, row and
478
+ cross-row validators, derived columns with a table-wide rule, server-side
479
+ errors.
446
480
  - [Editing API](references/editing-api.md) - every option, callback, column meta
447
481
  field, export, CSS variable and data attribute belonging to editing.
448
482
  - [Common mistakes](references/common-mistakes.md) - the failure modes above,
@@ -22,7 +22,7 @@ useTMDataGrid({
22
22
  Correct: wire `editing.onCommit` to apply the change where the data lives, as
23
23
  in the skill's Setup section.
24
24
 
25
- Source: `src/docs/editing.md`, `src/tmdatagrid/core/editEngine.ts`.
25
+ Source: `packages/tmdatagrid/docs/editing.md`, `packages/tmdatagrid/src/core/editEngine.ts`.
26
26
 
27
27
  ## CRITICAL `getRowId` built from the row index
28
28
 
@@ -42,7 +42,7 @@ Correct:
42
42
  getRowId: (row) => String(row.id),
43
43
  ```
44
44
 
45
- Source: `src/tmdatagrid/useTMDataGrid.tsx` (`TMDataGridEditingCallbacks`).
45
+ Source: `packages/tmdatagrid/src/useTMDataGrid.tsx` (`TMDataGridEditingCallbacks`).
46
46
 
47
47
  ## HIGH A cell editor defined inside the component
48
48
 
@@ -71,45 +71,7 @@ const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => (
71
71
  meta: { edit: { editor: SalaryEditor } },
72
72
  ```
73
73
 
74
- Source: `src/docs/editors.md`, `src/tmdatagrid/core/editEngine.ts`.
75
-
76
- ## HIGH A custom editor that binds no error text
77
-
78
- The built-in editors bind the field's first error to the input's `error` prop.
79
- A custom editor that binds nothing still blocks the commit, but all the user
80
- sees is `data-invalid` on the cell - the row stays open with no message
81
- anywhere on screen, which reads as a broken grid rather than a rejected value.
82
-
83
- Wrong:
84
-
85
- ```tsx
86
- const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => (
87
- <Slider value={field.state.value} onChange={field.handleChange} onChangeEnd={() => void commit()} />
88
- );
89
- ```
90
-
91
- Correct:
92
-
93
- ```tsx
94
- const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => {
95
- const error = field.state.meta.errors
96
- .map((e) => (typeof e === "string" ? e : e?.message))
97
- .find(Boolean);
98
- return (
99
- <Slider
100
- value={field.state.value}
101
- onChange={field.handleChange}
102
- onChangeEnd={() => void commit()}
103
- error={error}
104
- />
105
- );
106
- };
107
- ```
108
-
109
- An entry of `field.state.meta.errors` is a string from a function validator,
110
- or an issue carrying a `message` from a schema.
111
-
112
- Source: `src/docs/editors.md` (Writing your own).
74
+ Source: `packages/tmdatagrid/docs/editors.md`, `packages/tmdatagrid/src/core/editEngine.ts`.
113
75
 
114
76
  ## HIGH A cross-field rule under `editing.mode: "cell"`
115
77
 
@@ -120,7 +82,7 @@ other column's old value, and the row cannot be saved.
120
82
  Correct: `editing.rowValidators` needs `mode: "row"`, which validates the
121
83
  whole row in one commit.
122
84
 
123
- Source: `src/docs/editors.md` (Validation).
85
+ Source: `packages/tmdatagrid/docs/editors.md` (Validation).
124
86
 
125
87
  ## HIGH An `accessorFn` column that never opens an editor
126
88
 
@@ -147,7 +109,7 @@ columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
147
109
  });
148
110
  ```
149
111
 
150
- Source: `src/docs/editing.md` (Which cells edit).
112
+ Source: `packages/tmdatagrid/docs/editing.md` (Which cells edit).
151
113
 
152
114
  ## HIGH Swallowing the error in `editing.onCommit`
153
115
 
@@ -170,7 +132,7 @@ onCommit: async ({ rowId, value }) => {
170
132
  Correct: no `catch` - let the rejection propagate, and the form stays open
171
133
  with the error on the row.
172
134
 
173
- Source: `src/tmdatagrid/useTMDataGrid.tsx` (`TMDataGridEditingCallbacks`).
135
+ Source: `packages/tmdatagrid/src/useTMDataGrid.tsx` (`TMDataGridEditingCallbacks`).
174
136
 
175
137
  ## HIGH Submitting an outer form while the grid holds a draft
176
138
 
@@ -186,12 +148,14 @@ const hasOpenDraft = useSelector(grid.edit.store, (s) => s.openRowIds.length > 0
186
148
 
187
149
  <Button type="submit" disabled={!canSubmit || hasOpenDraft}>Save</Button>
188
150
  // or commit and flush instead of blocking:
189
- await grid.edit.commitAll();
190
- const flushed = await grid.edit.saveDrafts();
191
- if (flushed) await form.handleSubmit();
151
+ const committed = await grid.edit.commitAll();
152
+ const flushed = committed.ok ? await grid.edit.saveDrafts() : undefined;
153
+ if (flushed?.ok) await form.handleSubmit();
192
154
  ```
193
155
 
194
- Source: `src/docs/query-builder.md` (Which mode, Submitting).
156
+ Both results are objects, so test `.ok`: `if (await grid.edit.saveDrafts())` is always true.
157
+
158
+ Source: `packages/tmdatagrid/docs/query-builder.md` (Which mode, Submitting).
195
159
 
196
160
  ## HIGH A bulk write built from `begin`, `getForm` and `commit`
197
161
 
@@ -223,40 +187,51 @@ await grid.edit.setRowValues(rowId, { status: "Closed", closedOn: today() });
223
187
  Both write the stored value through the row's form, so `meta.edit.mapValue` does not run - no editor is involved - while `meta.edit.validate` does, at the commit.
224
188
  A refused value leaves the row open carrying its errors and the call resolves `false`, so read the result rather than assuming the fill landed.
225
189
 
226
- Source: `src/tmdatagrid/core/editEngine.ts` (`begin`, `writeFields`).
190
+ Source: `packages/tmdatagrid/src/core/editEngine.ts` (`begin`, `writeFields`).
227
191
 
228
- ## MEDIUM A computed column frozen while a row is edited
192
+ ## MEDIUM Reading an open row's values off `row.original` outside a cell
229
193
 
230
- A held draft is displayed by the column that owns the field. A column computed
231
- from other fields - `accessorFn` or `display` - reads `row.original`, which is
232
- `data`, so it keeps showing the saved record while the values it derives from
233
- are being typed.
194
+ Inside a `cell` renderer, `row.original` and `getValue()` are the row as
195
+ shown: the open form's values while the row is edited, the committed draft
196
+ after ✓, and `data` otherwise - a computed column follows the draft as it is
197
+ typed, and a button in a cell sends the draft the user sees. Outside it, the
198
+ row callbacks (`onRowClick`, `renderRowContextMenu`, `rowClassName`) and any
199
+ code holding a row id see `data`, or the committed draft, and never an open
200
+ form's values.
234
201
 
235
- Correct: read the drafted row from `edit.store` inside the cell renderer:
202
+ Correct: reach the row as shown by id:
236
203
 
237
204
  ```tsx
238
- function useDraftedRow(rowId: string, original: Product): Product {
239
- const { edit } = useTMDataGridContext();
240
- const values = useSelector(edit.store, (state) => state.rows[rowId]?.values);
241
- return (values as Product | undefined) ?? original;
242
- }
205
+ const shown = grid.edit.getRowValues(row.id) ?? row.original;
243
206
  ```
244
207
 
245
- Source: `src/docs/editing.md` (Draft lifetime).
208
+ Source: `packages/tmdatagrid/docs/editing.md` (How a draft renders).
246
209
 
247
210
  ## MEDIUM Reading a commit's result as the saved value
248
211
 
249
- `edit.commit(rowId)`, `edit.commitAll()` and `edit.saveDrafts()` resolve to a
250
- `boolean` saying whether everything landed, and resolve `false` when validation
251
- or a rejected save kept a row open. Ignoring the result reports a save that did
252
- not happen.
212
+ `edit.commit(rowId)` resolves a `boolean`: `false` when validation or a rejected commit kept the row open.
213
+ `edit.commitAll()` resolves `{ ok, committed, open }`, and `edit.saveDrafts()` resolves `{ ok, saved, kept, reopened }`.
214
+ Each id is in exactly one list, and `ok` is `false` when any row stayed open, was kept in the draft store, or was reopened with an error.
215
+ Ignoring the result reports a save that did not happen, and testing the object itself is always true.
216
+
217
+ Wrong:
253
218
 
254
219
  ```tsx
255
- const saved = await grid.edit.saveDrafts();
256
- notifications.show({ message: saved ? "Saved" : "Some rows need attention" });
220
+ if (await grid.edit.saveDrafts()) notifications.show({ message: "Saved" });
257
221
  ```
258
222
 
259
- Source: `src/tmdatagrid/core/editEngine.ts` (`TMDataGridEditApi`).
223
+ Correct:
224
+
225
+ ```tsx
226
+ const { ok, kept, reopened } = await grid.edit.saveDrafts();
227
+ notifications.show({
228
+ message: ok
229
+ ? "Saved"
230
+ : `${kept.length} rows not saved, ${reopened.length} rows need attention`,
231
+ });
232
+ ```
233
+
234
+ Source: `packages/tmdatagrid/src/core/editEngine.ts` (`TMDataGridEditApi`).
260
235
 
261
236
  ## MEDIUM Expecting `editing.onRowDelete` to fire under a draft store
262
237
 
@@ -266,4 +241,37 @@ Under `draft: true` it only toggles a deletion mark, so nothing is removed until
266
241
  no such callback, in the per-row `editing.onRowDelete` loop. A confirmation
267
242
  placed inside `editing.onRowDelete` therefore guards the save, not the trash.
268
243
 
269
- Source: `src/docs/editing.md` (Adding and deleting rows).
244
+ Source: `packages/tmdatagrid/docs/adding-rows.md`.
245
+
246
+ ## MEDIUM A custom editor that binds no invalid state
247
+
248
+ The message is the host's: it shows in a tooltip on the editor whatever the
249
+ editor is. The invalid styling is the editor's own, and one that binds nothing
250
+ keeps its normal border while the commit is refused, so the only marks on
251
+ screen are the tooltip and `data-invalid` on the cell.
252
+
253
+ Wrong:
254
+
255
+ ```tsx
256
+ const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => (
257
+ <Slider value={field.state.value} onChange={field.handleChange} onChangeEnd={() => void commit()} />
258
+ );
259
+ ```
260
+
261
+ Correct:
262
+
263
+ ```tsx
264
+ const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => {
265
+ const hasError = field.state.meta.errors.length > 0;
266
+ return (
267
+ <Slider
268
+ value={field.state.value}
269
+ onChange={field.handleChange}
270
+ onChangeEnd={() => void commit()}
271
+ error={hasError}
272
+ />
273
+ );
274
+ };
275
+ ```
276
+
277
+ Source: `packages/tmdatagrid/docs/editors.md` (Writing your own).
@@ -9,7 +9,7 @@ kind.
9
9
  | --- | --- | --- | --- |
10
10
  | `editing` | `TMDataGridEditingOptions` | off | The editing namespace. Setting it turns editing on. |
11
11
  | `editing.mode` | `"cell" \| "cellConfirm" \| "row"` | – | What counts as a commit, and which controls trigger it. |
12
- | `editing.draft` | `boolean` | `false` | Where a commit goes. On, it parks in the grid's draft store until `edit.saveDrafts()`. |
12
+ | `editing.draft` | `boolean` | `false` | Where a commit goes. On, it is held in the grid's draft store until `edit.saveDrafts()`. |
13
13
  | `getRowId` | `(row) => string` | – | A TanStack table option, required once `editing` is set. Drafts are keyed by it. |
14
14
  | `editing.columns` | `ReadonlyArray<string>` | every column mapping to a data path | The column ids that take edits. Gates before `meta.edit`, never past it: a column left out takes no edits whatever its meta says, and a listed column still answers to its `meta.edit.enabled`. Also decides which cells an entry row opens. |
15
15
  | `editing.isRowEditable` | `(row) => boolean` | – | Closes a whole row to editing, in every mode. |
@@ -28,8 +28,7 @@ the type.
28
28
  | Name | Argument | What it does |
29
29
  | --- | --- | --- |
30
30
  | `editing.onCommit` | `{ rowId, value, original, changes, source }` | Applies one row's change. Reject to keep the draft and show the error. |
31
- | `editing.onSaveDrafts` | `{ updated, created, deleted }` | `draft: true` only. One call for the whole draft store. Without it, `saveDrafts` loops `editing.onCommit`, `editing.onRowAdd` and `editing.onRowDelete`. |
32
- | `editing.onCommitDrafts` | `{ updated, created, deleted }` | **Deprecated** - renamed to `onSaveDrafts`. Still honoured; the new name wins if both are set. |
31
+ | `editing.onSaveDrafts` | `{ updated, created, deleted }` | `draft: true` only. One call for the whole draft store. May return a `TMDataGridSaveDraftsResponse` naming the ids that failed; they stay in the draft store. Each of its buckets is a `TMDataGridSaveOutcomes`: `boolean \| Record<string, boolean>`, where `false` keeps an entry's draft and an id the map does not name counts as saved. Without it, `saveDrafts` loops `editing.onCommit`, `editing.onRowAdd` and `editing.onRowDelete`. |
33
32
  | `editing.onRowAdd` | `{ tempId, value }` | Commits an entry row. Mint the real id here. |
34
33
  | `editing.onRowDelete` | `{ rowId, row }` | Deletes a row. Shows the trash; under `draft: true`, `onSaveDrafts` shows it too. |
35
34
 
@@ -54,24 +53,25 @@ path, which may be dotted.
54
53
  | Member | Signature | Notes |
55
54
  | --- | --- | --- |
56
55
  | `begin` | `({ rowId, columnId }) => void` | Row mode opens the entire row either way. `columnId` selects which cell takes the caret; `null` (the pencil) uses its first editable one. On a committed row it reopens it, taking it back out of the draft store. |
57
- | `commit` | `(rowId) => Promise<boolean>` | The OK gesture: submits the row's form. `false` keeps it open with its errors, and the message outlives the editor that found it. Under `draft: true` a pass parks the row - no consumer callback runs until `saveDrafts`. Column rules run whether or not an editor is mounted. |
58
- | `commitAll` | `() => Promise<boolean>` | Submits every open row. Rows that fail stay open. `false` when one did. |
59
- | `saveDrafts` | `() => Promise<boolean>` | Sends the draft store. Open rows are left alone and stay open. |
56
+ | `commit` | `(rowId) => Promise<boolean>` | The OK gesture: submits the row's form. `false` keeps it open with its errors, and the message outlives the editor that found it. Under `draft: true` a pass commits the row into the draft store - no consumer callback runs until `saveDrafts`. Column rules run whether or not an editor is mounted. |
57
+ | `commitAll` | `() => Promise<TMDataGridCommitAllResult>` | Submits every open row. Rows that fail stay open. Resolves `{ ok, committed, open }`: every row open at the call is in exactly one list, and `ok` is `false` when one stayed open. |
58
+ | `saveDrafts` | `() => Promise<TMDataGridSaveDraftsResult>` | Sends the draft store, re-running `editing.tableValidators` only; a committed row that fails is reopened with its errors. Open rows are left alone and stay open. Resolves `{ ok, saved, kept, reopened }` over row ids and temp ids, all kinds mixed: `saved` left the draft store, `kept` is still in it for the next save (reported failed by `onSaveDrafts`, sent when it threw, or a deletion whose per-row `onRowDelete` threw), `reopened` is open again with an error (a table rule, or a throwing per-row `onCommit` / `onRowAdd`). `ok` is `false` when anything was kept or reopened. A call during a save joins it and resolves the same result. |
60
59
  | `cancel` | `(rowId) => void` | Drops one draft. |
61
60
  | `cancelAll` | `() => void` | Drops every draft. |
62
61
  | `deactivate` | `() => void` | Closes the editor without touching the draft, as blur does under `"cellConfirm"`. |
63
- | `submitAll` | `() => Promise<boolean>` | **Deprecated** - `commitAll()` then `saveDrafts()`. |
64
62
  | `clearCell` | `(rowId, columnId) => Promise<boolean>` | What Delete does: writes the type's empty value and commits. |
65
- | `setCellValue` | `(rowId, columnId, value) => Promise<boolean>` | Writes one cell and commits the row, with no editor - toolbar actions and bulk fills. The row need not be mounted; a row inside a collapsed group takes the write. `value` is the stored value, so `meta.edit.mapValue` does not run and `meta.edit.validate` does. Under `draft: true` the row parks like any hand-made edit. `false` when the cell takes no edit, or when validation refused the value and left the row open with its errors. |
63
+ | `setCellValue` | `(rowId, columnId, value) => Promise<boolean>` | Writes one cell and commits the row, with no editor - toolbar actions and bulk fills. The row need not be mounted; a row inside a collapsed group takes the write. `value` is the stored value, so `meta.edit.mapValue` does not run and `meta.edit.validate` does. Under `draft: true` the row is committed into the draft store like any hand-made edit. `false` when the cell takes no edit, or when validation refused the value and left the row open with its errors. |
66
64
  | `setRowValues` | `(rowId, values) => Promise<boolean>` | `setCellValue` for several cells of one row in a single commit - one consumer call and one draft entry. Keys are column ids. All or nothing: if any named cell takes no edit, nothing is written and it resolves `false`. |
67
65
  | `addRow` | `(values?) => string` | Opens an entry row, returns its `tempId`. `values` overrides `editing.newRowDefaults` key by key for that row; with no argument the row is `newRowDefaults` alone. |
68
- | `addRows` | `(rows, options?) => Promise<{ committed, open }>` | Opens a batch in one write, each row seeded as `addRow` seeds. `{ commit: true }` submits each as it lands - valid rows commit, invalid ones stay open with their errors. |
69
- | `deleteRow` | `(rowId) => void` | `editing.onRowDelete`, or a deletion mark under `draft: true`. Toggles: a second call restores the row. |
66
+ | `addRows` | `(rows, options?: TMDataGridAddRowsOptions) => Promise<TMDataGridAddRowsResult>` | Opens a batch in one write, each row seeded as `addRow` seeds. `{ commit: true }` submits each as it lands - valid rows commit, invalid ones stay open with their errors. Resolves `{ ok, committed, open }`; `ok` is `false` when a row stayed open. |
67
+ | `deleteRow` | `(rowId) => void` | `editing.onRowDelete`, or a deletion mark under `draft: true`. Idempotent: a second call leaves the row marked; `restoreRow` is the undo. |
70
68
  | `canEditCell` | `(row, column) => boolean` | The check the built-in controls use. Both halves: the column's and the row's. |
71
69
  | `canEditRow` | `(row) => boolean` | The pencil's gate. |
72
70
  | `isColumnEditable` | `(column) => boolean` | The column's half alone, with no row in hand: it maps to a field, `editing.columns` lists it when that is set, and `meta.edit.enabled` is not `false`. |
73
71
  | `canDeleteRows` | `() => boolean` | Whether the delete control should be shown. |
74
- | `getForm` | `(rowId) => TMDataGridRowEditForm \| undefined` | The row's live `FormApi`. |
72
+ | `getForm` | `(rowId) => TMDataGridRowEditForm \| undefined` | The open row's live `FormApi`; `undefined` for a committed row. |
73
+ | `getRowValues` | `(rowId) => TData \| undefined` | The row as shown: the open form's values, else the committed draft, else the `data` value. `undefined` for an unknown row. |
74
+ | `getRows` | `() => ReadonlyArray<TMDataGridEditRowSnapshot>` | Every row as shown, from the core row model: drafts overlaid, entry rows appended, deletion-marked rows included and flagged `deleted`. |
75
75
  | `state` | `TMDataGridEditState` | Snapshot, for reads outside React. |
76
76
  | `store` | `Store<TMDataGridEditState>` | For `useSelector`. |
77
77
 
@@ -81,10 +81,10 @@ path, which may be dotted.
81
81
  type TMDataGridEditState = {
82
82
  // The cell the last open gesture named - where the caret goes.
83
83
  active: { rowId: string; columnId: string | null } | null;
84
- // Rows with a live form, committed or not. A row is *open* when it is in
85
- // here and not in `committedRowIds`.
84
+ // Every row the grid holds work for: open rows and committed rows. A row
85
+ // is *open* when it is in here and not in `committedRowIds`.
86
86
  openRowIds: ReadonlyArray<string>;
87
- // One `TMDataGridEditRowProjection` per open row.
87
+ // One `TMDataGridEditRowProjection` per row in `openRowIds`.
88
88
  rows: Record<
89
89
  string,
90
90
  {
@@ -96,8 +96,8 @@ type TMDataGridEditState = {
96
96
  values: TMDataGridRowData;
97
97
  }
98
98
  >;
99
- // The draft store's edit slice: existing rows whose form passed its submit,
100
- // parked for `saveDrafts`. Empty without `editing.draft`.
99
+ // The draft store's edit slice: existing rows that passed their commit,
100
+ // held as values for `saveDrafts`. Empty without `editing.draft`.
101
101
  committedRowIds: ReadonlyArray<string>;
102
102
  // What a committed row *is* to the table: the draft store's values per row,
103
103
  // snapshotted at each commit and kept across a reopen, so the row holds its
@@ -108,6 +108,8 @@ type TMDataGridEditState = {
108
108
  // a commit adds through `onRowAdd`, so it stays `false`.
109
109
  newRows: ReadonlyArray<{ tempId: string; committed: boolean }>;
110
110
  deletedRowIds: ReadonlyArray<string>;
111
+ // `true` while `saveDrafts` is in flight.
112
+ isSaving: boolean;
111
113
  };
112
114
  ```
113
115
 
@@ -119,16 +121,19 @@ type TMDataGridEditState = {
119
121
  | `TMDataGridDraftActions` | Export | The same component, for use outside the namespace. |
120
122
  | `EDIT_COLUMN_ID` | Export | `"__edit__"`, the generated edit lane's id. |
121
123
  | `clearedValueForType` | Export | `(type) => unknown` - what Delete writes per column type. |
124
+ | `hasPendingEdits` | Export | `(state) => boolean` - unsaved work: an open row with a changed value, any entry row, the draft store, or a save in flight. `useSelector(grid.edit.store, hasPendingEdits)` for a navigation blocker; do not use `openRowIds.length`, which also counts committed rows and rows that are only open. |
122
125
  | `getEditFieldName` | Export | `(column) => string` - the data path a column's edits write to. |
123
126
  | `normalizeFieldValidate` | Export | `(validate) => validators` - a bare schema into Form's shape. |
124
127
  | `TMDataGridStringEditor` … `TMDataGridMultiSelectEditor` | Exports | The six built-in editors, for wrapping. |
125
128
 
126
129
  Types: `TMDataGridEditMode`, `TMDataGridEditApi`, `TMDataGridEditState`,
127
- `TMDataGridEditCommitArgs`, `TMDataGridEditCommitDraftsArgs`,
130
+ `TMDataGridEditCommitArgs`, `TMDataGridSaveDraftsArgs`,
128
131
  `TMDataGridEditChange`, `TMDataGridEditorArgs`, `TMDataGridEditorComponent`,
129
132
  `TMDataGridEditField`, `TMDataGridEditRowProjection`, `TMDataGridFieldValidate`,
130
133
  `TMDataGridRowValidators`, `TMDataGridRowEditForm`, `TMDataGridRowAddArgs`,
131
- `TMDataGridRowDeleteArgs`, `TMDataGridEditingOptions`.
134
+ `TMDataGridRowDeleteArgs`, `TMDataGridEditingOptions`,
135
+ `TMDataGridSaveOutcomes`, `TMDataGridAddRowsOptions`,
136
+ `TMDataGridEditValueMapArgs`, `TMDataGridTableValidateArgs`.
132
137
 
133
138
  ## The edit lane
134
139
 
@@ -148,11 +153,11 @@ The trash itself shows when the deletion has somewhere to report to:
148
153
  mark is part of the save.
149
154
 
150
155
  What the lane holds follows the row's state, one axis each. An open row shows
151
- the mode's own controls, `save-row` and `cancel-row`. A parked row shows
156
+ the mode's own controls, `save-row` and `cancel-row`. A committed row shows
152
157
  `row-state` instead, whose `data-state` is `new`, `edited` or `deleted`,
153
158
  together with `revert-row` on an edited row, `restore-row` on one marked for
154
159
  deletion, and `edit-row` plus `discard-new-row` on an entered new row - never a
155
- save, which the engine would only park again. A parked row hides `delete-row`.
160
+ save, which the engine would only commit again. A committed row hides `delete-row`.
156
161
  Every control carries a tooltip from the labels, `revertRow`, `rowStateNew`,
157
162
  `rowStateEdited` and `rowStateDeleted` among them.
158
163