@jielga/tmdatagrid 2.0.0-beta.21 → 2.0.0-beta.22
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/dist/index.js +83 -81
- package/dist/index.js.map +1 -1
- package/docs/cell-selection.md +1 -1
- package/docs/editing.md +321 -336
- package/docs/grouping.md +1 -1
- package/docs/styling.md +1 -1
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +1 -1
- package/skills/cell-selection/SKILL.md +1 -1
- package/skills/columns/SKILL.md +1 -1
- package/skills/data/SKILL.md +1 -1
- package/skills/editing/SKILL.md +4 -3
- package/skills/editing/references/common-mistakes.md +11 -12
- package/skills/editing/references/editing-api.md +2 -0
- package/skills/filtering/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +1 -1
- package/skills/grouping/SKILL.md +1 -1
- package/skills/options/SKILL.md +1 -1
- package/skills/rows/SKILL.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/src/components/TMDataGridEntryRows.tsx +46 -21
package/docs/editing.md
CHANGED
|
@@ -2,21 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
`@tanstack/react-form` becomes a peer dependency once editing is used.
|
|
4
4
|
|
|
5
|
-
Editing is
|
|
6
|
-
|
|
7
|
-
The grid never mutates `data`, so you apply the commit and the new values
|
|
8
|
-
arrive back through `data`.
|
|
5
|
+
Editing is turned on with the `editing` option.
|
|
6
|
+
Two settings inside it work independently:
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
- `mode` - what counts as a commit: leaving the cell, confirming the cell, or saving the row
|
|
9
|
+
- `draft` - where a commit goes: to your `onCommit` callback at once, or into the grid's draft store until the user presses Save
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
The grid never modifies `data`.
|
|
12
|
+
You apply each commit in your callback, and the new values arrive back through `data`.
|
|
13
|
+
|
|
14
|
+
A row is in one of three places:
|
|
15
|
+
|
|
16
|
+
| Place | What it holds | Enters by | Leaves by |
|
|
13
17
|
| --- | --- | --- | --- |
|
|
14
18
|
| **data** | Your rows, the only source of truth | - | - |
|
|
15
|
-
| **form state** | A row being edited
|
|
16
|
-
| **draft store** | Rows that passed their commit, held as values
|
|
19
|
+
| **form state** | A row being edited, in its own TanStack Form | `edit.begin`, `edit.addRow` | `edit.commit`, `edit.cancel` |
|
|
20
|
+
| **draft store** | Rows that passed their commit, held as values inside the grid until Save | `edit.commit` under `draft: true` | `edit.saveDrafts`, `edit.cancel` |
|
|
21
|
+
|
|
22
|
+
A row is **open** while it is in form state, and **committed** once it is in the draft store.
|
|
23
|
+
Without `draft: true` a commit goes straight to `onCommit` and the form is dropped, so the draft store stays empty.
|
|
24
|
+
|
|
25
|
+
## Set up editing
|
|
17
26
|
|
|
18
|
-
|
|
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.
|
|
27
|
+
The smallest setup is cell mode with an `onCommit` that applies each change:
|
|
20
28
|
|
|
21
29
|
```tsx
|
|
22
30
|
const grid = useTMDataGrid({
|
|
@@ -33,35 +41,34 @@ const grid = useTMDataGrid({
|
|
|
33
41
|
});
|
|
34
42
|
```
|
|
35
43
|
|
|
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.
|
|
44
|
+
`onCommit` receives `{ rowId, value, original, changes, source }`:
|
|
40
45
|
|
|
41
|
-
`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`saveDrafts` falls back to the per-row `onCommit` loop.
|
|
46
|
+
- `value` - the whole row as edited
|
|
47
|
+
- `original` - the row as editing began
|
|
48
|
+
- `changes` - the per-field diff, `Array<{ columnId, field, previous, next }>`, one entry in cell mode
|
|
49
|
+
- `source` - the `mode` the commit came from
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
`editing` requires `getRowId`: drafts are keyed by row id, and the index fallback would name a different record after a sort.
|
|
52
|
+
`onSaveDrafts` is accepted only under `draft: true`.
|
|
53
|
+
Both are compile errors, not options that silently do nothing.
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
The `editing` object can be written inline.
|
|
56
|
+
Its callbacks are read through a ref on every render, so its identity does not matter.
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
Editing turns on cell selection: `cellSelection` defaults to `"single"` while `editing` is set.
|
|
59
|
+
|
|
60
|
+
## Modes
|
|
61
|
+
|
|
62
|
+
`editing.mode` sets what counts as a commit and which controls trigger it.
|
|
63
|
+
All three modes use the same engine and the same forms.
|
|
54
64
|
|
|
55
65
|
| Mode | Commit | Cancel | Controls |
|
|
56
66
|
| --------------- | ------------------------------------------ | ----------------- | ------------------------ |
|
|
57
67
|
| `"cell"` | Enter, Tab or leaving the cell | Escape | none |
|
|
58
68
|
| `"cellConfirm"` | ✓ or Enter; Tab walks input, ✓, ✕ and then leaves, keeping the draft | ✕ or Escape | ✓ / ✕ beside the input |
|
|
59
|
-
| `"row"` |
|
|
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.
|
|
69
|
+
| `"row"` | ✓ in the edit lane, or Enter | ✕, or Escape | generated edit lane |
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
A refused commit keeps the editor open, invalid, with the message in its tooltip, until the value is fixed or Escape drops it.
|
|
71
|
+
An entry row from `edit.addRow()` is row-shaped in every mode: every editable cell opens at once, Tab walks them, and Enter, or the lane's ✓, commits it.
|
|
65
72
|
|
|
66
73
|
```demo
|
|
67
74
|
file: editing/CellEditing.tsx
|
|
@@ -69,28 +76,24 @@ hint: Double-click a cell, or press Enter or F2, or start typing on it.
|
|
|
69
76
|
height: 440
|
|
70
77
|
```
|
|
71
78
|
|
|
72
|
-
|
|
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.
|
|
79
|
+
### Opening an editor
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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.
|
|
81
|
+
An editor opens on double-click, or, with the cell cursor on the cell, on Enter, F2 or typing, where the first character replaces the value as in a spreadsheet.
|
|
82
|
+
The grid places the caret in the opened cell, so a `meta.edit.editor` receives focus without handling it itself.
|
|
83
|
+
A row added with `edit.addRow()` opens the same way, with the caret in its first editable cell.
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Delete or Backspace clears the value and commits it without opening an editor.
|
|
86
|
+
The commit is validated: a column rule that rejects the empty value refuses the clear and marks the cell.
|
|
87
|
+
Under `draft: true` the cleared value goes into the draft store with the rest.
|
|
88
|
+
|
|
89
|
+
A commit that fails validation keeps the editor open and marked invalid, with the message in its tooltip, until the value is fixed or Escape drops the edit.
|
|
86
90
|
|
|
87
|
-
|
|
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
|
+
### Row editing
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
row
|
|
93
|
+
The pencil in the edit lane opens every cell of the row at once, and ✓ commits them as one commit.
|
|
94
|
+
Double-clicking a cell opens the whole row, with the caret in the clicked cell.
|
|
95
|
+
Cross-field rules belong in this mode, because the whole row is validated together.
|
|
96
|
+
Opening a second row leaves the first one open, and each row's ✓ and ✕ act on that row alone.
|
|
94
97
|
|
|
95
98
|
```demo
|
|
96
99
|
file: editing/RowEditing.tsx
|
|
@@ -100,115 +103,120 @@ height: 440
|
|
|
100
103
|
|
|
101
104
|
## The draft store
|
|
102
105
|
|
|
103
|
-
`draft: true` changes where a commit goes, and nothing else
|
|
104
|
-
|
|
106
|
+
`draft: true` changes where a commit goes, and nothing else.
|
|
107
|
+
Instead of reaching `onCommit`, the row is committed into the grid's draft store, where it waits for `edit.saveDrafts()`.
|
|
108
|
+
The mode still decides what counts as a commit: `{ mode: "row", draft: true }` commits a whole row from the lane's ✓, and `{ mode: "cell", draft: true }` commits a row as the caret leaves the cell.
|
|
109
|
+
|
|
110
|
+
`draft: true` requires `getRowId`.
|
|
111
|
+
The usual setup adds `onSaveDrafts`, which receives the whole store in one call when the user presses Save, and `TMDataGrid.DraftActions` in the toolbar, which renders Save and Discard:
|
|
105
112
|
|
|
106
113
|
```tsx
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
const grid = useTMDataGrid({
|
|
115
|
+
data,
|
|
116
|
+
columns,
|
|
117
|
+
getRowId: (row) => String(row.id),
|
|
118
|
+
editing: {
|
|
119
|
+
mode: "row",
|
|
120
|
+
draft: true,
|
|
121
|
+
onSaveDrafts: async ({ updated, created, deleted }) => {
|
|
122
|
+
await api.saveBatch({ updated, created, deleted });
|
|
123
|
+
},
|
|
112
124
|
},
|
|
113
|
-
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
<TMDataGrid {...grid}>
|
|
128
|
+
<TMDataGrid.Toolbar>
|
|
129
|
+
<TMDataGrid.DraftActions />
|
|
130
|
+
</TMDataGrid.Toolbar>
|
|
131
|
+
<TMDataGrid.Table />
|
|
132
|
+
</TMDataGrid>;
|
|
114
133
|
```
|
|
115
134
|
|
|
116
|
-
|
|
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.
|
|
135
|
+
In this demo:
|
|
119
136
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
A row reopened for a further edit keeps its place until it commits again or is cancelled.
|
|
137
|
+
- ✓ commits the row into the draft store, and the Backend panel logs nothing
|
|
138
|
+
- Save sends the whole store as one `onSaveDrafts` call
|
|
139
|
+
- with "Reject Sales rows" on, the backend refuses those rows and they keep their drafts
|
|
140
|
+
- "Go to open row" scrolls to a row left open
|
|
125
141
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
142
|
+
```demo
|
|
143
|
+
file: editing/DraftEditing.tsx
|
|
144
|
+
hint: Double-click a row, ✓ commits it, Save sends the store.
|
|
145
|
+
height: 440
|
|
146
|
+
```
|
|
134
147
|
|
|
135
|
-
|
|
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.
|
|
148
|
+
### Saving the store
|
|
140
149
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
While an editor is open, a field's own message shows in a tooltip on it, opened by focus and by hover.
|
|
150
|
+
`edit.saveDrafts()` sends the draft store and leaves open rows alone.
|
|
151
|
+
With `onSaveDrafts` set, it makes one call with the whole store, `onSaveDrafts({ updated, created, deleted })`:
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
- `updated` - one entry per committed edit, in the shape `onCommit` receives: `{ rowId, value, original, changes, source }`
|
|
154
|
+
- `created` - one `{ tempId, value }` per committed new row
|
|
155
|
+
- `deleted` - the ids of the rows marked for deletion
|
|
146
156
|
|
|
147
|
-
|
|
157
|
+
Without `onSaveDrafts`, `saveDrafts` makes one call per row instead: `onCommit` for each edit, `onRowAdd` for each new row and `onRowDelete` for each deletion.
|
|
158
|
+
`draft: true` without `onSaveDrafts` is therefore valid.
|
|
148
159
|
|
|
149
|
-
|
|
160
|
+
`changes` is a list of descriptors, not a patch object; spreading it into a row compiles and writes nothing.
|
|
161
|
+
To build a patch:
|
|
150
162
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- an open row - whatever the mode offers: Save and Cancel
|
|
163
|
+
```tsx
|
|
164
|
+
const patch = Object.fromEntries(entry.changes.map((c) => [c.field, c.next]));
|
|
165
|
+
```
|
|
155
166
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
Before the rows are sent, `editing.tableValidators` run once more over every committed row.
|
|
168
|
+
Column rules and `rowValidators` ran at commit on the same values, so they do not run again.
|
|
169
|
+
A row the table rules reject is reopened with its errors and left out of the save.
|
|
170
|
+
On the per-row path, a row whose `onCommit` or `onRowAdd` rejects is reopened the same way.
|
|
160
171
|
|
|
161
|
-
|
|
172
|
+
`TMDataGrid.DraftActions` renders the whole-grid controls: Save with the count of rows in the store, Discard, and a note counting the rows still open.
|
|
173
|
+
Save is disabled while the store is empty, however much is being typed, and shows a loading state while the save is in flight.
|
|
174
|
+
The toolbar is declarative: include the component yourself when the grid runs a draft store, or call `edit.saveDrafts()` from a control of your own.
|
|
175
|
+
Without `draft: true` there is nothing to save and Save stays disabled.
|
|
162
176
|
|
|
163
|
-
|
|
177
|
+
### Saving part of the store
|
|
164
178
|
|
|
165
|
-
|
|
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) |
|
|
179
|
+
`onSaveDrafts` decides how much of the store is cleared by what it returns:
|
|
171
180
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
| Returned | Effect |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| nothing | Everything saved. The store is cleared. |
|
|
184
|
+
| a rejected promise, or a throw | Nothing saved. Every draft is kept. |
|
|
185
|
+
| `{ updated, created, deleted }` | The ids reported `false` are kept; the rest are cleared. |
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
`rowStyle` on the Table:
|
|
187
|
+
Each key takes `false` for the whole bucket, or a map from id to result.
|
|
188
|
+
An id the map does not name counts as saved.
|
|
180
189
|
|
|
181
190
|
```tsx
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
: undefined
|
|
187
|
-
}
|
|
188
|
-
/>
|
|
191
|
+
onSaveDrafts: async ({ updated, created, deleted }) => {
|
|
192
|
+
const failed = await api.saveBatch({ updated, created, deleted });
|
|
193
|
+
return { updated: Object.fromEntries(failed.map((id) => [id, false])) };
|
|
194
|
+
};
|
|
189
195
|
```
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
`
|
|
197
|
+
A kept row stays committed rather than reopening, so the next `saveDrafts()` retries it with the values it already holds.
|
|
198
|
+
`saveDrafts()` resolves `false` when anything was kept.
|
|
199
|
+
A kept row carries the same markers as every other draft and nothing more; see [Styling pending rows](#styling-pending-rows).
|
|
200
|
+
|
|
201
|
+
### Rows left open
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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.
|
|
203
|
+
A row left open is neither lost nor sent.
|
|
204
|
+
It keeps everything typed into it, stays open across a save, and joins the next save once it is committed.
|
|
205
|
+
`edit.commitAll()` submits every open row at once; rows that fail validation stay open with their errors.
|
|
206
|
+
"Commit everything, then save" is `commitAll()` followed by `saveDrafts()`.
|
|
207
|
+
`edit.submitAll()` did both in one call and is **deprecated**; replace it with the half you meant.
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
the
|
|
204
|
-
actions, and `Controls.Save` / `Controls.Discard` / `Controls.OpenRowsNote` as
|
|
205
|
-
the built-in pieces.
|
|
209
|
+
The note beside Save counts the open rows.
|
|
210
|
+
On a long grid the open row may be far from the viewport, and because the grid is [virtualized](/docs/scrolling) it may have no element to scroll to.
|
|
211
|
+
`actions.scrollToFirstOpenRow(align?)` scrolls to the first open row in display order and returns whether it could be reached.
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
`actions
|
|
211
|
-
|
|
213
|
+
`renderActions` replaces the built-in pair and receives its pieces:
|
|
214
|
+
|
|
215
|
+
- `state` - `draftCount`, `openCount`, `openRowIds`, `isSubmitting` and `isSaving`
|
|
216
|
+
- `actions` - `save`, `commitAll`, `discard`, `scrollToRow` and `scrollToFirstOpenRow`
|
|
217
|
+
- `Controls` - `Save`, `Discard` and `OpenRowsNote`, the built-in pieces
|
|
218
|
+
|
|
219
|
+
The full list is on [Components](/docs/components#tmdatagriddraftactions).
|
|
212
220
|
|
|
213
221
|
```tsx
|
|
214
222
|
<TMDataGrid.DraftActions
|
|
@@ -231,147 +239,64 @@ whether it could be reached.
|
|
|
231
239
|
/>
|
|
232
240
|
```
|
|
233
241
|
|
|
234
|
-
`state.openRowIds`
|
|
235
|
-
|
|
236
|
-
|
|
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.
|
|
242
|
+
`state.openRowIds` lists the open rows in the order the grid opened them, for a control the grid does not offer, such as a list or a next-open-row cycle.
|
|
243
|
+
`scrollToFirstOpenRow` takes "first" in display order, so the two need not name the same row.
|
|
244
|
+
An entered row appears as its `tempId`; entry rows are always on screen in the entry block, so the scroll returns `true` without moving.
|
|
299
245
|
|
|
300
|
-
|
|
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 |
|
|
246
|
+
### How a committed row behaves
|
|
306
247
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`editing.columns` lists the column ids that take edits.
|
|
310
|
-
Unset, the default, every column mapping to a data path is editable.
|
|
248
|
+
A committed row holds no form: its values are data in the draft store.
|
|
249
|
+
To the table it is a row like any other, and everything that reads a row reads the draft:
|
|
311
250
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
251
|
+
- sorting, filtering, quick search and grouping
|
|
252
|
+
- `aggregatedCell`, `footer` and the faceted filter options
|
|
253
|
+
- export, row selection, the row numbers and counts
|
|
254
|
+
- `edit.getRows()` and `editing.tableValidators`
|
|
255
|
+
- the row callbacks `onRowClick`, `renderRowContextMenu`, `renderDetails`, `isRowEditable`, `meta.edit.enabled`, `rowClassName`, `rowStyle` and `enableRowPinning`, which receive a row whose `original` is the committed draft; an entered row is a record under its temp id, with no server id
|
|
315
256
|
|
|
316
|
-
|
|
317
|
-
|
|
257
|
+
`data` itself is never modified, and `getRowCount()` with a `rowCount` you set does not grow.
|
|
258
|
+
Only top-level rows are overlaid; children reached through `getSubRows` keep their `data` values.
|
|
259
|
+
A committed row that stops matching a filter or the quick search leaves the view, and Save still counts it.
|
|
318
260
|
|
|
319
|
-
|
|
320
|
-
|
|
261
|
+
Reopening a committed row, through `begin` or a write with `setCellValue`, `setRowValues` or `clearCell`, builds a fresh form seeded from the committed values and takes the row out of the store until it commits again.
|
|
262
|
+
The row keeps its place in the sort until it commits again or is cancelled.
|
|
321
263
|
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
```
|
|
264
|
+
A commit moves nothing else: the page stays, and open details panels and groups stay open.
|
|
265
|
+
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`.
|
|
327
266
|
|
|
328
|
-
|
|
267
|
+
A refetch that no longer returns a row drops that row's draft, its open editor and its deletion mark.
|
|
268
|
+
Under `manualPagination` or `manualFiltering` a row missing from `data` is on another page, not gone, so its draft is kept until Save.
|
|
329
269
|
|
|
330
|
-
|
|
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.
|
|
270
|
+
## Edit lane
|
|
333
271
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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.
|
|
272
|
+
The edit lane is the generated column at the end of every row, with the id `EDIT_COLUMN_ID`.
|
|
273
|
+
The grid adds it under `mode: "row"`, under `draft: true`, or when `onRowDelete` is set.
|
|
274
|
+
What it shows depends on the row's state:
|
|
341
275
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
276
|
+
| Row state | Icon | Actions |
|
|
277
|
+
| --- | --- | --- |
|
|
278
|
+
| Open | - | The mode's controls: ✓ ("Save row") commits the row, ✕ ("Cancel edit") cancels the edit |
|
|
279
|
+
| Committed edit | Pencil | Revert drops the row's draft |
|
|
280
|
+
| Committed new row | Plus | Pencil reopens it, ✕ removes it |
|
|
281
|
+
| Marked for deletion | Trash | Restore removes the mark |
|
|
346
282
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const values = useSelector(edit.store, (state) => state.rows[rowId]?.values);
|
|
351
|
-
return (values as Product | undefined) ?? original;
|
|
352
|
-
}
|
|
353
|
-
```
|
|
283
|
+
A committed row is never offered ✓ again; `TMDataGrid.DraftActions` sends it.
|
|
284
|
+
A committed row also hides the trash: revert first, then delete.
|
|
285
|
+
The trash shows when a deletion has somewhere to go: `onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is.
|
|
354
286
|
|
|
355
|
-
|
|
356
|
-
|
|
287
|
+
If validation blocks a row, its icon turns red and the tooltip shows the message; this covers the open row's ✓ and an entry row's ✓ alike.
|
|
288
|
+
A pathless issue from `rowValidators` has no cell to land on, so that tooltip is where its message shows.
|
|
357
289
|
|
|
358
|
-
## Adding
|
|
290
|
+
## Adding rows
|
|
359
291
|
|
|
360
|
-
`edit.addRow()` opens an **entry row** in a sticky block under the header, so
|
|
361
|
-
|
|
362
|
-
|
|
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.
|
|
292
|
+
`edit.addRow()` opens an **entry row** in a sticky block under the header, so the row being typed into stays in view.
|
|
293
|
+
Its cells are ordinary editors over a form seeded from `newRowDefaults`.
|
|
294
|
+
A cell the row does not open, a display column or one with `meta.edit.enabled: false`, renders through the column's own `cell` renderer over the row as shown, following the form as it is typed, as it does on a body row.
|
|
367
295
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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.
|
|
296
|
+
- Enter, or the lane's ✓, commits the row: `onRowAdd` receives it, or under `draft: true` it goes into the draft store and `saveDrafts` reports it in `created`
|
|
297
|
+
- Escape, or ✕, discards it
|
|
298
|
+
- clicking away decides nothing; an entry row is row-shaped in every mode
|
|
299
|
+
- an entry row that was never committed is not part of a save and stays open
|
|
375
300
|
|
|
376
301
|
```tsx
|
|
377
302
|
useTMDataGrid({
|
|
@@ -388,19 +313,13 @@ useTMDataGrid({
|
|
|
388
313
|
<Button onClick={() => grid.edit.addRow()}>Add row</Button>;
|
|
389
314
|
```
|
|
390
315
|
|
|
391
|
-
Annotate
|
|
392
|
-
field to `string`, and `(): Product => ({ ... })` keeps it checked.
|
|
316
|
+
Annotate the return type of `newRowDefaults`: a bare object literal widens a union field to `string`, and `(): Product => ({ ... })` keeps it checked.
|
|
393
317
|
|
|
394
|
-
`addRow`
|
|
395
|
-
|
|
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.
|
|
318
|
+
`addRow(values)` overrides `newRowDefaults` key by key: `addRow()` opens the defaults, `addRow({ department: "Sales" })` opens them with `department` filled in, and passing a whole row duplicates it.
|
|
319
|
+
The seeded values are editable and validate like any other, and nothing reaches `onRowAdd` until the row is committed.
|
|
400
320
|
|
|
401
|
-
A grouped column has no cell on the entry row
|
|
402
|
-
|
|
403
|
-
cannot type the grouped field. Seed it: `addRow({ region: "EMEA" })`.
|
|
321
|
+
A grouped column has no cell on the entry row; under the default `groupedColumnMode: "remove"` it is not in the grid at all.
|
|
322
|
+
Seed it instead: `addRow({ region: "EMEA" })`.
|
|
404
323
|
|
|
405
324
|
```tsx
|
|
406
325
|
<Button onClick={() => grid.edit.addRow({ department: "Sales", active: true })}>
|
|
@@ -410,8 +329,7 @@ cannot type the grouped field. Seed it: `addRow({ region: "EMEA" })`.
|
|
|
410
329
|
<Button onClick={() => grid.edit.addRow(selected.original)}>Duplicate</Button>;
|
|
411
330
|
```
|
|
412
331
|
|
|
413
|
-
To limit how many entry rows are open at once, read the entry state
|
|
414
|
-
`edit.store` and gate the button:
|
|
332
|
+
To limit how many entry rows are open at once, read the entry state from `edit.store` and gate the button:
|
|
415
333
|
|
|
416
334
|
```tsx
|
|
417
335
|
const hasOpenEntry = useSelector(grid.edit.store, (state) =>
|
|
@@ -423,22 +341,18 @@ const hasOpenEntry = useSelector(grid.edit.store, (state) =>
|
|
|
423
341
|
</Button>;
|
|
424
342
|
```
|
|
425
343
|
|
|
426
|
-
###
|
|
344
|
+
### Committed new rows
|
|
427
345
|
|
|
428
|
-
`
|
|
429
|
-
|
|
430
|
-
|
|
346
|
+
Under `draft: true` a committed entry row leaves the entry block and becomes a body row: it carries `data-new` and `data-draft`, is tinted with `--dg-row-new-bg`, and is sorted, filtered and counted with the rest on the values it was entered with.
|
|
347
|
+
Set `newRowsSticky: true` to keep committed rows in the entry block until the save instead, out of the body's sort and out of the row count.
|
|
348
|
+
Double-click, or the lane's pencil, reopens the row into the entry block, which takes it out of the draft store until it is committed again; ✕ removes it.
|
|
431
349
|
|
|
432
|
-
|
|
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.
|
|
350
|
+
### Importing rows
|
|
436
351
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
as plain values, not as a form.
|
|
352
|
+
`edit.addRows(rows)` opens a batch of entry rows in one write, where a loop over `addRow` is one write per row.
|
|
353
|
+
Each row is seeded over `newRowDefaults` like `addRow`.
|
|
354
|
+
`{ commit: true }` submits the rows too: rows that validate are committed, and rows that fail stay open in the entry block with their errors.
|
|
355
|
+
The result says which went which way, so the bad rows can be reported before anything is saved:
|
|
442
356
|
|
|
443
357
|
```tsx
|
|
444
358
|
const { committed, open } = await grid.edit.addRows(parsedRows, {
|
|
@@ -448,11 +362,10 @@ if (open.length > 0) notify(`${open.length} rows need attention`);
|
|
|
448
362
|
await grid.edit.saveDrafts();
|
|
449
363
|
```
|
|
450
364
|
|
|
451
|
-
Column rules
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
- one call per row, in the order given.
|
|
365
|
+
Column rules apply even though the rows never had an editor on screen: the engine runs `meta.edit.validate` at commit.
|
|
366
|
+
Under `draft: true` the whole import is one publish: the rows are validated together and land in the draft store in the same render that shows them, so ten thousand rows take about a second.
|
|
367
|
+
`saveDrafts` sends them the same way.
|
|
368
|
+
Without `draft: true`, `commit: true` adds each valid row through `onRowAdd`, one call per row, in the order given.
|
|
456
369
|
|
|
457
370
|
```demo
|
|
458
371
|
file: editing/ImportRows.tsx
|
|
@@ -460,42 +373,116 @@ hint: Import parses the pasted rows, commits the valid ones and leaves the rest
|
|
|
460
373
|
height: 460
|
|
461
374
|
```
|
|
462
375
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
`
|
|
471
|
-
On an entry row, committed or not,
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
`
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
`
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
376
|
+
## Deleting rows
|
|
377
|
+
|
|
378
|
+
`edit.deleteRow(rowId)` calls `onRowDelete({ rowId, row })` at once; put any confirmation in that callback.
|
|
379
|
+
Under `draft: true` it marks the row instead: the row renders struck through and inert with `data-deleted`, the lane shows Restore, and `saveDrafts` reports the id in `deleted`.
|
|
380
|
+
The mark goes straight into the draft store; there is nothing to type.
|
|
381
|
+
`edit.restoreRow(rowId)` removes the mark, which is what the lane's Restore calls.
|
|
382
|
+
|
|
383
|
+
`deleteRow` is idempotent: deleting a marked row leaves it marked.
|
|
384
|
+
On an entry row, committed or not, it discards the entry, and an unknown id is a no-op.
|
|
385
|
+
`edit.deleteRows(rowIds)` does the same over a list in one call, so a selection can be passed as it stands: duplicates, marked rows and stale ids included.
|
|
386
|
+
|
|
387
|
+
A marked row is read-only and not selectable until it is restored:
|
|
388
|
+
|
|
389
|
+
- `begin`, `setCellValue`, `setRowValues` and `clearCell` refuse it, and the keyboard cannot open an editor on it
|
|
390
|
+
- an editor open on the row when it is marked is cancelled
|
|
391
|
+
- its checkbox is disabled, select-all skips it, and the mark drops it from `rowSelection`
|
|
392
|
+
- it still sorts, filters, groups, aggregates and counts, and is left out of an export
|
|
393
|
+
- a committed edit stays under the mark: Restore brings the row back as edited, while Save leaves the edit out of `updated`, reports the row in `deleted` only, and forgets the edit once the deletion is saved
|
|
394
|
+
|
|
395
|
+
A row the engine takes out of the table, an entry row that is discarded or saved or a marked row once its deletion is saved, leaves `rowSelection`, `expanded` and `rowPinning` with it, so a saved deletion does not leave the select-all box indeterminate.
|
|
396
|
+
|
|
397
|
+
The grid never modifies `data`: you apply adds and deletes, and the new rows arrive back through `data`.
|
|
398
|
+
The engine's `tempId` (`__new__1`, …) does not need to become a real id; assign one when you create the record.
|
|
399
|
+
|
|
400
|
+
## Which cells edit
|
|
401
|
+
|
|
402
|
+
A column is editable when it maps to a data path: its `accessorKey`, or `meta.edit.field` for a column built on `accessorFn`.
|
|
403
|
+
Dot paths reach into nested records: `accessorKey: "address.city"` edits `values.address.city`, and issues from a nested schema map to the right column.
|
|
404
|
+
|
|
405
|
+
| Gate | Effect |
|
|
406
|
+
| --- | --- |
|
|
407
|
+
| `editing.columns: string[]` | Only the named columns edit |
|
|
408
|
+
| `meta.edit.enabled: false` | The column never edits |
|
|
409
|
+
| `meta.edit.enabled: (row) => boolean` | Per row, per column |
|
|
410
|
+
| `editing.isRowEditable: (row) => boolean` | The whole row, in every mode |
|
|
411
|
+
|
|
412
|
+
Group rows and the generated lanes never edit.
|
|
413
|
+
|
|
414
|
+
`editing.columns` lists the column ids that take edits.
|
|
415
|
+
By default, every column that maps to a data path is editable.
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
editing: { mode: "cell", columns: ["targetPct", "note"] }
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
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`.
|
|
422
|
+
The same list decides which cells an entry row opens.
|
|
423
|
+
|
|
424
|
+
`edit.isColumnEditable(column)` answers 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`.
|
|
425
|
+
A per-row `enabled` predicate is the row's half, and `edit.canEditCell(row, column)` asks both.
|
|
426
|
+
|
|
427
|
+
```demo
|
|
428
|
+
file: editing/EditableGating.tsx
|
|
429
|
+
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.
|
|
430
|
+
height: 440
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## How a draft renders
|
|
434
|
+
|
|
435
|
+
Forms live outside the DOM, keyed by row id.
|
|
436
|
+
Scrolling an editing row away unmounts its editor; the form keeps its values, dirty state and errors, and the editor remounts over the same form when the row returns.
|
|
437
|
+
|
|
438
|
+
A cell whose row holds a draft renders the draft value through the column's own `cell` renderer, in every mode; a `"cellConfirm"` draft kept when the caret left shows what was typed, not the value in `data`.
|
|
439
|
+
Cell corners show the state: blue for a dirty draft, red for a validation error, and the row carries `data-dirty`.
|
|
440
|
+
An entry row's cells take the red corner, never the blue one.
|
|
441
|
+
|
|
442
|
+
A validation message outlives the editor that found it: the cell keeps its red corner and the lane carries the text until that field's value changes.
|
|
443
|
+
While an editor is open, the field's message shows in a tooltip on it, opened by focus and by hover.
|
|
444
|
+
|
|
445
|
+
Inside a `cell` renderer, `row.original` and `getValue()` are the row as shown, in every data column: the open form's values while the row is edited, the committed draft after ✓, and `data` otherwise.
|
|
446
|
+
A column computed from other fields follows the draft as it is typed, and a button in a cell sends the draft the user sees.
|
|
447
|
+
A handler with no cell context, a toolbar action or a callback that received only an id, reaches the same row with `edit.getRowValues(rowId)`.
|
|
448
|
+
|
|
449
|
+
```demo
|
|
450
|
+
file: editing/ActionCell.tsx
|
|
451
|
+
hint: Double-click a row, change the salary, press Use: the button gets the draft, and data is untouched until Save.
|
|
452
|
+
height: 320
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Styling pending rows
|
|
456
|
+
|
|
457
|
+
Rows publish what they hold, for styling and for tests:
|
|
458
|
+
|
|
459
|
+
| Attribute | On | Means |
|
|
460
|
+
| --- | --- | --- |
|
|
461
|
+
| `data-dirty` | Body row, cell | Values typed in, committed or not |
|
|
462
|
+
| `data-draft` | Body row, entry row | Committed into the draft store, waiting for Save |
|
|
463
|
+
| `data-deleted` | Body row | Marked for deletion |
|
|
464
|
+
| `data-new` | Body row, entry row | An entered row, committed (body) or not (entry block) |
|
|
465
|
+
|
|
466
|
+
A row attribute is published on every body row as `"true"` or `"false"`, so match the value, `[data-draft="true"]`, rather than the bare attribute, which matches every row.
|
|
467
|
+
A cell's `data-dirty` is present only while the cell is dirty.
|
|
468
|
+
|
|
469
|
+
The grid styles none of them beyond the corners, the strike-through and the new-row tint.
|
|
470
|
+
To highlight every row pending a save, and to let the user toggle it, use `rowStyle` on the Table:
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
<TMDataGrid.Table
|
|
474
|
+
rowStyle={(row) =>
|
|
475
|
+
showPending && grid.edit.state.committedRowIds.includes(row.id)
|
|
476
|
+
? { "--row-bg": "color-mix(in srgb, var(--mantine-color-yellow-6) 15%, transparent)" }
|
|
477
|
+
: undefined
|
|
478
|
+
}
|
|
479
|
+
/>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
`rowClassName` takes a class instead.
|
|
483
|
+
For CSS alone, target the attribute: `[data-dg-part="row"][data-draft="true"]`.
|
|
484
|
+
|
|
485
|
+
## The edit API
|
|
499
486
|
|
|
500
487
|
The built-in controls do everything through `edit`, which is public.
|
|
501
488
|
|
|
@@ -519,18 +506,17 @@ The built-in controls do everything through `edit`, which is public.
|
|
|
519
506
|
| `edit.getForm(rowId)` | The open row's live `FormApi`; `undefined` for a committed row |
|
|
520
507
|
| `edit.getRowValues(rowId)` | The row as shown: its draft where one is held, else the `data` value. `undefined` for an unknown row |
|
|
521
508
|
| `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 |
|
|
509
|
+
| `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, and `isSaving` |
|
|
523
510
|
|
|
524
|
-
`commit`, `commitAll`, `saveDrafts`, `setCellValue`, `setRowValues`,
|
|
525
|
-
|
|
526
|
-
next when driving edits in a loop.
|
|
511
|
+
`commit`, `commitAll`, `saveDrafts`, `setCellValue`, `setRowValues`, `clearCell` and `addRows` return promises.
|
|
512
|
+
Await each call before starting the next when driving edits in a loop.
|
|
527
513
|
|
|
528
|
-
`getForm` returns the open row's own `FormApi`.
|
|
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
|
|
514
|
+
`getForm` returns the open row's own `FormApi`.
|
|
515
|
+
Render it in a drawer or side panel and it shares values, dirty state and errors with the inline cells.
|
|
516
|
+
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
517
|
|
|
532
518
|
`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
|
|
519
|
+
`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
520
|
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
521
|
|
|
536
522
|
```tsx
|
|
@@ -541,12 +527,11 @@ const selected = grid.table
|
|
|
541
527
|
const surviving = grid.edit.getRows().filter((row) => !row.deleted);
|
|
542
528
|
```
|
|
543
529
|
|
|
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).
|
|
530
|
+
For the inverse, a `@tanstack/react-form` form _around_ the grid holding the row array, see [A query builder form](/docs/query-builder).
|
|
546
531
|
|
|
547
532
|
### Bulk actions
|
|
548
533
|
|
|
549
|
-
`edit.setCellValue(rowId, columnId, value)` writes one cell and commits its row without an editor ever opening
|
|
534
|
+
`edit.setCellValue(rowId, columnId, value)` writes one cell and commits its row without an editor ever opening, for a toolbar action or a bulk fill.
|
|
550
535
|
The row need not be mounted, so a selected row inside a collapsed group takes the write like any other.
|
|
551
536
|
|
|
552
537
|
```tsx
|
|
@@ -555,10 +540,10 @@ for (const row of grid.table.getSelectedRowModel().rows) {
|
|
|
555
540
|
}
|
|
556
541
|
```
|
|
557
542
|
|
|
558
|
-
Under `draft: true` each row is committed into the draft store like
|
|
543
|
+
Under `draft: true` each row is committed into the draft store like a typed edit: the whole batch saves at once through `edit.saveDrafts()`, carries the same markers, and is reverted row by row from the edit lane.
|
|
559
544
|
|
|
560
545
|
`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
|
|
546
|
+
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
547
|
|
|
563
548
|
```tsx
|
|
564
549
|
await grid.edit.setRowValues(row.id, { status: "Closed", closedOn: today() });
|