@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
@@ -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,14 +9,14 @@ 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. |
16
16
  | `editing.rowValidators` | `TMDataGridRowValidators` | – | Form-level validation. Cross-field rules live here. |
17
17
  | `editing.tableValidators` | `TMDataGridTableValidators` | – | Cross-row rules, handed the collection with every draft overlaid. |
18
18
  | `editing.newRowDefaults` | `TData \| (() => TData)` | – | Seeds the entry row's form. A function is called per added row. |
19
- | `editing.newRowsSticky` | `boolean` | `false` | `draft: true` only. Keeps entered new rows pinned in the entry block until the save, instead of letting them scroll with the body. |
19
+ | `editing.newRowsSticky` | `boolean` | `false` | `draft: true` only. Keeps committed entry rows in the sticky entry block, out of the body's sort and out of the row count, until the save. Off, a committed entry row becomes a body row. |
20
20
  | `cellSelection` | `"none" \| "single" \| "range"` | `"single"` while `editing` is set | Editing turns the cell cursor on; set it explicitly to override. |
21
21
 
22
22
  Passing any other member of `editing` without `mode` is a compile error, and
@@ -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. 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?) => 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,13 +96,20 @@ 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
+ // What a committed row *is* to the table: the draft store's values per row,
103
+ // snapshotted at each commit and kept across a reopen, so the row holds its
104
+ // place until it commits again or is dropped. The grid feeds these to the
105
+ // table in place of the consumer's records.
106
+ committedValues: Readonly<Record<string, TMDataGridRowData>>;
102
107
  // `committed` is "in the store, awaiting the save". Without `editing.draft`
103
108
  // a commit adds through `onRowAdd`, so it stays `false`.
104
109
  newRows: ReadonlyArray<{ tempId: string; committed: boolean }>;
105
110
  deletedRowIds: ReadonlyArray<string>;
111
+ // `true` while `saveDrafts` is in flight.
112
+ isSaving: boolean;
106
113
  };
107
114
  ```
108
115
 
@@ -114,12 +121,13 @@ type TMDataGridEditState = {
114
121
  | `TMDataGridDraftActions` | Export | The same component, for use outside the namespace. |
115
122
  | `EDIT_COLUMN_ID` | Export | `"__edit__"`, the generated edit lane's id. |
116
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. |
117
125
  | `getEditFieldName` | Export | `(column) => string` - the data path a column's edits write to. |
118
126
  | `normalizeFieldValidate` | Export | `(validate) => validators` - a bare schema into Form's shape. |
119
127
  | `TMDataGridStringEditor` … `TMDataGridMultiSelectEditor` | Exports | The six built-in editors, for wrapping. |
120
128
 
121
129
  Types: `TMDataGridEditMode`, `TMDataGridEditApi`, `TMDataGridEditState`,
122
- `TMDataGridEditCommitArgs`, `TMDataGridEditCommitDraftsArgs`,
130
+ `TMDataGridEditCommitArgs`, `TMDataGridSaveDraftsArgs`,
123
131
  `TMDataGridEditChange`, `TMDataGridEditorArgs`, `TMDataGridEditorComponent`,
124
132
  `TMDataGridEditField`, `TMDataGridEditRowProjection`, `TMDataGridFieldValidate`,
125
133
  `TMDataGridRowValidators`, `TMDataGridRowEditForm`, `TMDataGridRowAddArgs`,
@@ -143,11 +151,11 @@ The trash itself shows when the deletion has somewhere to report to:
143
151
  mark is part of the save.
144
152
 
145
153
  What the lane holds follows the row's state, one axis each. An open row shows
146
- the mode's own controls, `save-row` and `cancel-row`. A parked row shows
154
+ the mode's own controls, `save-row` and `cancel-row`. A committed row shows
147
155
  `row-state` instead, whose `data-state` is `new`, `edited` or `deleted`,
148
156
  together with `revert-row` on an edited row, `restore-row` on one marked for
149
157
  deletion, and `edit-row` plus `discard-new-row` on an entered new row - never a
150
- save, which the engine would only park again. A parked row hides `delete-row`.
158
+ save, which the engine would only commit again. A committed row hides `delete-row`.
151
159
  Every control carries a tooltip from the labels, `revertRow`, `rowStateNew`,
152
160
  `rowStateEdited` and `rowStateDeleted` among them.
153
161
 
@@ -156,11 +164,11 @@ Every control carries a tooltip from the labels, `revertRow`, `rowStateNew`,
156
164
  | Name | Kind | What it is |
157
165
  | --- | --- | --- |
158
166
  | `--dg-entry-height` | CSS variable | Height of the sticky entry block. From `size`. |
159
- | `--dg-row-new-bg` | CSS variable | Background of an entered new row. A green tint. |
167
+ | `--dg-row-new-bg` | CSS variable | Background of a committed new row, in the body or the entry block. A green tint. |
160
168
  | `data-deleted` | Row attribute | On a row marked for deletion under `draft: true`. |
161
169
  | `data-dirty` | Row attribute | On a body row holding a dirty draft. Also on the cell whose field is dirty. |
162
- | `data-new` / `data-committed` | Entry row attributes | On an entry row; `data-committed` once it is committed, awaiting the save. |
163
- | `data-dg-entry-flow-block` | Attribute | The in-flow block above the body rows holding entered new rows, unless `editing.newRowsSticky` keeps them in the sticky entry block. |
170
+ | `data-new` | Row attribute | On a body row that is a committed new row, and on an entry row. |
171
+ | `data-committed` | Entry row attribute | On an entry row once it is committed, awaiting the save. Seen only under `editing.newRowsSticky`. |
164
172
  | `data-dg-part="editor-input"` | Part | The control inside an editing cell. |
165
173
  | `data-dg-part="save-row"` / `"cancel-row"` | Parts | The edit lane's buttons on an open row, with `data-row-id`. |
166
174
  | `data-dg-part="row-state"` | Part | The draft store's change marker, with `data-row-id` and `data-state` of `new`, `edited` or `deleted`. |
@@ -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";
@@ -193,8 +193,13 @@ meta: { edit: { validate: z.string().min(2, "At least two characters") } }
193
193
  // Object form: pick the trigger.
194
194
  meta: { edit: { validate: { onBlur: z.string().email("Not an email address") } } }
195
195
 
196
- // A plain function works too.
197
- meta: { edit: { validate: ({ value }) => (value > 0 ? undefined : "Must be positive") } }
196
+ // A plain function works too. `value` is typed `never`, so annotate the parameter.
197
+ meta: {
198
+ edit: {
199
+ validate: ({ value }: { value: unknown }) =>
200
+ typeof value === "number" && value > 0 ? undefined : "Must be positive",
201
+ },
202
+ }
198
203
  ```
199
204
 
200
205
  `normalizeFieldValidate(validate)` is exported for consumers building their own
@@ -224,8 +229,8 @@ const grid = useTMDataGrid({
224
229
  ```
225
230
 
226
231
  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
232
+ the row, where the message shows in the edit lane's tooltip on the open
233
+ row's ✓. A nested schema's issues follow the
229
234
  same rule, so a `address.city` issue lands on the column whose `editField` is
230
235
  `"address.city"`.
231
236
 
@@ -261,9 +266,9 @@ row's cells. `onSubmit` runs first, and its failure stands without
261
266
  `onSubmitAsync` running. Errors land on the committing row only.
262
267
 
263
268
  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`.
269
+ row's - after the row's own validators, and again for every committed row during
270
+ `saveDrafts`, the only rules that run there: a committed row that a later edit
271
+ invalidated is reopened with its errors, and the save reports it in `reopened`.
267
272
 
268
273
  ## Server-side errors
269
274
 
@@ -283,9 +288,9 @@ rowValidators: {
283
288
  },
284
289
  ```
285
290
 
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.
291
+ A commit blocked by validation keeps the editor open, invalid, with the message
292
+ in its tooltip. A rejected `editing.onCommit` keeps the draft too, with the
293
+ error on the row.
289
294
 
290
295
  ## Where the state shows
291
296
 
@@ -294,11 +299,13 @@ row.
294
299
  | 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
300
  | Blue cell corner | The field is dirty against its original value |
296
301
  | 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 |
302
+ | Field error message | In a tooltip on the open editor, shown while the input has focus and on hover |
303
+ | Row error text | A pathless rule failed, or a commit was rejected. In the lane's tooltip, on the open row's ✓ |
298
304
  | `data-dirty` on the row | The row holds a dirty draft |
299
305
 
300
306
  The same information is readable from `edit.store`: `rows[rowId].dirtyFields`,
301
307
  `rows[rowId].errorFields`, `rows[rowId].errorMessages` (`{ field, message }`
302
308
  pairs), `rows[rowId].hasRowError`, `rows[rowId].isSubmitting`, and
303
309
  `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`.
310
+ in the store; read it from `edit.getForm(rowId)?.state.errors` on the open
311
+ row that carries it.