@jielga/tmdatagrid 2.0.0-beta.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -212
- package/dist/index.d.ts +1281 -768
- package/dist/index.js +4607 -3250
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/adding-rows.md +132 -0
- package/docs/anatomy.md +119 -0
- package/docs/card-view.md +108 -0
- package/docs/cell-selection.md +194 -0
- package/docs/column-layout.md +182 -0
- package/docs/column-menu.md +66 -0
- package/docs/columns.md +269 -0
- package/docs/components.md +311 -0
- package/docs/draft-store.md +242 -0
- package/docs/editing.md +303 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +319 -0
- package/docs/filtering.md +362 -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/migrating-to-2.md +163 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +114 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +179 -0
- package/docs/quick-search.md +84 -0
- package/docs/row-details.md +115 -0
- package/docs/row-interaction.md +149 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +136 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +112 -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 +744 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +22 -46
- package/skills/appearance/SKILL.md +72 -19
- package/skills/cell-selection/SKILL.md +49 -48
- package/skills/columns/SKILL.md +125 -70
- package/skills/columns/references/columns-api.md +59 -0
- package/skills/data/SKILL.md +112 -18
- package/skills/editing/SKILL.md +76 -42
- package/skills/editing/references/common-mistakes.md +77 -69
- package/skills/editing/references/editing-api.md +25 -20
- package/skills/editing/references/editors-and-validation.md +80 -18
- package/skills/filtering/SKILL.md +155 -41
- package/skills/getting-started/SKILL.md +116 -16
- package/skills/grouping/SKILL.md +31 -16
- package/skills/migrating-to-2/SKILL.md +244 -0
- package/skills/options/SKILL.md +24 -12
- package/skills/rows/SKILL.md +22 -18
- package/skills/rows/references/rows-api.md +10 -6
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +150 -32
- package/skills/testing-components/SKILL.md +230 -0
- package/skills/testing-editing/SKILL.md +240 -0
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
- 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 +5 -51
- package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
- 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 +15 -8
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +357 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
- package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -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/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
- 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 +16 -2
- 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/components/generatedColumns.tsx +187 -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}/columnOptions.ts +60 -8
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
- package/src/core/export.ts +704 -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}/grouping.ts +21 -0
- package/src/{tmdatagrid/core → core}/labels.ts +51 -6
- package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
- package/src/core/pageReset.ts +120 -0
- package/src/core/pagination.ts +81 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/{tmdatagrid/core → core}/summary.ts +20 -4
- package/src/{tmdatagrid/index.ts → index.ts} +69 -35
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
- /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}/capabilities.ts +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}/controlledState.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.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}/resizePreview.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/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
package/docs/editing.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Editing
|
|
2
|
+
|
|
3
|
+
`@tanstack/react-form` becomes a peer dependency once editing is used.
|
|
4
|
+
|
|
5
|
+
Editing is turned on with the `editing` option.
|
|
6
|
+
Two settings inside it work independently:
|
|
7
|
+
|
|
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](/docs/draft-store) until the user presses Save
|
|
10
|
+
|
|
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 |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| **data** | Your rows, the only source of truth | - | - |
|
|
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
|
|
26
|
+
|
|
27
|
+
The smallest setup is cell mode with an `onCommit` that applies each change:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
const grid = useTMDataGrid({
|
|
31
|
+
data,
|
|
32
|
+
columns,
|
|
33
|
+
getRowId: (row) => String(row.id),
|
|
34
|
+
editing: {
|
|
35
|
+
mode: "cell",
|
|
36
|
+
onCommit: async ({ rowId, value, changes }) => {
|
|
37
|
+
// changes is a list of descriptors, not a patch object
|
|
38
|
+
await api.patch(rowId, Object.fromEntries(changes.map((c) => [c.field, c.next])));
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`onCommit` receives `{ rowId, value, original, changes, source }`:
|
|
45
|
+
|
|
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
|
|
50
|
+
|
|
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.
|
|
54
|
+
|
|
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.
|
|
57
|
+
|
|
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.
|
|
64
|
+
|
|
65
|
+
| Mode | Commit | Cancel | Controls |
|
|
66
|
+
| --------------- | ------------------------------------------ | ----------------- | ------------------------ |
|
|
67
|
+
| `"cell"` | Enter, Tab or leaving the cell | Escape | none |
|
|
68
|
+
| `"cellConfirm"` | ✓ or Enter; Tab walks input, ✓, ✕ and then leaves, keeping the draft | ✕ or Escape | ✓ / ✕ beside the input |
|
|
69
|
+
| `"row"` | ✓ in the edit lane, or Enter | ✕, or Escape | generated edit lane |
|
|
70
|
+
|
|
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.
|
|
72
|
+
|
|
73
|
+
```demo
|
|
74
|
+
file: editing/CellEditing.tsx
|
|
75
|
+
hint: Double-click a cell, or press Enter or F2, or start typing on it.
|
|
76
|
+
height: 440
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Opening an editor
|
|
80
|
+
|
|
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
|
+
|
|
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.
|
|
90
|
+
|
|
91
|
+
### Row editing
|
|
92
|
+
|
|
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.
|
|
97
|
+
|
|
98
|
+
```demo
|
|
99
|
+
file: editing/RowEditing.tsx
|
|
100
|
+
hint: Put a Sales row over 60 000 kr and Save reports why it is rejected.
|
|
101
|
+
height: 440
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Edit lane
|
|
105
|
+
|
|
106
|
+
The edit lane is the generated column at the end of every row, with the id `EDIT_COLUMN_ID`.
|
|
107
|
+
The grid adds it under `mode: "row"`, under `draft: true`, or when `onRowDelete` is set.
|
|
108
|
+
What it shows depends on the row's state:
|
|
109
|
+
|
|
110
|
+
| Row state | Icon | Actions |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| Open | - | The mode's controls: ✓ ("Save row") commits the row, ✕ ("Cancel edit") cancels the edit |
|
|
113
|
+
| Committed edit | Pencil | Revert drops the row's draft |
|
|
114
|
+
| Committed new row | Plus | Pencil reopens it, ✕ removes it |
|
|
115
|
+
| Marked for deletion | Trash | Restore removes the mark |
|
|
116
|
+
|
|
117
|
+
A committed row is never offered ✓ again; `TMDataGrid.DraftActions` sends it.
|
|
118
|
+
A committed row also hides the trash: revert first, then delete.
|
|
119
|
+
The trash shows when a deletion has somewhere to go: `onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is.
|
|
120
|
+
|
|
121
|
+
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.
|
|
122
|
+
A pathless issue from `rowValidators` has no cell to land on, so that tooltip is where its message shows.
|
|
123
|
+
|
|
124
|
+
## Which cells edit
|
|
125
|
+
|
|
126
|
+
A column is editable when it maps to a data path: its `accessorKey`, or `meta.edit.field` for a column built on `accessorFn`.
|
|
127
|
+
Dot paths reach into nested records: `accessorKey: "address.city"` edits `values.address.city`, and issues from a nested schema map to the right column.
|
|
128
|
+
|
|
129
|
+
| Gate | Effect |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `editing.columns: string[]` | Only the named columns edit |
|
|
132
|
+
| `meta.edit.enabled: false` | The column never edits |
|
|
133
|
+
| `meta.edit.enabled: (row) => boolean` | Per row, per column |
|
|
134
|
+
| `editing.isRowEditable: (row) => boolean` | The whole row, in every mode |
|
|
135
|
+
|
|
136
|
+
Group rows and the generated lanes never edit.
|
|
137
|
+
|
|
138
|
+
`editing.columns` lists the column ids that take edits.
|
|
139
|
+
By default, every column that maps to a data path is editable.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
editing: { mode: "cell", columns: ["targetPct", "note"] }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
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`.
|
|
146
|
+
The same list decides which cells an entry row opens.
|
|
147
|
+
|
|
148
|
+
`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`.
|
|
149
|
+
A per-row `enabled` predicate is the row's half, and `edit.canEditCell(row, column)` asks both.
|
|
150
|
+
|
|
151
|
+
```demo
|
|
152
|
+
file: editing/EditableGating.tsx
|
|
153
|
+
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.
|
|
154
|
+
height: 440
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## How a draft renders
|
|
158
|
+
|
|
159
|
+
Forms live outside the DOM, keyed by row id.
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
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`.
|
|
163
|
+
Cell corners show the state: blue for a dirty draft, red for a validation error, and the row carries `data-dirty`.
|
|
164
|
+
An entry row's cells take the red corner, never the blue one.
|
|
165
|
+
|
|
166
|
+
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.
|
|
167
|
+
While an editor is open, the field's message shows in a tooltip on it, opened by focus and by hover.
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
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.
|
|
171
|
+
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)`.
|
|
172
|
+
|
|
173
|
+
```demo
|
|
174
|
+
file: editing/ActionCell.tsx
|
|
175
|
+
hint: Double-click a row, change the salary, press Use: the button gets the draft, and data is untouched until Save.
|
|
176
|
+
height: 320
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Styling pending rows
|
|
180
|
+
|
|
181
|
+
Rows publish what they hold, for styling and for tests:
|
|
182
|
+
|
|
183
|
+
| Attribute | On | Means |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| `data-dirty` | Body row, cell | Values typed in, committed or not |
|
|
186
|
+
| `data-draft` | Body row, entry row | Committed into the draft store, waiting for Save |
|
|
187
|
+
| `data-deleted` | Body row | Marked for deletion |
|
|
188
|
+
| `data-new` | Body row, entry row | An entered row, committed (body) or not (entry block) |
|
|
189
|
+
|
|
190
|
+
A row attribute is present, with the value `"true"`, only on the rows it applies to, so `[data-draft]` and `[data-draft="true"]` match the same rows.
|
|
191
|
+
A cell's `data-dirty` is present only while the cell is dirty.
|
|
192
|
+
|
|
193
|
+
The grid styles none of them beyond the corners, the strike-through and the new-row tint.
|
|
194
|
+
To highlight every row pending a save, and to let the user toggle it, use `rowStyle` on the Table:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
<TMDataGrid.Table
|
|
198
|
+
rowStyle={(row) =>
|
|
199
|
+
showPending && grid.edit.state.committedRowIds.includes(row.id)
|
|
200
|
+
? { "--row-bg": "color-mix(in srgb, var(--mantine-color-yellow-6) 15%, transparent)" }
|
|
201
|
+
: undefined
|
|
202
|
+
}
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`rowClassName` takes a class instead.
|
|
207
|
+
For CSS alone, target the attribute: `[data-dg-part="row"][data-draft="true"]`.
|
|
208
|
+
|
|
209
|
+
## The edit API
|
|
210
|
+
|
|
211
|
+
The built-in controls do everything through `edit`, which is public.
|
|
212
|
+
|
|
213
|
+
| Member | Does |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| `edit.begin({ rowId, columnId })` | Opens a row into form state. On a committed row, takes it back out of the draft store |
|
|
216
|
+
| `edit.commit(rowId)` | Submits one row: into the draft store under `draft: true`, to `onCommit` otherwise. Resolves `false` if validation blocked it |
|
|
217
|
+
| `edit.commitAll()` | Submits every open row. Resolves `{ ok, committed, open }` - the rows that committed and the rows still open; `ok` is `false` when one stayed open |
|
|
218
|
+
| `edit.saveDrafts()` | Sends the draft store. Open rows are left alone. Resolves `{ ok, saved, kept, reopened }` - the ids that left the store, stayed in it for the next save, or reopened with an error; `ok` is `false` when anything was kept or reopened |
|
|
219
|
+
| `edit.cancel(rowId)` / `edit.cancelAll()` | Drops drafts - form state and the draft store alike |
|
|
220
|
+
| `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 |
|
|
221
|
+
| `edit.setRowValues(rowId, values)` | The same for several cells of one row, in one commit. All or nothing |
|
|
222
|
+
| `edit.clearCell(rowId, columnId)` | Writes the type's empty value and commits it - what Delete does |
|
|
223
|
+
| `edit.addRow(values?)` | Opens one entry row, seeded over `newRowDefaults` |
|
|
224
|
+
| `edit.addRows(rows, options?)` | Opens a batch; `{ commit: true }` submits the rows too - one publish for the lot under `draft: true`. Resolves `{ ok, committed, open }` |
|
|
225
|
+
| `edit.deleteRow(rowId)` | Deletes a row, or marks it deleted under `draft: true`. Idempotent; discards an entry row; ignores an unknown id |
|
|
226
|
+
| `edit.deleteRows(rowIds)` | `deleteRow` over a list in one call - safe to feed a selection as it stands |
|
|
227
|
+
| `edit.restoreRow(rowId)` | Removes a row's deletion mark - what the lane's Restore calls |
|
|
228
|
+
| `edit.canEditCell(row, column)` | Whether a cell may open an editor: the column takes edits and the row does too |
|
|
229
|
+
| `edit.canEditRow(row)` | Whether a row takes edits at all - what shows the edit lane's pencil |
|
|
230
|
+
| `edit.isColumnEditable(column)` | Whether a column takes edits at all, with no row in hand |
|
|
231
|
+
| `edit.getForm(rowId)` | The open row's live `FormApi`; `undefined` for a committed row |
|
|
232
|
+
| `edit.getRowValues(rowId)` | The row as shown: its draft where one is held, else the `data` value. `undefined` for an unknown row |
|
|
233
|
+
| `edit.getRows()` | Every row as shown - drafts overlaid, entry rows appended, deletion-marked rows included and flagged `deleted` |
|
|
234
|
+
| `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` |
|
|
235
|
+
|
|
236
|
+
`commit`, `commitAll`, `saveDrafts`, `setCellValue`, `setRowValues`, `clearCell` and `addRows` return promises.
|
|
237
|
+
Await each call before starting the next when driving edits in a loop.
|
|
238
|
+
|
|
239
|
+
`getForm` returns the open row's own `FormApi`.
|
|
240
|
+
Render it in a drawer or side panel and it shares values, dirty state and errors with the inline cells.
|
|
241
|
+
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.
|
|
242
|
+
|
|
243
|
+
`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.
|
|
244
|
+
`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.
|
|
245
|
+
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`.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
const selected = grid.table
|
|
249
|
+
.getSelectedRowModel()
|
|
250
|
+
.rows.flatMap((row) => grid.edit.getRowValues(row.id) ?? []);
|
|
251
|
+
|
|
252
|
+
const surviving = grid.edit.getRows().filter((row) => !row.deleted);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
For the inverse, a `@tanstack/react-form` form _around_ the grid holding the row array, see [A query builder form](/docs/query-builder).
|
|
256
|
+
|
|
257
|
+
### Bulk actions
|
|
258
|
+
|
|
259
|
+
`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.
|
|
260
|
+
The row need not be mounted, so a selected row inside a collapsed group takes the write like any other.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
for (const row of grid.table.getSelectedRowModel().rows) {
|
|
264
|
+
await grid.edit.setCellValue(row.id, "targetPct", equalWeight(row.original));
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
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.
|
|
269
|
+
|
|
270
|
+
`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.
|
|
271
|
+
Keys are column ids, and it is all or nothing: if any named cell takes no edit, nothing is written and it resolves `false`.
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
await grid.edit.setRowValues(row.id, { status: "Closed", closedOn: today() });
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
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.
|
|
278
|
+
`value` is the stored value: no editor runs, so `meta.edit.mapValue` does not run either, while `meta.edit.validate` does.
|
|
279
|
+
|
|
280
|
+
## Reference
|
|
281
|
+
|
|
282
|
+
| Name | Kind | Type | Default | What it does |
|
|
283
|
+
| ----------------------------- | -------------- | ------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------------ |
|
|
284
|
+
| `editing` | Option | `TMDataGridEditingOptions` | – | Turns editing on. One object holding both axes and every editing callback. |
|
|
285
|
+
| `editing.mode` | Member | `"cell" \| "cellConfirm" \| "row"` | – | Picks what counts as a commit and which controls trigger it. |
|
|
286
|
+
| `TMDataGridEditMode` | Type | `"cell" \| "cellConfirm" \| "row"` | – | The type of `editing.mode`. |
|
|
287
|
+
| `editing.draft` | Member | `boolean` | `false` | Holds commits in the draft store for `edit.saveDrafts()` instead of sending them out. |
|
|
288
|
+
| `getRowId` | Table option | `(row) => string` | – | Required once `editing` is set. Drafts are keyed by it. |
|
|
289
|
+
| `editing.columns` | Member | `ReadonlyArray<string>` | Every mapped column | The column ids that take edits. Gates before `meta.edit`, never past it. |
|
|
290
|
+
| `editing.isRowEditable` | Member | `(row) => boolean` | – | Closes a whole row to editing. |
|
|
291
|
+
| `editing.rowValidators` | Member | TanStack Form validators | – | Form-level rules for the whole editing row. See [Editors](/docs/editors). |
|
|
292
|
+
| `editing.tableValidators` | Member | `TMDataGridTableValidators` | – | Cross-row rules, handed the collection with every draft overlaid. See [Editors](/docs/editors#cross-row-rules). |
|
|
293
|
+
| `editing.onCommit` | Callback | `({ rowId, value, original, changes, source }) => void \| Promise` | – | Applies one row's change. Reject to keep the draft. |
|
|
294
|
+
| `TMDataGridEditCommitArgs` · `TMDataGridEditChange` | Types | – | – | What `onCommit` receives, and one entry of its `changes`. |
|
|
295
|
+
| `meta.edit.enabled` | Column meta | `boolean \| (row) => boolean` | `true` | Whether a column's cells edit. |
|
|
296
|
+
| `meta.edit.field` | Column meta | `string` | The `accessorKey` | The data path an edit writes to. |
|
|
297
|
+
| `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). |
|
|
298
|
+
| `EDIT_COLUMN_ID` | Export | `"__edit__"` | – | Id of the generated edit lane. |
|
|
299
|
+
| `clearedValueForType` | Export | `(type) => unknown` | – | What Delete writes for each column type. |
|
|
300
|
+
| `TMDataGridEditState` · `TMDataGridEditRowProjection` | Types | – | – | The value of `edit.state` and `edit.store`, and one open row's entry in its `rows`. |
|
|
301
|
+
| `TMDataGridEditRowSnapshot` | Type | `{ rowId, value, isNew, deleted }` | – | One row of `edit.getRows()`. |
|
|
302
|
+
| `data-dirty` | Data attribute | – | – | On a body row holding a dirty draft. |
|
|
303
|
+
| `data-draft` | Data attribute | – | – | On a body row or entry row committed into the draft store, waiting for a save. |
|
package/docs/editors.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Editors and validation
|
|
2
|
+
|
|
3
|
+
Which control a cell opens when editing starts, what the value becomes on the
|
|
4
|
+
way in, and what prevents a bad one being committed. All three follow from the
|
|
5
|
+
column: `meta.type` picks the editor, and `meta.edit` holds the rest.
|
|
6
|
+
|
|
7
|
+
```demo
|
|
8
|
+
file: editing/EditorsAndValidation.tsx
|
|
9
|
+
hint: Type a single letter into String, or 5 into Number, to see validation reject it.
|
|
10
|
+
height: 440
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## The built-in editors
|
|
14
|
+
|
|
15
|
+
`meta.type` picks one, and `meta.options` feeds the select editors from the same
|
|
16
|
+
source the filter panel reads. Neither lives under `meta.edit`.
|
|
17
|
+
|
|
18
|
+
| `meta.type` | Editor | Writes |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `string` (default) | Text input | `string` |
|
|
21
|
+
| `number` | Number input | `number`, or `null` while the cell is empty or the text is not yet a number |
|
|
22
|
+
| `boolean` | Checkbox | `boolean` |
|
|
23
|
+
| `date` | Native `<input type="date">` | A `Date`, or the `"YYYY-MM-DD"` string; `null` when cleared |
|
|
24
|
+
| `select` | Searchable select from `meta.options`. Commits on pick under `"cell"` | `string \| null` |
|
|
25
|
+
| `multiSelect` | Multi-select, same source | `string[]` |
|
|
26
|
+
|
|
27
|
+
**Writes** is the value the editor puts into the draft: what `meta.edit.mapValue` is handed, what `meta.edit.validate` checks, and what a commit carries in `value` and in `changes[].next`.
|
|
28
|
+
|
|
29
|
+
The number editor writes `null` rather than `NaN` while the text does not parse, so a half-typed number leaves the field empty instead of committing a number no rule can describe.
|
|
30
|
+
The date editor picks between its two types once, when it opens, from what the cell held: a `Date` cell keeps receiving `Date`s and a string cell keeps receiving `"YYYY-MM-DD"` strings, so clearing and retyping cannot flip the type.
|
|
31
|
+
|
|
32
|
+
Each is a named export (`TMDataGridStringEditor`, `TMDataGridNumberEditor`,
|
|
33
|
+
`TMDataGridBooleanEditor`, `TMDataGridDateEditor`, `TMDataGridSelectEditor`,
|
|
34
|
+
`TMDataGridMultiSelectEditor`), so a custom editor can wrap one instead of
|
|
35
|
+
starting from scratch.
|
|
36
|
+
|
|
37
|
+
## Writing your own
|
|
38
|
+
|
|
39
|
+
`meta.edit.editor` fills the same slot as the built-ins. It is a **component**,
|
|
40
|
+
rendered as JSX, so hooks may be used inside, and it receives the live TanStack
|
|
41
|
+
Form `field` API. Bind any control to it as you would inside a form:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => (
|
|
45
|
+
<Slider
|
|
46
|
+
value={field.state.value}
|
|
47
|
+
onChange={field.handleChange}
|
|
48
|
+
onChangeEnd={() => void commit()}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
meta: { edit: { editor: SalaryEditor } }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Define editors at module scope.** An inline arrow function gets a new identity
|
|
56
|
+
on every render, which remounts the editor mid-edit and discards what was typed.
|
|
57
|
+
|
|
58
|
+
The field's validation message is shown by the host, in a tooltip on the
|
|
59
|
+
editor: open while the input is focused, and on hover. A custom editor gets the
|
|
60
|
+
same tooltip, so it renders no message of its own.
|
|
61
|
+
|
|
62
|
+
The built-in editors mark the input invalid and add no inline text, since the
|
|
63
|
+
message would be clipped by the width of the cell. A custom editor does the
|
|
64
|
+
same by binding a boolean to the control's `error` prop:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const hasError = field.state.meta.errors.length > 0;
|
|
68
|
+
|
|
69
|
+
<Slider value={field.state.value} onChange={field.handleChange} error={hasError} />;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Mapping the value as it is typed
|
|
73
|
+
|
|
74
|
+
A column can rewrite every value on its way into the draft: uppercase a code,
|
|
75
|
+
strip spaces out of an IBAN, clamp a number into range. `meta.edit.mapValue`
|
|
76
|
+
runs on each write an editor makes, which is each keystroke for a text input and
|
|
77
|
+
each pick for a select.
|
|
78
|
+
|
|
79
|
+
```demo
|
|
80
|
+
file: editing/MappedInput.tsx
|
|
81
|
+
hint: Type into either column. Code uppercases, Reference keeps digits only.
|
|
82
|
+
height: 420
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
meta: {
|
|
87
|
+
edit: {
|
|
88
|
+
mapValue: ({ value }) =>
|
|
89
|
+
typeof value === "string" ? value.toUpperCase() : value,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
What it returns is what the cell shows, what the validators check and what is
|
|
95
|
+
committed.
|
|
96
|
+
|
|
97
|
+
The map is applied inside the editor host, around the field every editor writes
|
|
98
|
+
through, so one declaration covers all six built-in editors, your own
|
|
99
|
+
`meta.edit.editor`, and the character that opened the editor when typing started
|
|
100
|
+
the edit.
|
|
101
|
+
|
|
102
|
+
Some writes are not mapped: the value the editor opens with, and the writes that go through the form rather than through an editor - `edit.clearCell()`, the Delete key, `edit.setCellValue()` and `edit.setRowValues()`.
|
|
103
|
+
An editor calling `field.setValue` instead of `field.handleChange` also bypasses the map.
|
|
104
|
+
`handleChange` is the mapped path.
|
|
105
|
+
|
|
106
|
+
The map receives the row and column as well, so it can depend on the record
|
|
107
|
+
being edited:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
meta: {
|
|
111
|
+
edit: {
|
|
112
|
+
mapValue: ({ value, row }) =>
|
|
113
|
+
row.original.country === "SE" && typeof value === "string"
|
|
114
|
+
? value.replace(/ /g, "")
|
|
115
|
+
: value,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`previous` is the value the field held before this write, which a mask needs in
|
|
121
|
+
order to tell an insertion from a deletion.
|
|
122
|
+
|
|
123
|
+
### Keeping the caret
|
|
124
|
+
|
|
125
|
+
A mapped value differs from the one the input holds, so React writes the new
|
|
126
|
+
value into the DOM node and the browser collapses the selection to the end of
|
|
127
|
+
the field.
|
|
128
|
+
|
|
129
|
+
The built-in string and number editors restore the caret to where it was typed,
|
|
130
|
+
shifted by however much the map changed the length, so a mask that inserts or
|
|
131
|
+
strips characters keeps the caret beside the same text.
|
|
132
|
+
|
|
133
|
+
A custom editor rendering its own input has the same problem, and solves it the
|
|
134
|
+
same way: record `selectionStart` as the value is handed over, and restore it in
|
|
135
|
+
a layout effect once the mapped value has rendered.
|
|
136
|
+
|
|
137
|
+
## Validation
|
|
138
|
+
|
|
139
|
+
The validators are TanStack Form's own: a Standard Schema (Zod, Valibot,
|
|
140
|
+
ArkType) or a plain function returning an error message or nothing.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
// Per column: field-level validators. A bare schema or function means { onChange: it }.
|
|
144
|
+
meta: { edit: { validate: z.string().min(2, "Too short") } }
|
|
145
|
+
|
|
146
|
+
// The same rule without a schema library:
|
|
147
|
+
meta: {
|
|
148
|
+
edit: {
|
|
149
|
+
validate: ({ value }: { value: unknown }) =>
|
|
150
|
+
typeof value === "string" && value.length < 2 ? "Too short" : undefined,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Per row: form-level validators - cross-field rules live here.
|
|
155
|
+
useTMDataGrid({
|
|
156
|
+
editing: {
|
|
157
|
+
mode: "cell",
|
|
158
|
+
rowValidators: {
|
|
159
|
+
onSubmit: z.object({ salary: z.number().positive() })
|
|
160
|
+
.refine((r) => r.status !== "Terminated" || r.salary === 0, {
|
|
161
|
+
message: "A terminated employee has no salary",
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
A plain function is typed `TMDataGridValidator`, whose `value` is `never`, so annotate the parameter - `{ value: unknown }`, or the type the column's editor writes - rather than leaving it to be inferred.
|
|
169
|
+
|
|
170
|
+
Pathed issues land on the matching cells; pathless ones on the row, where the
|
|
171
|
+
message shows in the edit lane's tooltip on the open row's ✓. To show a
|
|
172
|
+
pathless message somewhere of your own, read it from
|
|
173
|
+
`edit.getForm(rowId)?.state.errors` on the open row that carries it;
|
|
174
|
+
`edit.store` carries the flag (`hasRowError`) and the field messages
|
|
175
|
+
(`errorMessages`), not the row text.
|
|
176
|
+
|
|
177
|
+
A commit blocked by validation keeps the editor open, invalid, with the message
|
|
178
|
+
in its tooltip. A rejected `editing.onCommit` keeps the draft too, with the
|
|
179
|
+
error on the row. Server-side field errors can be returned natively through
|
|
180
|
+
`editing.rowValidators.onSubmitAsync`'s `{ form, fields }` shape.
|
|
181
|
+
|
|
182
|
+
Cross-field rules need a mode that commits the whole row at once. Under
|
|
183
|
+
`"cell"` each cell commits alone, so a rule spanning two columns cannot be
|
|
184
|
+
satisfied by either one. Use `editing.rowValidators.onSubmit` with `"row"`. See
|
|
185
|
+
[Editing](/docs/editing#row-editing).
|
|
186
|
+
|
|
187
|
+
A rule about the whole collection, such as "no duplicates" or "shares sum to
|
|
188
|
+
100", is neither a field rule nor a row rule. It takes
|
|
189
|
+
`editing.tableValidators` - see [Cross-row rules](#cross-row-rules).
|
|
190
|
+
|
|
191
|
+
## Cross-row rules
|
|
192
|
+
|
|
193
|
+
`editing.tableValidators` holds the rules that need the other rows: no
|
|
194
|
+
duplicate keys, no overlapping ranges, allocations summing to a total. Its
|
|
195
|
+
validators receive the committing row and `rows`, the collection as it would
|
|
196
|
+
stand if the commit landed - every draft overlaid, entry rows appended,
|
|
197
|
+
deletion-marked rows removed:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
editing: {
|
|
201
|
+
mode: "cell",
|
|
202
|
+
draft: true,
|
|
203
|
+
tableValidators: {
|
|
204
|
+
onSubmit: ({ value, rowId, rows }) =>
|
|
205
|
+
rows.some((r) => r.rowId !== rowId && r.value.code === value.code)
|
|
206
|
+
? { fields: { code: "Codes must be unique" } }
|
|
207
|
+
: undefined,
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The result is the `rowValidators` vocabulary: nothing passes, a string is a
|
|
213
|
+
row-level message, and `{ form, fields }` lands pathed issues on the
|
|
214
|
+
committing row's cells. `onSubmit` runs first, and its failure stands without
|
|
215
|
+
`onSubmitAsync` running.
|
|
216
|
+
|
|
217
|
+
The rules run at every commit, after the row's own validators, and again for
|
|
218
|
+
every committed row during `saveDrafts`, where they are the only rules that
|
|
219
|
+
run: column rules and `rowValidators` saw the same values at commit. A
|
|
220
|
+
committed row that a later edit has invalidated is reopened with its errors,
|
|
221
|
+
the valid rows are sent, and the save reports it in `reopened`. Errors land on the
|
|
222
|
+
committing row only; the row it clashes with is not marked.
|
|
223
|
+
|
|
224
|
+
`rows` is unfiltered, so a rule sees the whole collection whatever the view
|
|
225
|
+
shows, and it never contains group rows.
|
|
226
|
+
|
|
227
|
+
```demo
|
|
228
|
+
file: editing/TableValidation.tsx
|
|
229
|
+
hint: Give two teams the same code, or push the shares past 100, and the commit is refused. Drafts count - a clash with a pending edit is caught too.
|
|
230
|
+
height: 380
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A grid inside an outer form can put collection rules in the form's own field
|
|
234
|
+
validator instead. See [A query builder form](/docs/query-builder).
|
|
235
|
+
|
|
236
|
+
## Reference
|
|
237
|
+
|
|
238
|
+
| Name | Kind | Type | Default | What it does |
|
|
239
|
+
| --- | --- | --- | --- | --- |
|
|
240
|
+
| `meta.edit.editor` | Column meta | `TMDataGridEditorComponent` | By `meta.type` | Replaces the cell editor. |
|
|
241
|
+
| `meta.edit.validate` | Column meta | `TMDataGridFieldValidate` | – | Field-level validation. A bare schema or function means `onChange`. |
|
|
242
|
+
| `meta.edit.mapValue` | Column meta | `TMDataGridEditValueMap` | – | Maps each value an editor writes, before it reaches the draft. |
|
|
243
|
+
| `editing.rowValidators` | Option | `TMDataGridRowValidators` | – | Form-level validation, for cross-field rules. |
|
|
244
|
+
| `editing.tableValidators` | Option | `TMDataGridTableValidators` | – | Cross-row rules, handed the collection with every draft overlaid. See [Cross-row rules](#cross-row-rules). |
|
|
245
|
+
| `TMDataGridTableValidateArgs` | Export | type | – | What a table validator receives: `value`, `rowId`, `isNew`, `rows`. |
|
|
246
|
+
| `TMDataGridEditorArgs` | Export | type | – | What an editor component receives: `field`, `commit`, `cancel`, `row`, `column`. |
|
|
247
|
+
| `TMDataGridEditValueMapArgs` | Export | type | – | What `mapValue` receives: `value`, `previous`, `row`, `column`, `table`. |
|
|
248
|
+
| `TMDataGridStringEditor` · `NumberEditor` · `BooleanEditor` · `DateEditor` · `SelectEditor` · `MultiSelectEditor` | Exports | components | – | The six built-ins, for wrapping. |
|
|
249
|
+
| `normalizeFieldValidate` | Export | `(validate) => validators` | – | Turns a bare schema into TanStack Form's validator shape. |
|
|
250
|
+
| `getEditFieldName` | Export | `(column) => string` | – | The data path a column's edits write to. |
|