@jielga/tmdatagrid 2.0.0-beta.4 → 2.0.0-beta.7
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.d.ts +339 -152
- package/dist/index.js +2602 -2450
- package/dist/index.js.map +1 -1
- package/dist/styles.css +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 +44 -2
- package/skills/data/SKILL.md +14 -10
- package/skills/editing/SKILL.md +140 -68
- package/skills/editing/references/common-mistakes.md +98 -8
- package/skills/editing/references/editing-api.md +40 -30
- package/skills/editing/references/editors-and-validation.md +65 -14
- package/skills/filtering/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +3 -3
- package/skills/grouping/SKILL.md +5 -3
- package/skills/options/SKILL.md +2 -2
- package/skills/rows/SKILL.md +7 -3
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/src/tmdatagrid/components/TMDataGrid.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +33 -34
- package/src/tmdatagrid/components/TMDataGridDraftActions.tsx +296 -0
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +40 -33
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +7 -7
- package/src/tmdatagrid/components/TMDataGridTable.tsx +27 -5
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +4 -0
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +8 -10
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +34 -4
- package/src/tmdatagrid/components/editors/editorShared.ts +1 -29
- package/src/tmdatagrid/core/capabilities.ts +14 -6
- package/src/tmdatagrid/core/editEngine.ts +472 -96
- package/src/tmdatagrid/core/labels.ts +3 -3
- package/src/tmdatagrid/core/summary.ts +10 -2
- package/src/tmdatagrid/index.ts +9 -7
- package/src/tmdatagrid/useTMDataGrid.tsx +107 -47
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -233
|
@@ -73,14 +73,52 @@ meta: { edit: { editor: SalaryEditor } },
|
|
|
73
73
|
|
|
74
74
|
Source: `src/docs/editors.md`, `src/tmdatagrid/core/editEngine.ts`.
|
|
75
75
|
|
|
76
|
+
## HIGH A custom editor that binds no error text
|
|
77
|
+
|
|
78
|
+
The built-in editors bind the field's first error to the input's `error` prop.
|
|
79
|
+
A custom editor that binds nothing still blocks the commit, but all the user
|
|
80
|
+
sees is `data-invalid` on the cell - the row stays open with no message
|
|
81
|
+
anywhere on screen, which reads as a broken grid rather than a rejected value.
|
|
82
|
+
|
|
83
|
+
Wrong:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => (
|
|
87
|
+
<Slider value={field.state.value} onChange={field.handleChange} onChangeEnd={() => void commit()} />
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Correct:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
const SalaryEditor: TMDataGridEditorComponent = ({ field, commit }) => {
|
|
95
|
+
const error = field.state.meta.errors
|
|
96
|
+
.map((e) => (typeof e === "string" ? e : e?.message))
|
|
97
|
+
.find(Boolean);
|
|
98
|
+
return (
|
|
99
|
+
<Slider
|
|
100
|
+
value={field.state.value}
|
|
101
|
+
onChange={field.handleChange}
|
|
102
|
+
onChangeEnd={() => void commit()}
|
|
103
|
+
error={error}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
An entry of `field.state.meta.errors` is a string from a function validator,
|
|
110
|
+
or an issue carrying a `message` from a schema.
|
|
111
|
+
|
|
112
|
+
Source: `src/docs/editors.md` (Writing your own).
|
|
113
|
+
|
|
76
114
|
## HIGH A cross-field rule under `editing.mode: "cell"`
|
|
77
115
|
|
|
78
116
|
Under `"cell"` each cell commits on its own, so a rule spanning two columns
|
|
79
117
|
cannot be satisfied by either one: the first cell edited is rejected against the
|
|
80
118
|
other column's old value, and the row cannot be saved.
|
|
81
119
|
|
|
82
|
-
Correct: `editing.rowValidators` needs `mode: "row"
|
|
83
|
-
|
|
120
|
+
Correct: `editing.rowValidators` needs `mode: "row"`, which validates the
|
|
121
|
+
whole row in one commit.
|
|
84
122
|
|
|
85
123
|
Source: `src/docs/editors.md` (Validation).
|
|
86
124
|
|
|
@@ -120,9 +158,9 @@ and the grid shows the old value as though nothing happened.
|
|
|
120
158
|
Wrong:
|
|
121
159
|
|
|
122
160
|
```tsx
|
|
123
|
-
onCommit: async ({ rowId,
|
|
161
|
+
onCommit: async ({ rowId, value }) => {
|
|
124
162
|
try {
|
|
125
|
-
await api.
|
|
163
|
+
await api.put(rowId, value);
|
|
126
164
|
} catch (error) {
|
|
127
165
|
console.error(error);
|
|
128
166
|
}
|
|
@@ -137,7 +175,8 @@ Source: `src/tmdatagrid/useTMDataGrid.tsx` (`TMDataGridEditingCallbacks`).
|
|
|
137
175
|
## HIGH Submitting an outer form while the grid holds a draft
|
|
138
176
|
|
|
139
177
|
The outer form's array contains only committed rows. Under any mode a mid-edit
|
|
140
|
-
row is invisible to it, and under `
|
|
178
|
+
row is invisible to it, and under `draft: true` every edit is until
|
|
179
|
+
`saveDrafts()`,
|
|
141
180
|
so a form submit saves stale rows and collection rules skip pending values.
|
|
142
181
|
|
|
143
182
|
Correct:
|
|
@@ -154,6 +193,57 @@ if (flushed) await form.handleSubmit();
|
|
|
154
193
|
|
|
155
194
|
Source: `src/docs/query-builder.md` (Which mode, Submitting).
|
|
156
195
|
|
|
196
|
+
## HIGH A bulk write built from `begin`, `getForm` and `commit`
|
|
197
|
+
|
|
198
|
+
`edit.setCellValue(rowId, columnId, value)` writes one cell and commits its row, so a fill over the selection is one call per row.
|
|
199
|
+
Reaching instead for `begin`, then `getForm(rowId)?.setFieldValue(...)`, then `commit` looks equivalent and is not: under `editing.mode: "cell"`, `begin` defers behind a pending commit whenever another row is open, so `getForm(rowId)` returns `undefined` immediately after it and the optional chain writes nothing.
|
|
200
|
+
The `commit` that follows finds no form and resolves `true`, reporting a write that never happened.
|
|
201
|
+
|
|
202
|
+
Wrong:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
for (const row of grid.table.getSelectedRowModel().rows) {
|
|
206
|
+
grid.edit.begin({ rowId: row.id, columnId: "targetPct" });
|
|
207
|
+
grid.edit.getForm(row.id)?.setFieldValue("targetPct", next(row.original));
|
|
208
|
+
await grid.edit.commit(row.id);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Correct:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
for (const row of grid.table.getSelectedRowModel().rows) {
|
|
216
|
+
await grid.edit.setCellValue(row.id, "targetPct", next(row.original));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Several cells of one row: one commit, one draft entry.
|
|
220
|
+
await grid.edit.setRowValues(rowId, { status: "Closed", closedOn: today() });
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Both write the stored value through the row's form, so `meta.edit.mapValue` does not run - no editor is involved - while `meta.edit.validate` does, at the commit.
|
|
224
|
+
A refused value leaves the row open carrying its errors and the call resolves `false`, so read the result rather than assuming the fill landed.
|
|
225
|
+
|
|
226
|
+
Source: `src/tmdatagrid/core/editEngine.ts` (`begin`, `writeFields`).
|
|
227
|
+
|
|
228
|
+
## MEDIUM A computed column frozen while a row is edited
|
|
229
|
+
|
|
230
|
+
A held draft is displayed by the column that owns the field. A column computed
|
|
231
|
+
from other fields - `accessorFn` or `display` - reads `row.original`, which is
|
|
232
|
+
`data`, so it keeps showing the saved record while the values it derives from
|
|
233
|
+
are being typed.
|
|
234
|
+
|
|
235
|
+
Correct: read the drafted row from `edit.store` inside the cell renderer:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
function useDraftedRow(rowId: string, original: Product): Product {
|
|
239
|
+
const { edit } = useTMDataGridContext();
|
|
240
|
+
const values = useSelector(edit.store, (state) => state.rows[rowId]?.values);
|
|
241
|
+
return (values as Product | undefined) ?? original;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Source: `src/docs/editing.md` (Draft lifetime).
|
|
246
|
+
|
|
157
247
|
## MEDIUM Reading a commit's result as the saved value
|
|
158
248
|
|
|
159
249
|
`edit.commit(rowId)`, `edit.commitAll()` and `edit.saveDrafts()` resolve to a
|
|
@@ -168,10 +258,10 @@ notifications.show({ message: saved ? "Saved" : "Some rows need attention" });
|
|
|
168
258
|
|
|
169
259
|
Source: `src/tmdatagrid/core/editEngine.ts` (`TMDataGridEditApi`).
|
|
170
260
|
|
|
171
|
-
## MEDIUM Expecting `editing.onRowDelete` to fire under draft
|
|
261
|
+
## MEDIUM Expecting `editing.onRowDelete` to fire under a draft store
|
|
172
262
|
|
|
173
|
-
|
|
174
|
-
Under `
|
|
263
|
+
Without `editing.draft`, `edit.deleteRow` calls `editing.onRowDelete` at once.
|
|
264
|
+
Under `draft: true` it only toggles a deletion mark, so nothing is removed until
|
|
175
265
|
`saveDrafts`: the ids arrive as `deleted` in `editing.onSaveDrafts`, or, with
|
|
176
266
|
no such callback, in the per-row `editing.onRowDelete` loop. A confirmation
|
|
177
267
|
placed inside `editing.onRowDelete` therefore guards the save, not the trash.
|
|
@@ -8,27 +8,30 @@ kind.
|
|
|
8
8
|
| Name | Type | Default | What it does |
|
|
9
9
|
| --- | --- | --- | --- |
|
|
10
10
|
| `editing` | `TMDataGridEditingOptions` | off | The editing namespace. Setting it turns editing on. |
|
|
11
|
-
| `editing.mode` | `"cell" \| "cellConfirm" \| "row"
|
|
11
|
+
| `editing.mode` | `"cell" \| "cellConfirm" \| "row"` | – | What counts as a commit, and which controls trigger it. |
|
|
12
|
+
| `editing.draft` | `boolean` | `false` | Where a commit goes. On, it parks in the grid's draft store until `edit.saveDrafts()`. |
|
|
12
13
|
| `getRowId` | `(row) => string` | – | A TanStack table option, required once `editing` is set. Drafts are keyed by it. |
|
|
14
|
+
| `editing.columns` | `ReadonlyArray<string>` | every column mapping to a data path | The column ids that take edits. Gates before `meta.edit`, never past it: a column left out takes no edits whatever its meta says, and a listed column still answers to its `meta.edit.enabled`. Also decides which cells an entry row opens. |
|
|
13
15
|
| `editing.isRowEditable` | `(row) => boolean` | – | Closes a whole row to editing, in every mode. |
|
|
14
16
|
| `editing.rowValidators` | `TMDataGridRowValidators` | – | Form-level validation. Cross-field rules live here. |
|
|
17
|
+
| `editing.tableValidators` | `TMDataGridTableValidators` | – | Cross-row rules, handed the collection with every draft overlaid. |
|
|
15
18
|
| `editing.newRowDefaults` | `TData \| (() => TData)` | – | Seeds the entry row's form. A function is called per added row. |
|
|
16
|
-
| `editing.newRowsSticky` | `boolean` | `false` |
|
|
19
|
+
| `editing.newRowsSticky` | `boolean` | `false` | `draft: true` only. Keeps entered new rows pinned in the entry block until the save, instead of letting them scroll with the body. |
|
|
17
20
|
| `cellSelection` | `"none" \| "single" \| "range"` | `"single"` while `editing` is set | Editing turns the cell cursor on; set it explicitly to override. |
|
|
18
21
|
|
|
19
22
|
Passing any other member of `editing` without `mode` is a compile error, and
|
|
20
|
-
`onSaveDrafts` and `newRowsSticky` exist only in the `
|
|
21
|
-
type.
|
|
23
|
+
`onSaveDrafts` and `newRowsSticky` exist only in the `draft: true` branch of
|
|
24
|
+
the type.
|
|
22
25
|
|
|
23
26
|
## Callbacks
|
|
24
27
|
|
|
25
28
|
| Name | Argument | What it does |
|
|
26
29
|
| --- | --- | --- |
|
|
27
30
|
| `editing.onCommit` | `{ rowId, value, original, changes, source }` | Applies one row's change. Reject to keep the draft and show the error. |
|
|
28
|
-
| `editing.onSaveDrafts` | `{
|
|
29
|
-
| `editing.onCommitDrafts` | `{
|
|
31
|
+
| `editing.onSaveDrafts` | `{ updated, created, deleted }` | `draft: true` only. One call for the whole draft store. Without it, `saveDrafts` loops `editing.onCommit`, `editing.onRowAdd` and `editing.onRowDelete`. |
|
|
32
|
+
| `editing.onCommitDrafts` | `{ updated, created, deleted }` | **Deprecated** - renamed to `onSaveDrafts`. Still honoured; the new name wins if both are set. |
|
|
30
33
|
| `editing.onRowAdd` | `{ tempId, value }` | Commits an entry row. Mint the real id here. |
|
|
31
|
-
| `editing.onRowDelete` | `{ rowId, row }` | Deletes a row
|
|
34
|
+
| `editing.onRowDelete` | `{ rowId, row }` | Deletes a row. Shows the trash; under `draft: true`, `onSaveDrafts` shows it too. |
|
|
32
35
|
|
|
33
36
|
`changes` entries are `{ columnId, field, previous, next }`; `field` is the data
|
|
34
37
|
path, which may be dotted.
|
|
@@ -51,7 +54,7 @@ path, which may be dotted.
|
|
|
51
54
|
| Member | Signature | Notes |
|
|
52
55
|
| --- | --- | --- |
|
|
53
56
|
| `begin` | `({ rowId, columnId }) => void` | Row mode opens the entire row either way. `columnId` selects which cell takes the caret; `null` (the pencil) uses its first editable one. On a committed row it reopens it, taking it back out of the draft store. |
|
|
54
|
-
| `commit` | `(rowId) => Promise<boolean>` | The OK gesture: submits the row's form. `false` keeps it open with its errors. Under `
|
|
57
|
+
| `commit` | `(rowId) => Promise<boolean>` | The OK gesture: submits the row's form. `false` keeps it open with its errors, and the message outlives the editor that found it. Under `draft: true` a pass parks the row - no consumer callback runs until `saveDrafts`. Column rules run whether or not an editor is mounted. |
|
|
55
58
|
| `commitAll` | `() => Promise<boolean>` | Submits every open row. Rows that fail stay open. `false` when one did. |
|
|
56
59
|
| `saveDrafts` | `() => Promise<boolean>` | Sends the draft store. Open rows are left alone and stay open. |
|
|
57
60
|
| `cancel` | `(rowId) => void` | Drops one draft. |
|
|
@@ -59,11 +62,14 @@ path, which may be dotted.
|
|
|
59
62
|
| `deactivate` | `() => void` | Closes the editor without touching the draft, as blur does under `"cellConfirm"`. |
|
|
60
63
|
| `submitAll` | `() => Promise<boolean>` | **Deprecated** - `commitAll()` then `saveDrafts()`. |
|
|
61
64
|
| `clearCell` | `(rowId, columnId) => Promise<boolean>` | What Delete does: writes the type's empty value and commits. |
|
|
65
|
+
| `setCellValue` | `(rowId, columnId, value) => Promise<boolean>` | Writes one cell and commits the row, with no editor - toolbar actions and bulk fills. The row need not be mounted; a row inside a collapsed group takes the write. `value` is the stored value, so `meta.edit.mapValue` does not run and `meta.edit.validate` does. Under `draft: true` the row parks like any hand-made edit. `false` when the cell takes no edit, or when validation refused the value and left the row open with its errors. |
|
|
66
|
+
| `setRowValues` | `(rowId, values) => Promise<boolean>` | `setCellValue` for several cells of one row in a single commit - one consumer call and one draft entry. Keys are column ids. All or nothing: if any named cell takes no edit, nothing is written and it resolves `false`. |
|
|
62
67
|
| `addRow` | `(values?) => string` | Opens an entry row, returns its `tempId`. `values` overrides `editing.newRowDefaults` key by key for that row; with no argument the row is `newRowDefaults` alone. |
|
|
63
68
|
| `addRows` | `(rows, options?) => Promise<{ committed, open }>` | Opens a batch in one write, each row seeded as `addRow` seeds. `{ commit: true }` submits each as it lands - valid rows commit, invalid ones stay open with their errors. |
|
|
64
|
-
| `deleteRow` | `(rowId) => void` | `editing.onRowDelete
|
|
65
|
-
| `canEditCell` | `(row, column) => boolean` | The check the built-in controls use. |
|
|
69
|
+
| `deleteRow` | `(rowId) => void` | `editing.onRowDelete`, or a deletion mark under `draft: true`. Toggles: a second call restores the row. |
|
|
70
|
+
| `canEditCell` | `(row, column) => boolean` | The check the built-in controls use. Both halves: the column's and the row's. |
|
|
66
71
|
| `canEditRow` | `(row) => boolean` | The pencil's gate. |
|
|
72
|
+
| `isColumnEditable` | `(column) => boolean` | The column's half alone, with no row in hand: it maps to a field, `editing.columns` lists it when that is set, and `meta.edit.enabled` is not `false`. |
|
|
67
73
|
| `canDeleteRows` | `() => boolean` | Whether the delete control should be shown. |
|
|
68
74
|
| `getForm` | `(rowId) => TMDataGridRowEditForm \| undefined` | The row's live `FormApi`. |
|
|
69
75
|
| `state` | `TMDataGridEditState` | Snapshot, for reads outside React. |
|
|
@@ -91,10 +97,10 @@ type TMDataGridEditState = {
|
|
|
91
97
|
}
|
|
92
98
|
>;
|
|
93
99
|
// The draft store's edit slice: existing rows whose form passed its submit,
|
|
94
|
-
// parked for `saveDrafts`. Empty
|
|
100
|
+
// parked for `saveDrafts`. Empty without `editing.draft`.
|
|
95
101
|
committedRowIds: ReadonlyArray<string>;
|
|
96
|
-
// `committed` is
|
|
97
|
-
//
|
|
102
|
+
// `committed` is "in the store, awaiting the save". Without `editing.draft`
|
|
103
|
+
// a commit adds through `onRowAdd`, so it stays `false`.
|
|
98
104
|
newRows: ReadonlyArray<{ tempId: string; committed: boolean }>;
|
|
99
105
|
deletedRowIds: ReadonlyArray<string>;
|
|
100
106
|
};
|
|
@@ -104,8 +110,8 @@ type TMDataGridEditState = {
|
|
|
104
110
|
|
|
105
111
|
| Name | Kind | What it is |
|
|
106
112
|
| --- | --- | --- |
|
|
107
|
-
| `TMDataGrid.
|
|
108
|
-
| `
|
|
113
|
+
| `TMDataGrid.DraftActions` | Component | Save with the draft-store count, Discard, and a note counting the rows still open. Any mode; renders nothing while editing is off. Takes `renderActions` over `{ state, actions, Controls }` - `state.openRowIds` and `actions.scrollToFirstOpenRow(align?)` reach the rows left open. |
|
|
114
|
+
| `TMDataGridDraftActions` | Export | The same component, for use outside the namespace. |
|
|
109
115
|
| `EDIT_COLUMN_ID` | Export | `"__edit__"`, the generated edit lane's id. |
|
|
110
116
|
| `clearedValueForType` | Export | `(type) => unknown` - what Delete writes per column type. |
|
|
111
117
|
| `getEditFieldName` | Export | `(column) => string` - the data path a column's edits write to. |
|
|
@@ -125,20 +131,24 @@ Generated, pinned right, id `EDIT_COLUMN_ID`. It appears when any of these
|
|
|
125
131
|
holds:
|
|
126
132
|
|
|
127
133
|
- `editing.mode: "row"` - the lane is Save and Cancel's home
|
|
128
|
-
- `editing.
|
|
129
|
-
|
|
134
|
+
- `editing.draft` - the lane is the change indicator and the per-row revert,
|
|
135
|
+
and `editing.onSaveDrafts` is not required for it
|
|
130
136
|
- `editing.onRowDelete` is set - the trash can has somewhere to report to
|
|
131
137
|
|
|
132
|
-
`"cell"` and `"cellConfirm"` have no lane unless `editing.
|
|
133
|
-
one.
|
|
138
|
+
`"cell"` and `"cellConfirm"` have no lane unless `editing.draft` or
|
|
139
|
+
`editing.onRowDelete` asks for one.
|
|
134
140
|
|
|
135
|
-
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
The trash itself shows when the deletion has somewhere to report to:
|
|
142
|
+
`onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is - a deletion
|
|
143
|
+
mark is part of the save.
|
|
144
|
+
|
|
145
|
+
What the lane holds follows the row's state, one axis each. An open row shows
|
|
146
|
+
the mode's own controls, `save-row` and `cancel-row`. A parked row shows
|
|
147
|
+
`row-state` instead, whose `data-state` is `new`, `edited` or `deleted`,
|
|
148
|
+
together with `revert-row` on an edited row, `restore-row` on one marked for
|
|
149
|
+
deletion, and `edit-row` plus `discard-new-row` on an entered new row - never a
|
|
150
|
+
save, which the engine would only park again. A parked row hides `delete-row`.
|
|
151
|
+
Every control carries a tooltip from the labels, `revertRow`, `rowStateNew`,
|
|
142
152
|
`rowStateEdited` and `rowStateDeleted` among them.
|
|
143
153
|
|
|
144
154
|
## Styling and test hooks
|
|
@@ -147,16 +157,16 @@ control carries a tooltip from the labels, `revertRow`, `rowStateNew`,
|
|
|
147
157
|
| --- | --- | --- |
|
|
148
158
|
| `--dg-entry-height` | CSS variable | Height of the sticky entry block. From `size`. |
|
|
149
159
|
| `--dg-row-new-bg` | CSS variable | Background of an entered new row. A green tint. |
|
|
150
|
-
| `data-deleted` | Row attribute | On a row marked for deletion under draft
|
|
160
|
+
| `data-deleted` | Row attribute | On a row marked for deletion under `draft: true`. |
|
|
151
161
|
| `data-dirty` | Row attribute | On a body row holding a dirty draft. Also on the cell whose field is dirty. |
|
|
152
162
|
| `data-new` / `data-committed` | Entry row attributes | On an entry row; `data-committed` once it is committed, awaiting the save. |
|
|
153
163
|
| `data-dg-entry-flow-block` | Attribute | The in-flow block above the body rows holding entered new rows, unless `editing.newRowsSticky` keeps them in the sticky entry block. |
|
|
154
164
|
| `data-dg-part="editor-input"` | Part | The control inside an editing cell. |
|
|
155
|
-
| `data-dg-part="save-row"` / `"cancel-row"` | Parts | The edit lane's buttons on an open row, with `data-row-id`.
|
|
156
|
-
| `data-dg-part="row-state"` | Part |
|
|
165
|
+
| `data-dg-part="save-row"` / `"cancel-row"` | Parts | The edit lane's buttons on an open row, with `data-row-id`. |
|
|
166
|
+
| `data-dg-part="row-state"` | Part | The draft store's change marker, with `data-row-id` and `data-state` of `new`, `edited` or `deleted`. |
|
|
157
167
|
| `data-dg-part="revert-row"` / `"restore-row"` | Parts | Drops a row's draft, and undoes a deletion mark, with `data-row-id`. |
|
|
158
168
|
| `data-dg-part="edit-row"` / `"delete-row"` | Parts | The idle lane's pencil and trash, with `data-row-id`. `edit-row` also reopens an entered new row. |
|
|
159
169
|
| `data-dg-part="confirm-new-row"` / `"discard-new-row"` | Parts | An entry row's ✓ and ✕, with `data-row-id`. |
|
|
160
|
-
| `data-dg-part="save-all"` / `"discard-all"` | Parts | `
|
|
170
|
+
| `data-dg-part="save-all"` / `"discard-all"` | Parts | `DraftActions`. |
|
|
161
171
|
|
|
162
172
|
See the `testing` skill for how to compose these into selectors.
|
|
@@ -8,14 +8,20 @@ bad value being committed. Both follow from the column.
|
|
|
8
8
|
`meta.type` picks one, `meta.options` feeds the two select editors from the same
|
|
9
9
|
declaration the filter panel reads.
|
|
10
10
|
|
|
11
|
-
| `meta.type` | Editor | Export |
|
|
12
|
-
| --- | --- | --- |
|
|
13
|
-
| `"string"` (default) | Text input | `TMDataGridStringEditor` |
|
|
14
|
-
| `"number"` | Number input | `TMDataGridNumberEditor` |
|
|
15
|
-
| `"boolean"` | Checkbox | `TMDataGridBooleanEditor` |
|
|
16
|
-
| `"date"` | Native `<input type="date"
|
|
17
|
-
| `"select"` | Searchable select, commits on pick under `"cell"` | `TMDataGridSelectEditor` |
|
|
18
|
-
| `"multiSelect"` | Multi-select, same source | `TMDataGridMultiSelectEditor` |
|
|
11
|
+
| `meta.type` | Editor | Writes | Export |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `"string"` (default) | Text input | `string` | `TMDataGridStringEditor` |
|
|
14
|
+
| `"number"` | Number input | `number`, or `null` while the cell is empty or the text is not yet a number | `TMDataGridNumberEditor` |
|
|
15
|
+
| `"boolean"` | Checkbox | `boolean` | `TMDataGridBooleanEditor` |
|
|
16
|
+
| `"date"` | Native `<input type="date">` | A `Date`, or the ISO `"YYYY-MM-DD"` string; `null` when cleared | `TMDataGridDateEditor` |
|
|
17
|
+
| `"select"` | Searchable select, commits on pick under `"cell"` | `string \| null` | `TMDataGridSelectEditor` |
|
|
18
|
+
| `"multiSelect"` | Multi-select, same source | `string[]` | `TMDataGridMultiSelectEditor` |
|
|
19
|
+
|
|
20
|
+
**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`.
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
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.
|
|
24
|
+
A validator for a date column has to accept whichever of the two that column's data holds.
|
|
19
25
|
|
|
20
26
|
```tsx
|
|
21
27
|
columnHelper.accessor("department", {
|
|
@@ -57,6 +63,12 @@ Bind any control to `field` exactly as inside any TanStack Form:
|
|
|
57
63
|
`field.state.value`, `field.state.meta.errors`, `field.handleChange`,
|
|
58
64
|
`field.handleBlur`.
|
|
59
65
|
|
|
66
|
+
Binding `field.state.meta.errors` is what shows a refused commit: the built-in
|
|
67
|
+
editors pass the first error to the input's `error` prop, and an editor that
|
|
68
|
+
binds nothing leaves a blocked save as `data-invalid` on the cell with no
|
|
69
|
+
message on screen. An entry is a string from a function validator, or an issue
|
|
70
|
+
carrying a `message` from a schema.
|
|
71
|
+
|
|
60
72
|
```tsx
|
|
61
73
|
import { Slider } from "@mantine/core";
|
|
62
74
|
import type { TMDataGridEditorComponent } from "@jielga/tmdatagrid";
|
|
@@ -155,6 +167,9 @@ Left unmapped on purpose:
|
|
|
155
167
|
edited, mark a pristine row dirty and swallow the select-all.
|
|
156
168
|
- `edit.clearCell()`, the Delete key: it writes the type's empty value through
|
|
157
169
|
the form, so there is no input to map.
|
|
170
|
+
- `edit.setCellValue()` and `edit.setRowValues()`: they write through the form
|
|
171
|
+
too, and the caller passes the stored value itself. `meta.edit.validate` still
|
|
172
|
+
runs on the commit.
|
|
158
173
|
- An editor calling `field.setValue`. `handleChange` is the mapped path.
|
|
159
174
|
|
|
160
175
|
The built-in string and number editors restore the caret after a mapped write,
|
|
@@ -209,12 +224,46 @@ const grid = useTMDataGrid({
|
|
|
209
224
|
```
|
|
210
225
|
|
|
211
226
|
Issues with a path land on the matching column's cell; pathless issues land on
|
|
212
|
-
the row
|
|
213
|
-
|
|
227
|
+
the row, where the message shows in the edit lane's tooltip - on the open
|
|
228
|
+
row's ✓, and on the parked row's marker. A nested schema's issues follow the
|
|
229
|
+
same rule, so a `address.city` issue lands on the column whose `editField` is
|
|
230
|
+
`"address.city"`.
|
|
214
231
|
|
|
215
232
|
Cross-field rules need a mode that commits the whole row at once. Under `"cell"`
|
|
216
233
|
each cell commits alone, so the rule is evaluated against the other column's
|
|
217
|
-
unedited value and cannot pass. Use `"row"
|
|
234
|
+
unedited value and cannot pass. Use `"row"`.
|
|
235
|
+
|
|
236
|
+
## Cross-row rules
|
|
237
|
+
|
|
238
|
+
`editing.tableValidators` holds the rules that need the other rows. Its
|
|
239
|
+
`onSubmit` / `onSubmitAsync` receive `{ value, rowId, isNew, rows }`:
|
|
240
|
+
`value` is the committing row as drafted, and `rows` is
|
|
241
|
+
`Array<{ rowId, value }>` - the collection as it would stand if the commit
|
|
242
|
+
landed, with every draft overlaid, entry rows appended and deletion-marked
|
|
243
|
+
rows removed. `rows` is unfiltered and never contains group rows.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
editing: {
|
|
247
|
+
mode: "cell",
|
|
248
|
+
draft: true,
|
|
249
|
+
tableValidators: {
|
|
250
|
+
onSubmit: ({ value, rowId, rows }) =>
|
|
251
|
+
rows.some((r) => r.rowId !== rowId && r.value.code === value.code)
|
|
252
|
+
? { fields: { code: "Codes must be unique" } }
|
|
253
|
+
: undefined,
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The result is the `rowValidators` vocabulary: nothing passes, a string is a
|
|
259
|
+
row-level message, `{ form, fields }` lands pathed issues on the committing
|
|
260
|
+
row's cells. `onSubmit` runs first, and its failure stands without
|
|
261
|
+
`onSubmitAsync` running. Errors land on the committing row only.
|
|
262
|
+
|
|
263
|
+
The rules run at every commit - typed, ✓, `edit.setCellValue`, an entry
|
|
264
|
+
row's - after the row's own validators, and again for every parked row during
|
|
265
|
+
`saveDrafts`: a draft that a later edit invalidated fails there, keeps its
|
|
266
|
+
markers, and the save resolves `false`.
|
|
218
267
|
|
|
219
268
|
## Server-side errors
|
|
220
269
|
|
|
@@ -245,9 +294,11 @@ row.
|
|
|
245
294
|
| The cell's own value | A held draft is displayed: the cell renders the draft value through the column's `cell` renderer, in every mode |
|
|
246
295
|
| Blue cell corner | The field is dirty against its original value |
|
|
247
296
|
| Red cell corner | The field carries a validation error |
|
|
248
|
-
| Row error text | A pathless rule failed, or a commit was rejected |
|
|
297
|
+
| Row error text | A pathless rule failed, or a commit was rejected. In the lane's tooltip: the open row's ✓, or the parked row's marker |
|
|
249
298
|
| `data-dirty` on the row | The row holds a dirty draft |
|
|
250
299
|
|
|
251
300
|
The same information is readable from `edit.store`: `rows[rowId].dirtyFields`,
|
|
252
|
-
`rows[rowId].errorFields`, `rows[rowId].
|
|
253
|
-
`rows[rowId].
|
|
301
|
+
`rows[rowId].errorFields`, `rows[rowId].errorMessages` (`{ field, message }`
|
|
302
|
+
pairs), `rows[rowId].hasRowError`, `rows[rowId].isSubmitting`, and
|
|
303
|
+
`rows[rowId].values` for the draft itself. The pathless message's text is not
|
|
304
|
+
in the store; read it from `edit.getForm(rowId)?.state.errors`.
|
|
@@ -4,14 +4,14 @@ description: >
|
|
|
4
4
|
Set up TMDataGrid, a compound React data grid built on TanStack Table v9 and
|
|
5
5
|
Mantine. Covers useTMDataGrid, the TMDataGrid root, context, the component
|
|
6
6
|
catalog (Table, Footer, Toolbar, Spacer, SummaryCount, Search,
|
|
7
|
-
LoadingIndicator,
|
|
7
|
+
LoadingIndicator, DraftActions, FilterButton, ColumnsButton, FilterPanel,
|
|
8
8
|
FilterPills, ColumnsPanel), the size scale and the bounded-height layout
|
|
9
9
|
requirement. Load when adding a grid, choosing which parts to render, or when
|
|
10
10
|
rows do not appear.
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/tmdatagrid'
|
|
14
|
-
library_version: '2.0.0-beta.
|
|
14
|
+
library_version: '2.0.0-beta.7'
|
|
15
15
|
sources:
|
|
16
16
|
- 'Jielga/TMDataGrid:src/docs/getting-started.md'
|
|
17
17
|
- 'Jielga/TMDataGrid:src/docs/anatomy.md'
|
|
@@ -156,7 +156,7 @@ flex column.
|
|
|
156
156
|
| `TMDataGrid.SummaryCount` | `children` | Visible rows out of total. |
|
|
157
157
|
| `TMDataGrid.Search` | `placeholder`, `debounce` (default `250`), `w` (default `220`) | Quick search over every column, debounced into `globalFilter`. Renders nothing under `enableGlobalFilter: false`. |
|
|
158
158
|
| `TMDataGrid.LoadingIndicator` | - | Small spinner while `meta.loading` is `true` and rows stay on screen. |
|
|
159
|
-
| `TMDataGrid.
|
|
159
|
+
| `TMDataGrid.DraftActions` | `renderActions` | Save with the pending count, and Discard. Renders nothing while editing is off - see the `editing` skill. |
|
|
160
160
|
| `TMDataGrid.FilterButton` | - | Toggles filter panel. Renders nothing if no column is filterable. |
|
|
161
161
|
| `TMDataGrid.ColumnsButton` | - | Opens column manager. Renders nothing if no column is hideable. |
|
|
162
162
|
| `TMDataGrid.FilterPanel` | - | Rendered by `.Table`; exported for custom layouts. Header close button, Escape, click-away, "Add filter" and "Clear all". |
|
package/skills/grouping/SKILL.md
CHANGED
|
@@ -14,7 +14,7 @@ description: >
|
|
|
14
14
|
metadata:
|
|
15
15
|
type: core
|
|
16
16
|
library: '@jielga/tmdatagrid'
|
|
17
|
-
library_version: '2.0.0-beta.
|
|
17
|
+
library_version: '2.0.0-beta.7'
|
|
18
18
|
sources:
|
|
19
19
|
- 'Jielga/TMDataGrid:src/docs/grouping.md'
|
|
20
20
|
- 'Jielga/TMDataGrid:src/docs/summary-row.md'
|
|
@@ -86,7 +86,9 @@ would pass a row that looks real but is the wrong one. Group rows therefore:
|
|
|
86
86
|
- do not fire `onRowClick` or the cell handlers
|
|
87
87
|
- cannot be highlighted, pinned, or given a details panel
|
|
88
88
|
- never edit
|
|
89
|
-
- carry `data-grouped` and `data-depth`, with `--dg-row-group-bg`
|
|
89
|
+
- carry `data-grouped="true"` and `data-depth`, with `--dg-row-group-bg`
|
|
90
|
+
behind them. `data-grouped` is on every row, `"true"` or `"false"`, so match
|
|
91
|
+
the value rather than the bare attribute
|
|
90
92
|
|
|
91
93
|
A group row's checkbox selects every record under it at any depth, including
|
|
92
94
|
records inside collapsed sub-groups, showing a tick once all are selected and a
|
|
@@ -256,7 +258,7 @@ Source: `src/docs/grouping.md` (Server-side grids).
|
|
|
256
258
|
| `isPagingActive` | Export | `(table, features) => boolean` | – | Whether the pager is slicing anything. `false` while grouped. |
|
|
257
259
|
| `--dg-row-group-bg` | CSS variable | colour | Themed | Group row background. |
|
|
258
260
|
| `--dg-summary-height` | CSS variable | length | From `size` | Height of the summary row. |
|
|
259
|
-
| `data-grouped` · `data-depth` | Data attributes | – | – |
|
|
261
|
+
| `data-grouped` · `data-depth` | Data attributes | – | – | `"true"` on group rows (published on every row), and the nesting level on every row. |
|
|
260
262
|
|
|
261
263
|
See also: the `rows` skill for selection and the details lane, and the `data`
|
|
262
264
|
skill for the pager grouping suspends.
|
package/skills/options/SKILL.md
CHANGED
|
@@ -14,7 +14,7 @@ description: >
|
|
|
14
14
|
metadata:
|
|
15
15
|
type: core
|
|
16
16
|
library: '@jielga/tmdatagrid'
|
|
17
|
-
library_version: '2.0.0-beta.
|
|
17
|
+
library_version: '2.0.0-beta.7'
|
|
18
18
|
sources:
|
|
19
19
|
- 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
|
|
20
20
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
|
@@ -66,7 +66,7 @@ rather than forwarded to TanStack.
|
|
|
66
66
|
| `state` | `Partial<TableState>` | – | Controlled state. Each slice requires its `onXChange` - see below. |
|
|
67
67
|
| `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration, see below. |
|
|
68
68
|
| `persist` | `TMDataGridPersistence` | – | State persistence, see below. |
|
|
69
|
-
| `editing` | `TMDataGridEditingOptions` | off | Turns editing on. `mode` (`"cell" \| "cellConfirm" \| "row"
|
|
69
|
+
| `editing` | `TMDataGridEditingOptions` | off | Turns editing on. Two axes: `mode` (`"cell" \| "cellConfirm" \| "row"`) picks what counts as a commit, `draft` picks whether it parks in the draft store; the object also holds `onCommit`, `onSaveDrafts`, `rowValidators`, `isRowEditable`, `newRowDefaults`, `newRowsSticky`, `onRowAdd` and `onRowDelete` - see the `editing` skill. |
|
|
70
70
|
| `labels` | `TMDataGridLabelsOverride` | English | Overrides for the grid's strings and `aria-label`s. |
|
|
71
71
|
|
|
72
72
|
### Default initial state
|
package/skills/rows/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ description: >
|
|
|
17
17
|
metadata:
|
|
18
18
|
type: core
|
|
19
19
|
library: '@jielga/tmdatagrid'
|
|
20
|
-
library_version: '2.0.0-beta.
|
|
20
|
+
library_version: '2.0.0-beta.7'
|
|
21
21
|
sources:
|
|
22
22
|
- 'Jielga/TMDataGrid:src/docs/row-selection.md'
|
|
23
23
|
- 'Jielga/TMDataGrid:src/docs/row-interaction.md'
|
|
@@ -194,7 +194,7 @@ Returning an empty list leaves the column with no menu button at all.
|
|
|
194
194
|
striped
|
|
195
195
|
rowStyle={(row) =>
|
|
196
196
|
row.original.status === "Terminated"
|
|
197
|
-
? { "--row-bg": "var(--mantine-color-red-
|
|
197
|
+
? { "--row-bg": "color-mix(in srgb, var(--mantine-color-red-6) 12%, transparent)" }
|
|
198
198
|
: undefined
|
|
199
199
|
}
|
|
200
200
|
rowClassName={(row) => (row.original.overdue ? classes.overdue : undefined)}
|
|
@@ -210,7 +210,11 @@ are not striped.
|
|
|
210
210
|
|
|
211
211
|
Rows carry `data-selected`, `data-selected-bg`, `data-highlighted`,
|
|
212
212
|
`data-grouped`, `data-depth`, `data-context-menu` and `data-row-id`, so a
|
|
213
|
-
stylesheet can target any of it without a callback.
|
|
213
|
+
stylesheet can target any of it without a callback. The boolean attributes are
|
|
214
|
+
published on every row as `"true"` or `"false"`, so match the value
|
|
215
|
+
(`[data-grouped="true"]`), not the bare attribute. Pick a `--row-bg` that
|
|
216
|
+
reads under both colour schemes - a `-0` Mantine shade is near-white and
|
|
217
|
+
unreadable in dark mode; mix a mid shade into transparency instead.
|
|
214
218
|
|
|
215
219
|
## The details panel
|
|
216
220
|
|
|
@@ -10,7 +10,7 @@ description: >
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: core
|
|
12
12
|
library: '@jielga/tmdatagrid'
|
|
13
|
-
library_version: '2.0.0-beta.
|
|
13
|
+
library_version: '2.0.0-beta.7'
|
|
14
14
|
sources:
|
|
15
15
|
- 'Jielga/TMDataGrid:src/docs/server-side.md'
|
|
16
16
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -11,7 +11,7 @@ description: >
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/tmdatagrid'
|
|
14
|
-
library_version: '2.0.0-beta.
|
|
14
|
+
library_version: '2.0.0-beta.7'
|
|
15
15
|
sources:
|
|
16
16
|
- 'Jielga/TMDataGrid:src/docs/testing.md'
|
|
17
17
|
- 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'
|
|
@@ -3,7 +3,7 @@ import { type CSSProperties, type ReactNode, useMemo } from "react";
|
|
|
3
3
|
import classes from "./TMDataGrid.module.css";
|
|
4
4
|
import { TMDataGridColumnsPanel } from "./TMDataGridColumnsPanel";
|
|
5
5
|
import { TMDataGridContextProvider } from "../TMDataGridContext";
|
|
6
|
-
import {
|
|
6
|
+
import { TMDataGridDraftActions } from "./TMDataGridDraftActions";
|
|
7
7
|
import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
|
|
8
8
|
import { TMDataGridFilterPills } from "./TMDataGridFilterPills";
|
|
9
9
|
import { TMDataGridFooter } from "./TMDataGridFooter";
|
|
@@ -151,7 +151,7 @@ export const TMDataGrid = Object.assign(TMDataGridRoot, {
|
|
|
151
151
|
SummaryCount: TMDataGridSummaryCount,
|
|
152
152
|
LoadingIndicator: TMDataGridLoadingIndicator,
|
|
153
153
|
Search: TMDataGridSearch,
|
|
154
|
-
|
|
154
|
+
DraftActions: TMDataGridDraftActions,
|
|
155
155
|
ColumnsButton: TMDataGridColumnsButton,
|
|
156
156
|
FilterButton: TMDataGridFilterButton,
|
|
157
157
|
Table: TMDataGridTable,
|