@jielga/tmdatagrid 1.0.2 → 1.1.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 +17 -14
- package/dist/index.d.ts +193 -21
- package/dist/index.js +1914 -1802
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +1 -1
- package/skills/cell-selection/SKILL.md +4 -4
- package/skills/columns/SKILL.md +1 -1
- package/skills/data/SKILL.md +33 -20
- package/skills/editing/SKILL.md +100 -103
- package/skills/editing/references/editing-api.md +5 -2
- package/skills/editing/references/editors-and-validation.md +7 -1
- package/skills/filtering/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +23 -17
- package/skills/grouping/SKILL.md +1 -1
- package/skills/options/SKILL.md +1 -1
- package/skills/rows/SKILL.md +50 -15
- package/skills/rows/references/rows-api.md +3 -2
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +1 -1
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +132 -32
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +223 -95
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +41 -4
- package/src/tmdatagrid/components/TMDataGridTable.tsx +218 -67
- package/src/tmdatagrid/core/editEngine.ts +28 -20
- package/src/tmdatagrid/index.ts +17 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jielga/tmdatagrid",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "A React data grid built on TanStack Table v9 and Mantine - always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,7 @@ description: >
|
|
|
14
14
|
metadata:
|
|
15
15
|
type: core
|
|
16
16
|
library: '@jielga/tmdatagrid'
|
|
17
|
-
library_version: '1.
|
|
17
|
+
library_version: '1.1.1'
|
|
18
18
|
sources:
|
|
19
19
|
- 'Jielga/TMDataGrid:src/docs/cell-selection.md'
|
|
20
20
|
- 'Jielga/TMDataGrid:src/tmdatagrid/core/cellNavigation.ts'
|
|
@@ -125,7 +125,7 @@ Excel's own copy carries no header row either.
|
|
|
125
125
|
|
|
126
126
|
Right-clicking inside the selection opens Copy, "Export as CSV for Excel" and an
|
|
127
127
|
"Include headers" toggle. A right-click outside it moves the selection there
|
|
128
|
-
first, the way a spreadsheet does. Your own `
|
|
128
|
+
first, the way a spreadsheet does. Your own `renderRowContextMenu` items are appended
|
|
129
129
|
below a divider, so nothing is lost by turning cell selection on.
|
|
130
130
|
|
|
131
131
|
The CSV is written for a Nordic Excel: a `sep=;` first line, a UTF-8 BOM, CRLF
|
|
@@ -236,5 +236,5 @@ Source: `src/docs/cell-selection.md` (Copy and export).
|
|
|
236
236
|
| `data-edge-top` · `-bottom` · `-left` · `-right` | Data attributes | – | – | On cells at the rectangle's border. |
|
|
237
237
|
|
|
238
238
|
See also: the `editing` skill, which turns this on implicitly and takes over
|
|
239
|
-
Enter and F2, and the `rows` skill for `
|
|
240
|
-
|
|
239
|
+
Enter and F2, and the `rows` skill for `renderRowContextMenu`, whose
|
|
240
|
+
`internalItems` are exactly the copy and export items described here.
|
package/skills/columns/SKILL.md
CHANGED
package/skills/data/SKILL.md
CHANGED
|
@@ -3,19 +3,19 @@ name: data
|
|
|
3
3
|
description: >
|
|
4
4
|
How a TMDataGrid presents the rows it has: pagination, virtualization, and
|
|
5
5
|
having nothing to show. Covers the three pagination modes (off,
|
|
6
|
-
enablePagination, manualPagination), the Footer's
|
|
7
|
-
getTMDataGridPaginationApi, isPagingActive versus
|
|
8
|
-
virtualization with overscan and meta.rowHeight,
|
|
9
|
-
the edge callbacks onScrollToBottom /
|
|
10
|
-
better for loading more, the header and
|
|
11
|
-
four empty states in precedence order with
|
|
12
|
-
hasActiveFilters, TMDataGrid.LoadingIndicator
|
|
13
|
-
Load when adding a pager, tuning scrolling,
|
|
14
|
-
what an empty grid should say.
|
|
6
|
+
enablePagination, manualPagination), the Footer's renderPagination slot over {
|
|
7
|
+
state, actions, Controls }, getTMDataGridPaginationApi, isPagingActive versus
|
|
8
|
+
canPaginate, always-on virtualization with overscan and meta.rowHeight,
|
|
9
|
+
scrollToRow and scrollerRef, the edge callbacks onScrollToBottom /
|
|
10
|
+
onScrollToRight and why onReachEnd is better for loading more, the header and
|
|
11
|
+
pinned-lane depth shadows, and the four empty states in precedence order with
|
|
12
|
+
meta.loading, renderEmptyState, hasActiveFilters, TMDataGrid.LoadingIndicator
|
|
13
|
+
and TMDataGrid.SummaryCount. Load when adding a pager, tuning scrolling,
|
|
14
|
+
scrolling to a row, or deciding what an empty grid should say.
|
|
15
15
|
metadata:
|
|
16
16
|
type: core
|
|
17
17
|
library: '@jielga/tmdatagrid'
|
|
18
|
-
library_version: '1.
|
|
18
|
+
library_version: '1.1.1'
|
|
19
19
|
sources:
|
|
20
20
|
- 'Jielga/TMDataGrid:src/docs/pagination.md'
|
|
21
21
|
- 'Jielga/TMDataGrid:src/docs/scrolling.md'
|
|
@@ -53,28 +53,39 @@ Initial page size is 25, through `initialState.pagination`. `enablePagination`
|
|
|
53
53
|
is one of the two switches the grid defines itself, and the one switch that
|
|
54
54
|
defaults to off.
|
|
55
55
|
|
|
56
|
-
`TMDataGrid.Footer` takes a `
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
`TMDataGrid.Footer` takes a `renderPagination` slot handed three things: the
|
|
57
|
+
`state` the pager shows, the `actions` it can take, and `Controls` - the
|
|
58
|
+
built-in pieces, already wired.
|
|
59
59
|
|
|
60
60
|
```tsx
|
|
61
61
|
<TMDataGrid.Footer
|
|
62
|
-
|
|
62
|
+
renderPagination={({ state, actions, Controls }) => (
|
|
63
63
|
<Group>
|
|
64
|
-
<
|
|
64
|
+
<Controls.PageSize />
|
|
65
|
+
<Button onClick={actions.previousPage} disabled={!state.canPreviousPage}>
|
|
65
66
|
Back
|
|
66
67
|
</Button>
|
|
67
68
|
<Text>
|
|
68
|
-
{
|
|
69
|
+
{state.pageIndex + 1} / {state.pageCount}
|
|
69
70
|
</Text>
|
|
70
|
-
<
|
|
71
|
-
Next
|
|
72
|
-
</Button>
|
|
71
|
+
<Controls.Pager />
|
|
73
72
|
</Group>
|
|
74
73
|
)}
|
|
75
74
|
/>
|
|
76
75
|
```
|
|
77
76
|
|
|
77
|
+
`Controls.PageSize`, `Controls.Range` and `Controls.Pager` are exactly what the
|
|
78
|
+
default footer renders, in that order - so a custom layout keeps the parts it
|
|
79
|
+
likes rather than rebuilding them to look the same, greying out under a
|
|
80
|
+
suspended pager included.
|
|
81
|
+
|
|
82
|
+
`state` carries `pageIndex`, `pageSize`, `pageCount`, `rowCount`,
|
|
83
|
+
`canPreviousPage`, `canNextPage`, `from`, `to` and `isPagingActive`. `actions`
|
|
84
|
+
carries `setPageIndex`, `setPageSize`, `previousPage`, `nextPage`, `firstPage`
|
|
85
|
+
and `lastPage`. `getTMDataGridPaginationApi(table)` returns the same
|
|
86
|
+
`{ state, actions }` outside the Footer; `Controls` are bound to the grid
|
|
87
|
+
context, so they exist only inside the slot.
|
|
88
|
+
|
|
78
89
|
Grouping suspends the pager and wins: it greys out and the range becomes
|
|
79
90
|
`Grouped · all N rows`. `isPagingActive(table, features)` is the live state -
|
|
80
91
|
whether the pager is slicing anything right now - while
|
|
@@ -268,7 +279,9 @@ Source: `src/docs/pagination.md` (Grouping suspends it).
|
|
|
268
279
|
| `initialState.pagination` | Table option | `{ pageIndex, pageSize }` | `{ 0, 25 }` | Where paging starts. A `data` slice, so it persists. |
|
|
269
280
|
| `onPaginationChange` | Table option | `OnChangeFn` | – | Controls the pagination state. |
|
|
270
281
|
| `TMDataGrid.Footer` | Component | `pageSizeOptions`, `pagination` | `[10, 25, 50, 100]` | The footer bar. Renders nothing when paging is off. |
|
|
271
|
-
| `
|
|
282
|
+
| `Footer` `renderPagination` | Slot | `({ state, actions, Controls }) => ReactNode` | Built-in pager | Replaces the pager, and hands over its pieces. |
|
|
283
|
+
| `getTMDataGridPaginationApi` | Export | `(table) => { state, actions }` | – | The pager API, outside the Footer. |
|
|
284
|
+
| `TMDataGridPaginationState` · `TMDataGridPaginationActions` · `TMDataGridPaginationControls` | Exports | types | – | The three halves of the slot argument. |
|
|
272
285
|
| `isPagingActive` | Export | `(table, features) => boolean` | – | Whether the pager is slicing anything right now. |
|
|
273
286
|
| `overscan` | Option | `number` | `6` | Rows kept mounted beyond each edge of the viewport. |
|
|
274
287
|
| `meta.rowHeight` | Option | `number` | From `size` | Row height in pixels. The virtualizer needs a number. |
|
package/skills/editing/SKILL.md
CHANGED
|
@@ -8,16 +8,18 @@ description: >
|
|
|
8
8
|
meta.type, custom editors through meta.editor, field validation with
|
|
9
9
|
meta.validate and cross-field rules with rowValidators (Standard Schema and
|
|
10
10
|
Zod), adding and deleting rows with edit.addRow, newRowDefaults, onRowAdd,
|
|
11
|
-
onRowDelete, the generated edit lane, TMDataGrid.EditActions
|
|
12
|
-
edit engine (begin, commit, cancel,
|
|
13
|
-
making a grid editable, choosing an edit
|
|
14
|
-
editor, validating an edit, or when cells
|
|
11
|
+
onRowDelete, the generated edit lane, TMDataGrid.EditActions with its
|
|
12
|
+
renderActions slot, and the public edit engine (begin, commit, cancel,
|
|
13
|
+
submitAll, getForm, store). Load when making a grid editable, choosing an edit
|
|
14
|
+
mode, wiring a save, writing a cell editor, validating an edit, or when cells
|
|
15
|
+
will not open for editing.
|
|
15
16
|
metadata:
|
|
16
17
|
type: core
|
|
17
18
|
library: '@jielga/tmdatagrid'
|
|
18
|
-
library_version: '1.
|
|
19
|
+
library_version: '1.1.1'
|
|
19
20
|
sources:
|
|
20
21
|
- 'Jielga/TMDataGrid:src/docs/editing.md'
|
|
22
|
+
- 'Jielga/TMDataGrid:src/docs/query-builder.md'
|
|
21
23
|
- 'Jielga/TMDataGrid:src/docs/editors.md'
|
|
22
24
|
- 'Jielga/TMDataGrid:src/tmdatagrid/core/editEngine.ts'
|
|
23
25
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
|
@@ -41,50 +43,26 @@ every wiring question below:
|
|
|
41
43
|
## Setup
|
|
42
44
|
|
|
43
45
|
```tsx
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
columnHelper.accessor("salary", {
|
|
58
|
-
header: "Salary",
|
|
59
|
-
minSize: 130,
|
|
60
|
-
meta: { type: "number", align: "right" },
|
|
61
|
-
}),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
export function Employees({ initial }: { initial: Array<Employee> }) {
|
|
65
|
-
const [employees, setEmployees] = useState(initial);
|
|
66
|
-
|
|
67
|
-
const grid = useTMDataGrid({
|
|
68
|
-
data: employees,
|
|
69
|
-
columns,
|
|
70
|
-
getRowId: (row) => String(row.id),
|
|
71
|
-
editMode: "cell",
|
|
72
|
-
// The grid writes nothing: apply the change, and the edited row arrives
|
|
73
|
-
// back through `data`.
|
|
74
|
-
onEditCommit: ({ rowId, value }) =>
|
|
75
|
-
setEmployees((previous) =>
|
|
76
|
-
previous.map((employee) =>
|
|
77
|
-
String(employee.id) === rowId ? value : employee,
|
|
78
|
-
),
|
|
46
|
+
const [employees, setEmployees] = useState(initial);
|
|
47
|
+
|
|
48
|
+
const grid = useTMDataGrid({
|
|
49
|
+
data: employees,
|
|
50
|
+
columns,
|
|
51
|
+
getRowId: (row) => String(row.id),
|
|
52
|
+
editMode: "cell",
|
|
53
|
+
// The grid writes nothing: apply the change, and the edited row arrives
|
|
54
|
+
// back through `data`.
|
|
55
|
+
onEditCommit: ({ rowId, value }) =>
|
|
56
|
+
setEmployees((previous) =>
|
|
57
|
+
previous.map((employee) =>
|
|
58
|
+
String(employee.id) === rowId ? value : employee,
|
|
79
59
|
),
|
|
80
|
-
|
|
60
|
+
),
|
|
61
|
+
});
|
|
81
62
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</TMDataGrid>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
63
|
+
<TMDataGrid {...grid} style={{ flex: 1, minHeight: 0 }}>
|
|
64
|
+
<TMDataGrid.Table<Employee> />
|
|
65
|
+
</TMDataGrid>;
|
|
88
66
|
```
|
|
89
67
|
|
|
90
68
|
The editing options travel together, and the types say so: passing any of them
|
|
@@ -103,19 +81,23 @@ One axis, four policies over the same engine.
|
|
|
103
81
|
| `"row"` | Save in the edit lane, or Ctrl+Enter | Cancel, or Escape | generated edit lane |
|
|
104
82
|
| `"batch"` | `edit.submitAll()` | `edit.cancelAll()` | `TMDataGrid.EditActions` |
|
|
105
83
|
|
|
106
|
-
Which to pick:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
click: `"cellConfirm"`.
|
|
111
|
-
- The row is the unit of the save, or a rule spans two columns: `"row"`.
|
|
112
|
-
- Many edits reviewed and sent as one transaction: `"batch"`.
|
|
84
|
+
Which to pick: `"cell"` for saved-as-you-go spreadsheet feel; `"cellConfirm"`
|
|
85
|
+
when a stray click must not fire a request; `"row"` when the row is the unit of
|
|
86
|
+
the save or a rule spans two columns; `"batch"` for many edits sent as one
|
|
87
|
+
transaction.
|
|
113
88
|
|
|
114
89
|
An editor opens on double-click, or with the cell cursor on the cell: Enter, F2,
|
|
115
90
|
or just typing, where the character replaces the value. Delete or Backspace
|
|
116
91
|
clears the value and commits without opening an editor. Editing brings cell
|
|
117
92
|
selection with it: `cellSelection` defaults to `"single"` while `editMode` is
|
|
118
|
-
set.
|
|
93
|
+
set. Every open gesture puts the caret in the cell it named, and the grid places
|
|
94
|
+
it rather than the editor - a `meta.editor` needs no `autoFocus` wiring.
|
|
95
|
+
|
|
96
|
+
Under `"row"` the pencil - or a double-click on any cell, which puts the caret
|
|
97
|
+
in the cell clicked - opens every editable cell of the row, and ✓ saves them as
|
|
98
|
+
one commit. Rows accumulate: opening a second row leaves the first open, and
|
|
99
|
+
each row's ✓ and ✕ act on that row alone, so no row is closed or saved to make
|
|
100
|
+
space for the next.
|
|
119
101
|
|
|
120
102
|
Under `"batch"` nothing leaves the grid until `submitAll`. Enter and Tab park
|
|
121
103
|
the draft, dirty-marked, Escape drops that one draft, and drafts accumulate
|
|
@@ -153,37 +135,25 @@ Group rows and the generated lanes (checkbox, row number, details, edit) never
|
|
|
153
135
|
edit.
|
|
154
136
|
|
|
155
137
|
```tsx
|
|
156
|
-
// Computed column
|
|
138
|
+
// Computed column writing back to a real field, and per-row gating.
|
|
157
139
|
columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
|
|
158
140
|
id: "fullName",
|
|
159
141
|
header: "Full name",
|
|
160
142
|
meta: { label: "Full name", editField: "lastName" },
|
|
161
143
|
});
|
|
162
|
-
|
|
163
|
-
// Editable only while the employee is active.
|
|
164
144
|
columnHelper.accessor("salary", {
|
|
165
145
|
header: "Salary",
|
|
166
|
-
meta: {
|
|
167
|
-
type: "number",
|
|
168
|
-
align: "right",
|
|
169
|
-
editable: (row) => row.original.status !== "Terminated",
|
|
170
|
-
},
|
|
146
|
+
meta: { editable: (row) => row.original.status !== "Terminated" },
|
|
171
147
|
});
|
|
172
148
|
```
|
|
173
149
|
|
|
174
150
|
## Editors and validation
|
|
175
151
|
|
|
176
|
-
`meta.type` picks the editor
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
| `"string"` (default) | Text input |
|
|
182
|
-
| `"number"` | Number input |
|
|
183
|
-
| `"boolean"` | Checkbox |
|
|
184
|
-
| `"date"` | Native `<input type="date">`, ISO `YYYY-MM-DD` |
|
|
185
|
-
| `"select"` | Searchable select from `meta.options`; commits on pick under `"cell"` |
|
|
186
|
-
| `"multiSelect"` | Multi-select, same source |
|
|
152
|
+
`meta.type` picks the editor - `"string"` (the default), `"number"`,
|
|
153
|
+
`"boolean"`, `"date"`, `"select"` and `"multiSelect"` - and `meta.options` feeds
|
|
154
|
+
the two select editors from the same declaration the filter panel reads. Each
|
|
155
|
+
one, with the export that wraps it, is in
|
|
156
|
+
[references/editors-and-validation.md](references/editors-and-validation.md#the-built-in-editors).
|
|
187
157
|
|
|
188
158
|
`meta.editor` replaces one, `meta.validate` guards the field, and
|
|
189
159
|
`rowValidators` carries cross-field rules. The validators are TanStack Form's
|
|
@@ -241,11 +211,8 @@ const grid = useTMDataGrid({
|
|
|
241
211
|
the immediate modes, so a confirmation belongs inside that callback. Under batch
|
|
242
212
|
it toggles a mark instead: the row renders struck through and inert
|
|
243
213
|
(`data-deleted`), the lane's trash becomes a restore, and `submitAll` reports
|
|
244
|
-
the ids in `deleted`.
|
|
245
|
-
|
|
246
|
-
Adds and deletes are applied by you, the same as edits. The engine's `tempId`
|
|
247
|
-
(`__new__1`, `__new__2`, …) never needs to become a real id: mint one when you
|
|
248
|
-
create the record.
|
|
214
|
+
the ids in `deleted`. Adds and deletes are applied by you, the same as edits -
|
|
215
|
+
the engine's `tempId` (`__new__1`, …) never needs to become a real id.
|
|
249
216
|
|
|
250
217
|
## The chrome
|
|
251
218
|
|
|
@@ -258,19 +225,29 @@ chrome at all by design.
|
|
|
258
225
|
out while nothing is pending, spins while a submit is in flight, renders nothing
|
|
259
226
|
while editing is off, and works under any mode, not only batch.
|
|
260
227
|
|
|
261
|
-
|
|
228
|
+
`renderActions` replaces the pair while handing over its pieces:
|
|
262
229
|
|
|
263
|
-
|
|
230
|
+
```tsx
|
|
231
|
+
<TMDataGrid.EditActions
|
|
232
|
+
renderActions={({ state, Controls }) => (
|
|
233
|
+
<Group>
|
|
234
|
+
{state.pendingCount > 0 && <Badge>{state.pendingCount}</Badge>}
|
|
235
|
+
<Controls.Save />
|
|
236
|
+
<Controls.Discard />
|
|
237
|
+
</Group>
|
|
238
|
+
)}
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
264
241
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
242
|
+
`state` is `{ pendingCount, isSubmitting }`, `actions` is `{ save, discard }`.
|
|
243
|
+
|
|
244
|
+
## The engine: `edit`
|
|
245
|
+
|
|
246
|
+
`grid.edit` is public, and everything the chrome does goes through it:
|
|
247
|
+
`begin` and `commit`, `cancel` / `cancelAll`, `submitAll`, `addRow` /
|
|
248
|
+
`deleteRow`, `getForm`, and `store` for `useSelector`. Every member with its
|
|
249
|
+
signature, the gates, `clearCell`, `deactivate`, and the `edit.store` shape are
|
|
250
|
+
in [references/editing-api.md](references/editing-api.md#the-edit-engine).
|
|
274
251
|
|
|
275
252
|
```tsx
|
|
276
253
|
import { useSelector } from "@tanstack/react-store";
|
|
@@ -281,15 +258,24 @@ const pendingCount = useSelector(
|
|
|
281
258
|
);
|
|
282
259
|
```
|
|
283
260
|
|
|
284
|
-
`edit.store` holds `active`, `openRowIds`, `rows` (per row `dirtyFields`,
|
|
285
|
-
`errorFields`, `isSubmitting`), `newRows` and `deletedRowIds`. The full member
|
|
286
|
-
list, `clearCell`, `deactivate` and the gates included, is in
|
|
287
|
-
[references/editing-api.md](references/editing-api.md).
|
|
288
|
-
|
|
289
261
|
`getForm` is the *one row, one form* promise made public: render that same form
|
|
290
262
|
in a drawer and it shares values, dirty state and errors with the inline cells,
|
|
291
263
|
because it is the same `FormApi`.
|
|
292
264
|
|
|
265
|
+
## Inside an outer form
|
|
266
|
+
|
|
267
|
+
A `@tanstack/react-form` form can own the row array, with the grid as a
|
|
268
|
+
controlled field: `data` from `field.state.value`, and `onEditCommit` /
|
|
269
|
+
`onRowAdd` / `onRowDelete` composing the next array into `field.handleChange`.
|
|
270
|
+
Use `editMode: "row"` so a row reaches the form on approval, map by row id
|
|
271
|
+
(never index), and mint negative ids for new rows.
|
|
272
|
+
|
|
273
|
+
The validation split is structural, not stylistic: **a rule decidable from one
|
|
274
|
+
row belongs to the grid (`meta.validate`, `rowValidators`); a rule needing the
|
|
275
|
+
other rows or the collection ("has rows", "no duplicates") belongs to the
|
|
276
|
+
form's field validator.** A row's form cannot see the array, and `edit.store`
|
|
277
|
+
publishes field names, never values, so the form cannot see a draft.
|
|
278
|
+
|
|
293
279
|
## Common mistakes
|
|
294
280
|
|
|
295
281
|
### CRITICAL Expecting the grid to write into `data`
|
|
@@ -450,20 +436,31 @@ onEditCommit: async ({ rowId, changes }) => {
|
|
|
450
436
|
|
|
451
437
|
Source: `src/tmdatagrid/useTMDataGrid.tsx` (`onEditCommit`).
|
|
452
438
|
|
|
453
|
-
###
|
|
439
|
+
### HIGH Submitting an outer form while the grid holds a draft
|
|
454
440
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
441
|
+
The outer form's array contains only committed rows. Under any mode a mid-edit
|
|
442
|
+
row is invisible to it, and under `"batch"` *every* edit is until
|
|
443
|
+
`submitAll()` - so a form submit saves stale rows and collection rules pass
|
|
444
|
+
over pending values.
|
|
458
445
|
|
|
459
|
-
|
|
446
|
+
Correct:
|
|
460
447
|
|
|
461
448
|
```tsx
|
|
462
|
-
|
|
463
|
-
|
|
449
|
+
const hasOpenDraft = useSelector(grid.edit.store, (s) => s.openRowIds.length > 0);
|
|
450
|
+
|
|
451
|
+
<Button type="submit" disabled={!canSubmit || hasOpenDraft}>Save</Button>
|
|
452
|
+
// or flush instead of blocking:
|
|
453
|
+
const flushed = await grid.edit.submitAll();
|
|
454
|
+
if (flushed) await form.handleSubmit();
|
|
464
455
|
```
|
|
465
456
|
|
|
466
|
-
|
|
457
|
+
Source: `src/docs/query-builder.md` (Which mode, Submitting).
|
|
458
|
+
|
|
459
|
+
### MEDIUM Reading a commit's result as the saved value
|
|
460
|
+
|
|
461
|
+
`edit.commit(rowId)` and `edit.submitAll()` resolve to a `boolean` saying
|
|
462
|
+
whether the form closed, and resolve `false` when validation or a rejected save
|
|
463
|
+
kept it open. Ignoring the result reports a save that did not happen.
|
|
467
464
|
|
|
468
465
|
```tsx
|
|
469
466
|
const saved = await grid.edit.submitAll();
|
|
@@ -46,7 +46,7 @@ path, which may be dotted.
|
|
|
46
46
|
|
|
47
47
|
| Member | Signature | Notes |
|
|
48
48
|
| --- | --- | --- |
|
|
49
|
-
| `begin` | `({ rowId, columnId }) => void` |
|
|
49
|
+
| `begin` | `({ rowId, columnId }) => void` | Row mode opens the whole row either way; `columnId` says which cell takes the caret, `null` (the pencil) its first editable one. |
|
|
50
50
|
| `commit` | `(rowId) => Promise<boolean>` | `false` keeps the form open with its errors. |
|
|
51
51
|
| `cancel` | `(rowId) => void` | Drops one draft. |
|
|
52
52
|
| `cancelAll` | `() => void` | Drops every draft. |
|
|
@@ -66,7 +66,10 @@ path, which may be dotted.
|
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
68
|
type TMDataGridEditState = {
|
|
69
|
+
// The cell the last open gesture named - where the caret goes.
|
|
69
70
|
active: { rowId: string; columnId: string | null } | null;
|
|
71
|
+
// Rows with a live form. One under `"cell"`; as many as the user opened
|
|
72
|
+
// under `"row"`, `"cellConfirm"` and `"batch"` - which rows are editing.
|
|
70
73
|
openRowIds: ReadonlyArray<string>;
|
|
71
74
|
rows: Record<
|
|
72
75
|
string,
|
|
@@ -85,7 +88,7 @@ type TMDataGridEditState = {
|
|
|
85
88
|
|
|
86
89
|
| Name | Kind | What it is |
|
|
87
90
|
| --- | --- | --- |
|
|
88
|
-
| `TMDataGrid.EditActions` | Component | Save with the pending count, and Discard. Any mode; renders nothing while editing is off. |
|
|
91
|
+
| `TMDataGrid.EditActions` | Component | Save with the pending count, and Discard. Any mode; renders nothing while editing is off. Takes `renderActions` over `{ state, actions, Controls }`. |
|
|
89
92
|
| `TMDataGridEditActions` | Export | The same component, for use outside the namespace. |
|
|
90
93
|
| `EDIT_COLUMN_ID` | Export | `"__edit__"`, the generated edit lane's id. |
|
|
91
94
|
| `clearedValueForType` | Export | `(type) => unknown` - what Delete writes per column type. |
|
|
@@ -43,11 +43,17 @@ type TMDataGridEditorArgs = {
|
|
|
43
43
|
commit: () => Promise<boolean>; // what Enter would do
|
|
44
44
|
cancel: () => void; // what Escape would do
|
|
45
45
|
size: TMDataGridSize;
|
|
46
|
-
autoFocus: boolean; //
|
|
46
|
+
autoFocus: boolean; // deprecated - the grid places the caret itself
|
|
47
47
|
seedText?: string; // set when typing opened the editor
|
|
48
48
|
};
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
The grid places the caret itself, once per open gesture: it focuses
|
|
52
|
+
`data-dg-part="editor-input"` when the editor publishes it, and the first
|
|
53
|
+
focusable element inside the editor otherwise. So a custom editor gets the caret
|
|
54
|
+
without honouring `autoFocus` - set `editor-input` only to name which of several
|
|
55
|
+
inputs should take it.
|
|
56
|
+
|
|
51
57
|
Bind any control to `field` exactly as inside any TanStack Form:
|
|
52
58
|
`field.state.value`, `field.state.meta.errors`, `field.handleChange`,
|
|
53
59
|
`field.handleBlur`.
|
|
@@ -11,7 +11,7 @@ description: >
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/tmdatagrid'
|
|
14
|
-
library_version: '1.
|
|
14
|
+
library_version: '1.1.1'
|
|
15
15
|
sources:
|
|
16
16
|
- 'Jielga/TMDataGrid:src/docs/getting-started.md'
|
|
17
17
|
- 'Jielga/TMDataGrid:src/docs/anatomy.md'
|
|
@@ -149,14 +149,14 @@ flex column.
|
|
|
149
149
|
| Component | Props | Notes |
|
|
150
150
|
| --- | --- | --- |
|
|
151
151
|
| `TMDataGrid` | `table`, `ui`, `features`, `size`, `className`, `style` | Root. Provides context. `style` also takes CSS variables: `--dg-row-selected-bg`, `--dg-row-height`, `--dg-header-height`, `--dg-font-size`, `--dg-padding`. |
|
|
152
|
-
| `TMDataGrid.Table` | `onRowClick(row)`, `
|
|
152
|
+
| `TMDataGrid.Table` | `onRowClick(row)`, `renderRowContextMenu`, `renderColumnMenuItems`, `rowContextMenuProps` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `selectionMode: "row"`. |
|
|
153
153
|
| `TMDataGrid.Footer` | `pageSizeOptions` (default `[10, 25, 50, 100]`), `pagination` render prop | Pagination controls. Renders nothing unless pagination is enabled. |
|
|
154
154
|
| `TMDataGrid.Toolbar` | `children` | Flex row above the grid. |
|
|
155
155
|
| `TMDataGrid.Spacer` | - | Pushes later toolbar items right. |
|
|
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.EditActions` |
|
|
159
|
+
| `TMDataGrid.EditActions` | `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". |
|
|
@@ -169,14 +169,14 @@ Pass the row type so `onRowClick` stays typed:
|
|
|
169
169
|
<TMDataGrid.Table<Employee> onRowClick={(row) => open(row.original.id)} />
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
`
|
|
172
|
+
`renderRowContextMenu` fills the dropdown of a Mantine `Menu` the grid opens at the
|
|
173
173
|
pointer on a right-click (or a long press). The grid owns the `Menu` - opening,
|
|
174
174
|
positioning, and closing on Escape, outside click, body scroll or an item pick -
|
|
175
175
|
so the render prop only says what goes in it:
|
|
176
176
|
|
|
177
177
|
```tsx
|
|
178
178
|
<TMDataGrid.Table<Employee>
|
|
179
|
-
|
|
179
|
+
renderRowContextMenu={({ table, row, cell, close }) => (
|
|
180
180
|
<>
|
|
181
181
|
<Menu.Label>{row.original.firstName}</Menu.Label>
|
|
182
182
|
<Menu.Item onClick={() => open(row.original.id)}>Open</Menu.Item>
|
|
@@ -204,25 +204,31 @@ highlight; the row carries `data-context-menu` while its menu is open. Pass
|
|
|
204
204
|
`options.rowCount`. `TMDataGrid.SummaryCount` uses `meta.totalRowCount` when
|
|
205
205
|
provided, otherwise the pre-filtered row count.
|
|
206
206
|
|
|
207
|
-
The Footer's `
|
|
208
|
-
|
|
207
|
+
The Footer's `renderPagination` slot replaces the built-in pager, and hands
|
|
208
|
+
over the pieces of it - `state`, `actions`, and `Controls` as the built-in
|
|
209
|
+
parts:
|
|
209
210
|
|
|
210
211
|
```tsx
|
|
211
212
|
<TMDataGrid.Footer
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
renderPagination={({ state, actions, Controls }) => (
|
|
214
|
+
<>
|
|
215
|
+
<Controls.PageSize />
|
|
216
|
+
<Pagination
|
|
217
|
+
total={state.pageCount}
|
|
218
|
+
value={state.pageIndex + 1}
|
|
219
|
+
onChange={(page) => actions.setPageIndex(page - 1)}
|
|
220
|
+
/>
|
|
221
|
+
</>
|
|
218
222
|
)}
|
|
219
223
|
/>
|
|
220
224
|
```
|
|
221
225
|
|
|
222
|
-
`
|
|
223
|
-
`
|
|
224
|
-
`setPageSize`, `previousPage`, `nextPage`, `firstPage
|
|
225
|
-
|
|
226
|
+
`state` carries `pageIndex`, `pageSize`, `pageCount`, `rowCount`,
|
|
227
|
+
`canPreviousPage`, `canNextPage`, `from`, `to` and `isPagingActive`; `actions`
|
|
228
|
+
carries `setPageIndex`, `setPageSize`, `previousPage`, `nextPage`, `firstPage`
|
|
229
|
+
and `lastPage`. Outside the Footer,
|
|
230
|
+
`getTMDataGridPaginationApi(grid.table)` returns the same `{ state, actions }`.
|
|
231
|
+
See the `data` skill.
|
|
226
232
|
|
|
227
233
|
## size
|
|
228
234
|
|
package/skills/grouping/SKILL.md
CHANGED
package/skills/options/SKILL.md
CHANGED