@jielga/tmdatagrid 2.0.0-beta.2 → 2.0.0-beta.21
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.
- package/README.md +5 -212
- package/dist/index.d.ts +1664 -632
- package/dist/index.js +5226 -3223
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/anatomy.md +102 -0
- package/docs/cell-selection.md +154 -0
- package/docs/column-layout.md +204 -0
- package/docs/columns.md +262 -0
- package/docs/components.md +304 -0
- package/docs/editing.md +603 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +326 -0
- package/docs/filtering.md +358 -0
- package/docs/getting-started.md +123 -0
- package/docs/grouping.md +165 -0
- package/docs/loading-and-empty.md +92 -0
- package/docs/localization.md +79 -0
- package/docs/menu.md +143 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +111 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +175 -0
- package/docs/quick-search.md +83 -0
- package/docs/row-details.md +113 -0
- package/docs/row-interaction.md +148 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +134 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +111 -0
- package/docs/server-query.md +246 -0
- package/docs/server-side.md +206 -0
- package/docs/sorting.md +101 -0
- package/docs/styling.md +126 -0
- package/docs/summary-row.md +76 -0
- package/docs/testing.md +309 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +21 -45
- package/skills/appearance/SKILL.md +70 -17
- package/skills/cell-selection/SKILL.md +70 -76
- package/skills/columns/SKILL.md +131 -32
- package/skills/data/SKILL.md +100 -23
- package/skills/editing/SKILL.md +217 -96
- package/skills/editing/references/common-mistakes.md +111 -24
- package/skills/editing/references/editing-api.md +63 -39
- package/skills/editing/references/editors-and-validation.md +77 -19
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +18 -16
- package/skills/grouping/SKILL.md +32 -15
- package/skills/options/SKILL.md +39 -9
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +10 -7
- package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
- package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +39 -23
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +106 -38
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +4 -4
- package/src/components/TMDataGridDraftActions.tsx +307 -0
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +58 -50
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +150 -115
- package/src/components/TMDataGridExportPicker.module.css +77 -0
- package/src/components/TMDataGridExportPicker.tsx +234 -0
- package/src/components/TMDataGridFilterPanel.module.css +54 -0
- package/src/components/TMDataGridFilterPanel.tsx +348 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +7 -5
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -13
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +4 -3
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +10 -0
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +100 -28
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +354 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +12 -7
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +90 -67
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +678 -156
- package/src/components/TMDataGridToolbar.module.css +21 -0
- package/src/components/TMDataGridToolbar.tsx +181 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
- package/src/components/editors/TMDataGridNumberEditor.tsx +70 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/editorShared.ts +17 -31
- package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
- package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
- package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
- package/src/components/filters/controlLayout.ts +32 -0
- package/src/components/filters/filterControlFor.ts +65 -0
- package/src/{tmdatagrid/components → components}/icons.ts +1 -0
- package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
- package/src/components/useHideableColumns.ts +52 -0
- package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
- package/src/{tmdatagrid/core → core}/capabilities.ts +14 -6
- package/src/{tmdatagrid/core → core}/columnOptions.ts +46 -0
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledState.ts +179 -0
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/core/editEngine.ts +2476 -0
- package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
- package/src/core/export.ts +843 -0
- package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
- package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
- package/src/core/filterSurface.ts +99 -0
- package/src/{tmdatagrid/core → core}/labels.ts +66 -8
- package/src/{tmdatagrid/core → core}/labelsSv.ts +26 -3
- package/src/core/pageReset.ts +120 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/core/resizePreview.ts +141 -0
- package/src/core/summary.ts +59 -0
- package/src/core/useSettledTableState.ts +36 -0
- package/src/{tmdatagrid/index.ts → index.ts} +75 -12
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +734 -135
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -162
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +0 -12
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -162
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +0 -40
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- package/src/tmdatagrid/core/editEngine.ts +0 -1006
- package/src/tmdatagrid/core/summary.ts +0 -35
- /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
- /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/grouping.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
package/docs/editing.md
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
# Editing
|
|
2
|
+
|
|
3
|
+
`@tanstack/react-form` becomes a peer dependency once editing is used.
|
|
4
|
+
|
|
5
|
+
Editing is configured through one option, `editing`, and it has two axes.
|
|
6
|
+
`mode` decides what counts as a commit; `draft` decides where that commit goes.
|
|
7
|
+
The grid never mutates `data`, so you apply the commit and the new values
|
|
8
|
+
arrive back through `data`.
|
|
9
|
+
|
|
10
|
+
A row moves through three places, and the words for them are used exactly:
|
|
11
|
+
|
|
12
|
+
| Place | What it holds | In | Out |
|
|
13
|
+
| --- | --- | --- | --- |
|
|
14
|
+
| **data** | Your rows, the only source of truth | - | - |
|
|
15
|
+
| **form state** | A row being edited: its own TanStack Form, undecided | `edit.begin`, `edit.addRow` | `edit.cancel` |
|
|
16
|
+
| **draft store** | Rows that passed their commit, held as values in the grid | `edit.commit` | `edit.saveDrafts` |
|
|
17
|
+
|
|
18
|
+
A row is **open** while it is form state, and **committed** once it is in the draft store.
|
|
19
|
+
The store only has dwell under `draft: true`; without it a commit goes straight to `onCommit` and the form is dropped, which is the same pipeline with the middle step lasting no time at all.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const grid = useTMDataGrid({
|
|
23
|
+
data,
|
|
24
|
+
columns,
|
|
25
|
+
getRowId: (row) => String(row.id),
|
|
26
|
+
editing: {
|
|
27
|
+
mode: "cell",
|
|
28
|
+
onCommit: async ({ rowId, value, changes }) => {
|
|
29
|
+
// changes is a list of descriptors, not a patch object
|
|
30
|
+
await api.patch(rowId, Object.fromEntries(changes.map((c) => [c.field, c.next])));
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`onCommit` receives `{ rowId, value, original, changes, source }`.
|
|
37
|
+
`value` is the whole row as edited, `original` the row as editing began, and
|
|
38
|
+
`changes` the per-field diff: `Array<{ columnId, field, previous, next }>`, one
|
|
39
|
+
entry in cell mode.
|
|
40
|
+
|
|
41
|
+
`editing` requires `getRowId`: drafts are keyed by row id, and the index
|
|
42
|
+
fallback would name a different record after any sort. `onSaveDrafts` is
|
|
43
|
+
accepted only under `draft: true`. Both are compile errors rather than options
|
|
44
|
+
that silently do nothing. `draft: true` without `onSaveDrafts` is fine:
|
|
45
|
+
`saveDrafts` falls back to the per-row `onCommit` loop.
|
|
46
|
+
|
|
47
|
+
The `editing` object may be written inline. Its callbacks are read through a ref
|
|
48
|
+
on every render, so its identity does not matter.
|
|
49
|
+
|
|
50
|
+
## The three modes
|
|
51
|
+
|
|
52
|
+
All three use the same engine and the same forms. `editing.mode` sets what
|
|
53
|
+
counts as a commit and which controls trigger it.
|
|
54
|
+
|
|
55
|
+
| Mode | Commit | Cancel | Controls |
|
|
56
|
+
| --------------- | ------------------------------------------ | ----------------- | ------------------------ |
|
|
57
|
+
| `"cell"` | Enter, Tab or leaving the cell | Escape | none |
|
|
58
|
+
| `"cellConfirm"` | ✓ or Enter; Tab walks input, ✓, ✕ and then leaves, keeping the draft | ✕ or Escape | ✓ / ✕ beside the input |
|
|
59
|
+
| `"row"` | Save in the edit lane, or Enter | Cancel, or Escape | generated edit lane |
|
|
60
|
+
|
|
61
|
+
An entry row from `edit.addRow()` is row-shaped in every mode: every editable cell opens at once, Tab walks them, and the lane's ✓ is what enters it.
|
|
62
|
+
|
|
63
|
+
Leaving a cell commits it only once the value passes.
|
|
64
|
+
A refused commit keeps the editor open, invalid, with the message in its tooltip, until the value is fixed or Escape drops it.
|
|
65
|
+
|
|
66
|
+
```demo
|
|
67
|
+
file: editing/CellEditing.tsx
|
|
68
|
+
hint: Double-click a cell, or press Enter or F2, or start typing on it.
|
|
69
|
+
height: 440
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Opening an editor**: double-click, or, with the cell cursor on the cell,
|
|
73
|
+
Enter, F2, or typing, where the first character replaces the value as it would
|
|
74
|
+
in a spreadsheet. The grid places the caret in the cell that was opened, so a
|
|
75
|
+
`meta.edit.editor` receives focus without handling it itself. A row added with
|
|
76
|
+
`edit.addRow()` opens the same way, with the caret in its first editable cell.
|
|
77
|
+
|
|
78
|
+
Delete or Backspace clears the value and commits without opening an editor;
|
|
79
|
+
under `draft: true` the cleared value goes into the draft store with the rest. The
|
|
80
|
+
commit validates either way - a column rule that rejects the empty value
|
|
81
|
+
refuses the clear and marks the cell, rather than writing past the rule.
|
|
82
|
+
Editing implies cell selection: `cellSelection` defaults to `"single"` while
|
|
83
|
+
`editing` is set.
|
|
84
|
+
|
|
85
|
+
### Row editing
|
|
86
|
+
|
|
87
|
+
The pencil opens every cell of the row at once, and ✓ saves them as **one
|
|
88
|
+
commit**. Cross-field rules belong here, since the whole row is validated
|
|
89
|
+
together. Double-clicking a cell opens the whole row, with the caret in the cell
|
|
90
|
+
that was clicked.
|
|
91
|
+
|
|
92
|
+
Rows **accumulate**: opening a second row leaves the first one open, and each
|
|
93
|
+
row's ✓ and ✕ act on that row alone.
|
|
94
|
+
|
|
95
|
+
```demo
|
|
96
|
+
file: editing/RowEditing.tsx
|
|
97
|
+
hint: Put a Sales row over 60 000 kr and Save reports why it is rejected.
|
|
98
|
+
height: 440
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## The draft store
|
|
102
|
+
|
|
103
|
+
`draft: true` changes where a commit goes, and nothing else: instead of reaching `onCommit`, the row is committed into the grid's draft store and waits for `edit.saveDrafts()`.
|
|
104
|
+
The mode still decides what counts as a commit, so the two combine freely - `{ mode: "row", draft: true }` commits a whole row from the lane's ✓, `{ mode: "cell", draft: true }` commits a row as the caret leaves it.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
editing: {
|
|
108
|
+
mode: "row",
|
|
109
|
+
draft: true,
|
|
110
|
+
onSaveDrafts: async ({ updated, created, deleted }) => {
|
|
111
|
+
await api.saveBatch({ updated, created, deleted });
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
It is also a row like any other to the table: it sorts, filters, groups, aggregates and counts on its draft values.
|
|
118
|
+
A committed row that stops matching a filter or the quick search leaves the view, and the Save bar still counts it.
|
|
119
|
+
|
|
120
|
+
Everything that reads a row reads the draft: sorting, filtering, quick search, grouping, `aggregatedCell` and `footer`, the faceted filter options, export, row selection, the row numbers and counts, `edit.getRows()` and `editing.tableValidators`.
|
|
121
|
+
The row callbacks are handed the same rows: `onRowClick`, `renderRowContextMenu`, `renderDetails`, `isRowEditable`, `meta.edit.enabled`, `rowClassName`, `rowStyle` and `enableRowPinning` receive a row whose `original` is the committed draft, and for an entered row a record under its temp id, carrying no server id.
|
|
122
|
+
`data` itself is never modified, and `getRowCount()` with a `rowCount` you set does not grow.
|
|
123
|
+
Only top-level rows are overlaid: children reached through `getSubRows` keep their `data` values.
|
|
124
|
+
A row reopened for a further edit keeps its place until it commits again or is cancelled.
|
|
125
|
+
|
|
126
|
+
A commit moves nothing else: the page stays, and open details panels and groups stay open.
|
|
127
|
+
TanStack's `autoResetPageIndex` and `autoResetExpanded` fire on any change to the `data` array, which under `draft: true` is every commit, so the grid switches both off and resets the page on a query change itself - see `resetPageOnQueryChange`.
|
|
128
|
+
A refetch that no longer returns a row drops that row's draft, its open editor and its deletion mark: the server has nothing for Save to act on.
|
|
129
|
+
Under `manualPagination` or `manualFiltering` a row missing from `data` is on another page, not gone, so its draft is kept until Save.
|
|
130
|
+
|
|
131
|
+
A committed row holds no form: its values are data in the draft store.
|
|
132
|
+
`begin` on it, or a write through `setCellValue`, `setRowValues` or `clearCell`, builds a fresh form seeded with those values and takes the row back out of the store until it commits again.
|
|
133
|
+
At `saveDrafts` only `editing.tableValidators` run again, over every committed row; a row they reject is reopened with its errors, and so is a row whose `onCommit` or `onRowAdd` rejects on the per-row path.
|
|
134
|
+
|
|
135
|
+
A row left open is not lost and not sent. It keeps everything typed into it,
|
|
136
|
+
stays open across a save, and joins the next save once it is committed. This
|
|
137
|
+
is what `edit.commitAll()` is for: it submits every open row at once, so
|
|
138
|
+
"commit everything, then save" is two calls, and the rows that fail validation
|
|
139
|
+
stay open with their errors instead of travelling half-checked.
|
|
140
|
+
|
|
141
|
+
A row that fails validation on the way out is the other way a row stays open.
|
|
142
|
+
Its message outlives the editor that found it: the cell keeps its invalid marker and the lane carries the text, until the value that failed is changed.
|
|
143
|
+
While an editor is open, a field's own message shows in a tooltip on it, opened by focus and by hover.
|
|
144
|
+
|
|
145
|
+
The rest of this page is what `draft: true` turns on.
|
|
146
|
+
|
|
147
|
+
### The lane
|
|
148
|
+
|
|
149
|
+
The edit lane holds two things at once, one per axis: the mode's own controls while a row is open, and the draft store's marker once it is committed.
|
|
150
|
+
|
|
151
|
+
- a committed edit - a pencil icon, and Revert, which drops the row's draft
|
|
152
|
+
- a committed new row - a plus icon, a pencil that reopens it, and ✕, which removes it
|
|
153
|
+
- a row marked for deletion - a trash icon, and Restore
|
|
154
|
+
- an open row - whatever the mode offers: Save and Cancel
|
|
155
|
+
|
|
156
|
+
A committed row has had its submit, so the lane never offers to save it again - `TMDataGrid.DraftActions` is what sends it.
|
|
157
|
+
A committed row also hides the trash: revert first, then delete.
|
|
158
|
+
If validation blocks a row, its icon turns red with the message in the tooltip: the open row's ✓, an entry row's included.
|
|
159
|
+
A pathless issue from `rowValidators` has no cell to land on, so that tooltip is where its message shows.
|
|
160
|
+
|
|
161
|
+
### Marking the drafts
|
|
162
|
+
|
|
163
|
+
Rows publish what they are holding, for styling and for tests:
|
|
164
|
+
|
|
165
|
+
| Attribute | On | Means |
|
|
166
|
+
| --- | --- | --- |
|
|
167
|
+
| `data-dirty` | Body row, cell | Values typed in, decided or not |
|
|
168
|
+
| `data-draft` | Body row, entry row | Committed into the draft store, waiting for Save |
|
|
169
|
+
| `data-deleted` | Body row | Marked for deletion |
|
|
170
|
+
| `data-new` | Body row, entry row | An entered row, committed (body) or not (entry block) |
|
|
171
|
+
|
|
172
|
+
A row attribute is published on every body row, `"true"` or `"false"`, so match
|
|
173
|
+
the value - `[data-draft="true"]` - rather than the bare attribute, which
|
|
174
|
+
matches every row. A cell's `data-dirty` is present only while the cell is
|
|
175
|
+
dirty.
|
|
176
|
+
|
|
177
|
+
The grid paints none of them beyond the markers already described. To
|
|
178
|
+
highlight everything pending a save, and to let the user toggle it, use
|
|
179
|
+
`rowStyle` on the Table:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<TMDataGrid.Table
|
|
183
|
+
rowStyle={(row) =>
|
|
184
|
+
showPending && grid.edit.state.committedRowIds.includes(row.id)
|
|
185
|
+
? { "--row-bg": "color-mix(in srgb, var(--mantine-color-yellow-6) 15%, transparent)" }
|
|
186
|
+
: undefined
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`rowClassName` takes a class instead. For CSS alone, target the attribute:
|
|
192
|
+
`[data-dg-part="row"][data-draft="true"]`.
|
|
193
|
+
|
|
194
|
+
`TMDataGrid.DraftActions` in the toolbar provides the whole-grid controls: Save
|
|
195
|
+
with the draft-store count, Discard, and a note counting the rows still open.
|
|
196
|
+
Save sends the store and leaves open rows alone, so it greys out while nothing
|
|
197
|
+
is committed however much is being typed - the note is what keeps those rows
|
|
198
|
+
visible rather than silently left behind.
|
|
199
|
+
The toolbar is declarative: the grid does not add or remove this component for you, so include it when the grid runs a draft store - without `draft: true` there is nothing to save and Save stays disabled.
|
|
200
|
+
|
|
201
|
+
`renderActions` replaces the set and hands over its pieces: `state.draftCount`,
|
|
202
|
+
`state.openCount`, `state.openRowIds`, `state.isSubmitting`, `state.isSaving`,
|
|
203
|
+
the `save`, `commitAll`, `discard`, `scrollToRow` and `scrollToFirstOpenRow`
|
|
204
|
+
actions, and `Controls.Save` / `Controls.Discard` / `Controls.OpenRowsNote` as
|
|
205
|
+
the built-in pieces.
|
|
206
|
+
|
|
207
|
+
Counting the open rows is only half the job on a long grid: the row that still
|
|
208
|
+
needs a decision may be nowhere near the viewport, and the grid is always
|
|
209
|
+
[virtualized](/docs/scrolling), so it may have no element to scroll to.
|
|
210
|
+
`actions.scrollToFirstOpenRow(align?)` goes to the topmost one and answers
|
|
211
|
+
whether it could be reached.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
<TMDataGrid.DraftActions
|
|
215
|
+
renderActions={({ state, actions, Controls }) => (
|
|
216
|
+
<Group>
|
|
217
|
+
{state.draftCount > 0 && <Badge>{state.draftCount} ready</Badge>}
|
|
218
|
+
<Button
|
|
219
|
+
disabled={state.openCount === 0}
|
|
220
|
+
onClick={() => {
|
|
221
|
+
actions.scrollToFirstOpenRow("center");
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
Go to open row
|
|
225
|
+
</Button>
|
|
226
|
+
<Controls.OpenRowsNote />
|
|
227
|
+
<Controls.Save />
|
|
228
|
+
<Controls.Discard />
|
|
229
|
+
</Group>
|
|
230
|
+
)}
|
|
231
|
+
/>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`state.openRowIds` is the ids behind `openCount`, for a control the grid does
|
|
235
|
+
not offer - a list, or a next-open-row cycle. It is in the order the grid
|
|
236
|
+
opened the rows, while `scrollToFirstOpenRow` takes "first" in display order,
|
|
237
|
+
so the two need not name the same row. An entered row appears as its `tempId`;
|
|
238
|
+
those are always on screen in the entry block, so the scroll answers `true`
|
|
239
|
+
without moving.
|
|
240
|
+
|
|
241
|
+
```demo
|
|
242
|
+
file: editing/DraftEditing.tsx
|
|
243
|
+
hint: Double-click a row, ✓ commits it into the draft store and the Backend panel stays quiet. Save sends the whole store in one call; with "Reject Sales rows" on, the backend refuses those and they keep their drafts. "Go to open row" returns to a row left undecided.
|
|
244
|
+
height: 440
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`edit.saveDrafts()` sends the draft store, through the per-row `onCommit` /
|
|
248
|
+
`onRowAdd` / `onRowDelete` loop by default, or through one
|
|
249
|
+
`onSaveDrafts({ updated, created, deleted })` call when that is set - the whole
|
|
250
|
+
store in one payload, for a server that applies it as a transaction.
|
|
251
|
+
`updated` entries are the shape `onCommit` receives -
|
|
252
|
+
`{ rowId, value, original, changes, source }`. `created` entries are
|
|
253
|
+
`{ tempId, value }`, and `deleted` is a list of row ids.
|
|
254
|
+
|
|
255
|
+
`changes` is a list of descriptors, not a patch object; spreading it into a row
|
|
256
|
+
compiles and writes nothing. To build a patch:
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
const patch = Object.fromEntries(entry.changes.map((c) => [c.field, c.next]));
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Saving part of the store
|
|
263
|
+
|
|
264
|
+
`onSaveDrafts` decides how much of the store is cleared:
|
|
265
|
+
|
|
266
|
+
| Returned | Effect |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| nothing | Everything saved. The store is cleared. |
|
|
269
|
+
| a rejected promise, or a throw | Nothing saved. Every draft is kept. |
|
|
270
|
+
| `{ updated, created, deleted }` | The ids reported `false` are kept; the rest are cleared. |
|
|
271
|
+
|
|
272
|
+
Each key takes `false` for the whole bucket, or a map of id to result. An id
|
|
273
|
+
the map does not name saved.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
onSaveDrafts: async ({ updated, created, deleted }) => {
|
|
277
|
+
const failed = await api.saveBatch({ updated, created, deleted });
|
|
278
|
+
return { updated: Object.fromEntries(failed.map((id) => [id, false])) };
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
A kept row stays committed rather than reopening, so the next `saveDrafts()`
|
|
283
|
+
retries it with the values it already holds. `saveDrafts()` resolves `false`
|
|
284
|
+
when anything was kept.
|
|
285
|
+
|
|
286
|
+
Nothing about a kept row is styled by the grid. It carries the same markers
|
|
287
|
+
every draft carries - see [Marking the drafts](#marking-the-drafts).
|
|
288
|
+
|
|
289
|
+
`edit.submitAll()` is the old single verb and is **deprecated**: it now does
|
|
290
|
+
`commitAll()` followed by `saveDrafts()`, which is what it always did in
|
|
291
|
+
effect. Replace it with whichever half you meant.
|
|
292
|
+
|
|
293
|
+
## Which cells edit
|
|
294
|
+
|
|
295
|
+
A column is editable when it maps to a data path: its `accessorKey`, or
|
|
296
|
+
`meta.edit.field` for a column built on `accessorFn`. Dot paths reach into
|
|
297
|
+
nested records: `accessorKey: "address.city"` edits `values.address.city`, and
|
|
298
|
+
issues from a nested schema map to the right column.
|
|
299
|
+
|
|
300
|
+
| Gate | Effect |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `editing.columns: string[]` | Only the named columns edit |
|
|
303
|
+
| `meta.edit.enabled: false` | The column never edits |
|
|
304
|
+
| `meta.edit.enabled: (row) => boolean` | Per row, per column |
|
|
305
|
+
| `editing.isRowEditable: (row) => boolean` | The whole row, in every mode |
|
|
306
|
+
|
|
307
|
+
Group rows and the generated lanes never edit.
|
|
308
|
+
|
|
309
|
+
`editing.columns` lists the column ids that take edits.
|
|
310
|
+
Unset, the default, every column mapping to a data path is editable.
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
editing: { mode: "cell", columns: ["targetPct", "note"] }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
It gates before `meta.edit`, never past it: a column left out takes no edits whatever its own meta says, and a listed column still answers to its `meta.edit.enabled`.
|
|
317
|
+
The same list decides which cells an entry row opens.
|
|
318
|
+
|
|
319
|
+
`edit.isColumnEditable(column)` asks the column's half of the question on its own, for a toolbar or a menu with no row in hand: the column maps to a field, `editing.columns` lists it when that is set, and `meta.edit.enabled` is not `false`.
|
|
320
|
+
A per-row `enabled` predicate is the row's half, and `edit.canEditCell(row, column)` asks both.
|
|
321
|
+
|
|
322
|
+
```demo
|
|
323
|
+
file: editing/EditableGating.tsx
|
|
324
|
+
hint: ID never edits · Salary is closed on Terminated rows · rows under 25 are closed entirely · Full name is computed but writes to Last name.
|
|
325
|
+
height: 440
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Draft lifetime
|
|
329
|
+
|
|
330
|
+
Forms live outside the DOM, keyed by row id. Scrolling an editing row away
|
|
331
|
+
unmounts the editor; the form keeps its values, dirty state and errors, and the
|
|
332
|
+
editor remounts over the same form when the row returns.
|
|
333
|
+
|
|
334
|
+
A cell whose row holds a draft renders the draft value through the column's
|
|
335
|
+
own `cell` renderer, in every mode - a `"cellConfirm"` draft kept on the way
|
|
336
|
+
out displays what was typed, not the value in `data`. Cell corners show the
|
|
337
|
+
state: blue for a dirty draft, red for a validation error, and the row carries
|
|
338
|
+
`data-dirty`. A red corner outlives the editor that found the error: it stands
|
|
339
|
+
until that field's value changes. An entry row's cells take the red corner,
|
|
340
|
+
and never the blue one.
|
|
341
|
+
|
|
342
|
+
The draft is displayed by the column that owns the field. A column computed
|
|
343
|
+
from other fields - `accessorFn` or `display` - reads `row.original`, which is
|
|
344
|
+
`data`, so it shows the saved record while the row is edited. To make a
|
|
345
|
+
computed cell follow the draft, read the drafted row from `edit.store`:
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
function useDraftedRow(rowId: string, original: Product): Product {
|
|
349
|
+
const { edit } = useTMDataGridContext();
|
|
350
|
+
const values = useSelector(edit.store, (state) => state.rows[rowId]?.values);
|
|
351
|
+
return (values as Product | undefined) ?? original;
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`useTMDataGridContext()` reaches the engine from inside a cell renderer, and
|
|
356
|
+
the selector re-renders the cell as the draft changes.
|
|
357
|
+
|
|
358
|
+
## Adding and deleting rows
|
|
359
|
+
|
|
360
|
+
`edit.addRow()` opens an **entry row** in a sticky block under the header, so a
|
|
361
|
+
row being typed into stays in view. Entry cells are ordinary editors over a form
|
|
362
|
+
seeded from `newRowDefaults`.
|
|
363
|
+
Enter, or the lane's ✓, commits the row: `onRowAdd` receives it, or under `draft: true` it is committed into the draft store and `saveDrafts` reports it in `created`.
|
|
364
|
+
Escape, or ✕, discards the entry.
|
|
365
|
+
Clicking away decides nothing - an entry row is row-shaped in every mode.
|
|
366
|
+
An entry row never OK'd is not part of a save; it stays open.
|
|
367
|
+
|
|
368
|
+
Under `draft: true` a committed entry row leaves the entry block and becomes a
|
|
369
|
+
body row: marked `data-new` and `data-draft`, tinted with `--dg-row-new-bg`, and
|
|
370
|
+
sorted, filtered and counted with the rest on the values it was entered with.
|
|
371
|
+
Set `newRowsSticky: true` to keep committed rows in the entry block until the
|
|
372
|
+
save instead, out of the body's sort and out of the row count. Double-click, or
|
|
373
|
+
the lane's pencil, reopens the row back into the entry block - which takes it
|
|
374
|
+
out of the draft store until it is committed again; ✕ removes it.
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
useTMDataGrid({
|
|
378
|
+
editing: {
|
|
379
|
+
mode: "row",
|
|
380
|
+
draft: true,
|
|
381
|
+
newRowDefaults: () => ({ id: 0, name: "", hired: today() }),
|
|
382
|
+
onSaveDrafts: async ({ updated, created, deleted }) => {
|
|
383
|
+
await api.saveBatch({ updated, created, deleted });
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
<Button onClick={() => grid.edit.addRow()}>Add row</Button>;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Annotate `newRowDefaults`' return type: a bare object literal widens a union
|
|
392
|
+
field to `string`, and `(): Product => ({ ... })` keeps it checked.
|
|
393
|
+
|
|
394
|
+
`addRow` takes the values the row starts from. They override `newRowDefaults`
|
|
395
|
+
key by key, so `addRow()` opens the `newRowDefaults` row and
|
|
396
|
+
`addRow({ department: "Sales" })` opens that row with `department` filled in.
|
|
397
|
+
Passing a whole row duplicates it. The entry row is an ordinary form either way:
|
|
398
|
+
the seeded values are editable, validate like any other, and nothing reaches
|
|
399
|
+
`onRowAdd` until the row is committed.
|
|
400
|
+
|
|
401
|
+
A grouped column has no cell on the entry row - under the default
|
|
402
|
+
`groupedColumnMode: "remove"` it is not in the grid at all - so an entry row
|
|
403
|
+
cannot type the grouped field. Seed it: `addRow({ region: "EMEA" })`.
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
<Button onClick={() => grid.edit.addRow({ department: "Sales", active: true })}>
|
|
407
|
+
Add to Sales
|
|
408
|
+
</Button>;
|
|
409
|
+
|
|
410
|
+
<Button onClick={() => grid.edit.addRow(selected.original)}>Duplicate</Button>;
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
To limit how many entry rows are open at once, read the entry state off
|
|
414
|
+
`edit.store` and gate the button:
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
const hasOpenEntry = useSelector(grid.edit.store, (state) =>
|
|
418
|
+
state.newRows.some((newRow) => !newRow.committed),
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
<Button disabled={hasOpenEntry} onClick={() => grid.edit.addRow()}>
|
|
422
|
+
Add row
|
|
423
|
+
</Button>;
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Importing rows
|
|
427
|
+
|
|
428
|
+
`edit.addRows(rows)` opens a batch of entry rows in one write, where a loop
|
|
429
|
+
over `addRow` is one write per row. Each row is seeded over `newRowDefaults`
|
|
430
|
+
exactly as `addRow` is.
|
|
431
|
+
|
|
432
|
+
`{ commit: true }` submits the rows too, which is what an import wants: rows
|
|
433
|
+
that validate are committed, and rows that fail stay open in the entry block
|
|
434
|
+
carrying their errors, for the user to fix. The result says which went which
|
|
435
|
+
way, so the file's bad rows can be reported before anything is saved.
|
|
436
|
+
|
|
437
|
+
Under `draft: true` the whole import is one publish: the rows are validated
|
|
438
|
+
together and land in the draft store in the same render that shows them, so
|
|
439
|
+
ten thousand rows take about a second, and the grid renders once rather than
|
|
440
|
+
once per row. `saveDrafts` sends them the same way. A committed row is held
|
|
441
|
+
as plain values, not as a form.
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
const { committed, open } = await grid.edit.addRows(parsedRows, {
|
|
445
|
+
commit: true,
|
|
446
|
+
});
|
|
447
|
+
if (open.length > 0) notify(`${open.length} rows need attention`);
|
|
448
|
+
await grid.edit.saveDrafts();
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Column rules are enforced here even though the rows never had an editor on
|
|
452
|
+
screen: the engine runs `meta.edit.validate` itself at commit, so an imported
|
|
453
|
+
row is held to the same rules as a typed one. Without `draft: true` there is
|
|
454
|
+
no store to commit into, so `commit: true` adds each valid row through `onRowAdd`
|
|
455
|
+
- one call per row, in the order given.
|
|
456
|
+
|
|
457
|
+
```demo
|
|
458
|
+
file: editing/ImportRows.tsx
|
|
459
|
+
hint: Import parses the pasted rows, commits the valid ones and leaves the rest open with their errors. The second button imports ten thousand generated rows, twenty of them invalid.
|
|
460
|
+
height: 460
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`edit.deleteRow(rowId)` calls `onRowDelete({ rowId, row })` immediately; put
|
|
464
|
+
any confirmation in that callback. Under `draft: true` it marks the row
|
|
465
|
+
instead: the row renders struck through and inert
|
|
466
|
+
(`data-deleted`), the lane shows Restore, and `saveDrafts` reports the ids in
|
|
467
|
+
`deleted`. A deletion mark is a decision the moment it is made, so it goes
|
|
468
|
+
straight into the draft store - there is nothing to type. The mark is
|
|
469
|
+
idempotent - deleting a marked row again leaves it marked - and
|
|
470
|
+
`edit.restoreRow(rowId)` is the undo, which is what the lane's Restore calls.
|
|
471
|
+
On an entry row, committed or not, `deleteRow` just discards the entry, and an
|
|
472
|
+
id the grid does not know is a no-op. `edit.deleteRows(rowIds)` is the same
|
|
473
|
+
over a list in one call, for a bulk action: because each id marks
|
|
474
|
+
idempotently, discards an entry row or does nothing, the list may be passed
|
|
475
|
+
exactly as a selection stands - duplicates, already-marked rows and stale ids
|
|
476
|
+
included. The trash can shows when the deletion has somewhere to report to:
|
|
477
|
+
`onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is.
|
|
478
|
+
|
|
479
|
+
A marked row is read-only and not selectable until it is restored: `begin`,
|
|
480
|
+
`setCellValue`, `setRowValues` and `clearCell` refuse it, the keyboard cannot
|
|
481
|
+
open an editor on it, its checkbox is disabled, select-all skips it, and the
|
|
482
|
+
mark drops it from `rowSelection`. An editor open on the row when it is marked
|
|
483
|
+
is cancelled. A committed edit stays under the mark, so Restore brings the row
|
|
484
|
+
back as edited; Save leaves that edit out of `updated` - the row is in
|
|
485
|
+
`deleted` only - and forgets it once the deletion is saved. A marked row still
|
|
486
|
+
sorts, filters, groups, aggregates and counts, and is left out of an export.
|
|
487
|
+
|
|
488
|
+
A row the engine takes out of the table - an entry row that is discarded or
|
|
489
|
+
saved, a marked row once its deletion is saved - leaves `rowSelection`,
|
|
490
|
+
`expanded` and `rowPinning` with it. TanStack itself never drops an id from
|
|
491
|
+
those maps, so without this a deleted row would keep the select-all box
|
|
492
|
+
indeterminate and count as selected for good.
|
|
493
|
+
|
|
494
|
+
The grid still never mutates `data`: you apply adds and deletes, and the new
|
|
495
|
+
rows arrive back through `data`. The engine's `tempId` (`__new__1`, …) does not
|
|
496
|
+
need to become a real id; assign one when you create the record.
|
|
497
|
+
|
|
498
|
+
## The engine: `edit`
|
|
499
|
+
|
|
500
|
+
The built-in controls do everything through `edit`, which is public.
|
|
501
|
+
|
|
502
|
+
| Member | Does |
|
|
503
|
+
| --- | --- |
|
|
504
|
+
| `edit.begin({ rowId, columnId })` | Opens a row into form state. On a committed row, takes it back out of the draft store |
|
|
505
|
+
| `edit.commit(rowId)` | Submits one row: into the draft store under `draft: true`, to `onCommit` otherwise. Resolves `false` if validation blocked it |
|
|
506
|
+
| `edit.commitAll()` | Submits every open row. Resolves `false` when one stayed open |
|
|
507
|
+
| `edit.saveDrafts()` | Sends the draft store. Open rows are left alone |
|
|
508
|
+
| `edit.submitAll()` | **Deprecated** - `commitAll()` then `saveDrafts()` |
|
|
509
|
+
| `edit.cancel(rowId)` / `edit.cancelAll()` | Drops drafts - form state and the draft store alike |
|
|
510
|
+
| `edit.setCellValue(rowId, columnId, value)` | Writes one cell and commits the row, with no editor. Resolves `false` if the cell takes no edit, or validation refused the value |
|
|
511
|
+
| `edit.setRowValues(rowId, values)` | The same for several cells of one row, in one commit. All or nothing |
|
|
512
|
+
| `edit.clearCell(rowId, columnId)` | Writes the type's empty value and commits it - what Delete does |
|
|
513
|
+
| `edit.addRow(values?)` | Opens one entry row, seeded over `newRowDefaults` |
|
|
514
|
+
| `edit.addRows(rows, options?)` | Opens a batch; `{ commit: true }` submits the rows too - one publish for the lot under `draft: true` |
|
|
515
|
+
| `edit.deleteRow(rowId)` | Deletes a row, or marks it deleted under `draft: true`. Idempotent; discards an entry row; ignores an unknown id |
|
|
516
|
+
| `edit.deleteRows(rowIds)` | `deleteRow` over a list in one call - safe to feed a selection as it stands |
|
|
517
|
+
| `edit.restoreRow(rowId)` | Removes a row's deletion mark - what the lane's Restore calls |
|
|
518
|
+
| `edit.isColumnEditable(column)` | Whether a column takes edits at all, with no row in hand |
|
|
519
|
+
| `edit.getForm(rowId)` | The open row's live `FormApi`; `undefined` for a committed row |
|
|
520
|
+
| `edit.getRowValues(rowId)` | The row as shown: its draft where one is held, else the `data` value. `undefined` for an unknown row |
|
|
521
|
+
| `edit.getRows()` | Every row as shown - drafts overlaid, entry rows appended, deletion-marked rows included and flagged `deleted` |
|
|
522
|
+
| `edit.store` | Open rows, committed rows, active cell, dirty and error projections, draft values, the committed values the table shows (`committedValues`), entry rows, deletion marks |
|
|
523
|
+
|
|
524
|
+
`commit`, `commitAll`, `saveDrafts`, `setCellValue`, `setRowValues`,
|
|
525
|
+
`clearCell` and `addRows` return promises. Await each call before starting the
|
|
526
|
+
next when driving edits in a loop.
|
|
527
|
+
|
|
528
|
+
`getForm` returns the open row's own `FormApi`. Render it in a drawer or side
|
|
529
|
+
panel and it shares values, dirty state and errors with the inline cells.
|
|
530
|
+
A committed row has no form, so `getForm` returns `undefined` for it: call `begin` first, which reopens the row with a form seeded from the committed values.
|
|
531
|
+
|
|
532
|
+
`getRowValues` and `getRows` read what the grid shows rather than what `data` holds: an open form's values, a committed draft, or the `data` value when neither exists.
|
|
533
|
+
`getRows` walks the core row model, so it is unfiltered and never contains group rows, and it filters nothing out - a row marked deleted comes back flagged `deleted`, an entry row flagged `isNew` under its temp id.
|
|
534
|
+
The order is the core row model's, committed new rows ahead of the `data` rows, and then the entry rows the table does not hold: the ones still being typed into, and the committed ones under `newRowsSticky`.
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
const selected = grid.table
|
|
538
|
+
.getSelectedRowModel()
|
|
539
|
+
.rows.flatMap((row) => grid.edit.getRowValues(row.id) ?? []);
|
|
540
|
+
|
|
541
|
+
const surviving = grid.edit.getRows().filter((row) => !row.deleted);
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
For the inverse, a `@tanstack/react-form` form _around_ the grid holding the row
|
|
545
|
+
array, see [A query builder form](/docs/query-builder).
|
|
546
|
+
|
|
547
|
+
### Bulk actions
|
|
548
|
+
|
|
549
|
+
`edit.setCellValue(rowId, columnId, value)` writes one cell and commits its row without an editor ever opening: a typed edit without the typing, for a toolbar action or a bulk fill.
|
|
550
|
+
The row need not be mounted, so a selected row inside a collapsed group takes the write like any other.
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
for (const row of grid.table.getSelectedRowModel().rows) {
|
|
554
|
+
await grid.edit.setCellValue(row.id, "targetPct", equalWeight(row.original));
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Under `draft: true` each row is committed into the draft store like any hand-made edit, so the whole basket saves at once through `edit.saveDrafts()`, carries the same change markers, and is reverted row by row from the edit lane.
|
|
559
|
+
|
|
560
|
+
`edit.setRowValues(rowId, values)` does the same for several cells of one row in a single commit: one `onCommit` call and one draft entry rather than one per column.
|
|
561
|
+
Keys are column ids, and it is all or nothing - if any named cell takes no edit, nothing is written and it resolves `false`.
|
|
562
|
+
|
|
563
|
+
```tsx
|
|
564
|
+
await grid.edit.setRowValues(row.id, { status: "Closed", closedOn: today() });
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
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.
|
|
568
|
+
`value` is the stored value: no editor runs, so `meta.edit.mapValue` does not run either, while `meta.edit.validate` does.
|
|
569
|
+
|
|
570
|
+
## Reference
|
|
571
|
+
|
|
572
|
+
| Name | Kind | Type | Default | What it does |
|
|
573
|
+
| ----------------------------- | -------------- | ------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------------ |
|
|
574
|
+
| `editing` | Option | `TMDataGridEditingOptions` | – | Turns editing on. One object holding both axes and every editing callback. |
|
|
575
|
+
| `editing.mode` | Member | `"cell" \| "cellConfirm" \| "row"` | – | Picks what counts as a commit and which controls trigger it. |
|
|
576
|
+
| `editing.draft` | Member | `boolean` | `false` | Holds commits in the draft store for `edit.saveDrafts()` instead of sending them out. |
|
|
577
|
+
| `getRowId` | Table option | `(row) => string` | – | Required once `editing` is set. Drafts are keyed by it. |
|
|
578
|
+
| `editing.columns` | Member | `ReadonlyArray<string>` | Every mapped column | The column ids that take edits. Gates before `meta.edit`, never past it. |
|
|
579
|
+
| `editing.isRowEditable` | Member | `(row) => boolean` | – | Closes a whole row to editing. |
|
|
580
|
+
| `editing.rowValidators` | Member | TanStack Form validators | – | Form-level rules for the whole editing row. See [Editors](/docs/editors). |
|
|
581
|
+
| `editing.tableValidators` | Member | `TMDataGridTableValidators` | – | Cross-row rules, handed the collection with every draft overlaid. See [Editors](/docs/editors#cross-row-rules). |
|
|
582
|
+
| `editing.onCommit` | Callback | `({ rowId, value, original, changes, source }) => void \| Promise` | – | Applies one row's change. Reject to keep the draft. |
|
|
583
|
+
| `editing.onSaveDrafts` | Callback | `({ updated, created, deleted }) => void \| Result \| Promise` | – | `draft: true` only. One call for the whole draft store. See [Saving part of the store](#saving-part-of-the-store). |
|
|
584
|
+
| `editing.onCommitDrafts` | Callback | `({ updated, created, deleted }) => void \| Result \| Promise` | – | **Deprecated** - renamed to `onSaveDrafts`. Still honoured. |
|
|
585
|
+
| `editing.newRowsSticky` | Member | `boolean` | `false` | `draft: true` only. Keeps committed entry rows in the sticky entry block, out of the body's sort, until the save. |
|
|
586
|
+
| `editing.newRowDefaults` | Member | `TData \| () => TData` | – | Seeds the entry row's form. |
|
|
587
|
+
| `editing.onRowAdd` | Callback | `({ tempId, value }) => void \| Promise` | – | Commits an added row. |
|
|
588
|
+
| `editing.onRowDelete` | Callback | `({ rowId, row }) => void \| Promise` | – | Deletes a row. Shows the trash; under `draft: true`, `onSaveDrafts` shows it too. |
|
|
589
|
+
| `meta.edit.enabled` | Column meta | `boolean \| (row) => boolean` | `true` | Whether a column's cells edit. |
|
|
590
|
+
| `meta.edit.field` | Column meta | `string` | The `accessorKey` | The data path an edit writes to. |
|
|
591
|
+
| `meta.edit.mapValue` | Column meta | `({ value, previous, row, column }) => unknown` | – | Maps each value an editor writes. See [Editors](/docs/editors#mapping-the-value-as-it-is-typed). |
|
|
592
|
+
| `EDIT_COLUMN_ID` | Export | `"__edit__"` | – | Id of the generated edit lane. |
|
|
593
|
+
| `TMDataGrid.DraftActions` | Component | – | – | Save and Discard for pending edits. |
|
|
594
|
+
| `DraftActions` `renderActions` | Slot | `({ state, actions, Controls }) => ReactNode` | Built-in pair | Replaces the buttons, and hands over their pieces. See [Components](/docs/components#tmdatagriddraftactions). |
|
|
595
|
+
| `actions.scrollToFirstOpenRow` | Slot action | `(align?) => boolean` | `align: "auto"` | Scrolls to the first open row in display order. `false` when none could be reached. |
|
|
596
|
+
| `clearedValueForType` | Export | `(type) => unknown` | – | What Delete writes for each column type. |
|
|
597
|
+
| `--dg-entry-height` | CSS variable | length | From `size` | Height of the sticky entry block. |
|
|
598
|
+
| `--dg-row-new-bg` | CSS variable | color | Green tint | Background of a committed new row, in the body or the entry block. |
|
|
599
|
+
| `data-deleted` | Data attribute | – | – | On a row marked for deletion under `draft: true`. |
|
|
600
|
+
| `data-dirty` | Data attribute | – | – | On a body row holding a dirty draft. |
|
|
601
|
+
| `data-draft` | Data attribute | – | – | On a body row or entry row committed into the draft store, waiting for a save. |
|
|
602
|
+
| `data-new` | Data attribute | – | – | On a body row that is a committed new row, and on an entry row. |
|
|
603
|
+
| `data-committed` | Data attribute | – | – | On an entry row once it is committed, awaiting the save. Seen only under `newRowsSticky`. |
|