@jielga/tmdatagrid 1.0.2 → 1.1.0
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 +172 -16
- package/dist/index.js +1691 -1599
- 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 +82 -75
- package/skills/editing/references/editing-api.md +1 -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 +41 -11
- 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/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 +102 -44
- 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.0
|
|
3
|
+
"version": "1.1.0",
|
|
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.0
|
|
17
|
+
library_version: '1.1.0'
|
|
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.0
|
|
18
|
+
library_version: '1.1.0'
|
|
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.0
|
|
19
|
+
library_version: '1.1.0'
|
|
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,13 +81,10 @@ 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
|
|
@@ -153,21 +128,15 @@ Group rows and the generated lanes (checkbox, row number, details, edit) never
|
|
|
153
128
|
edit.
|
|
154
129
|
|
|
155
130
|
```tsx
|
|
156
|
-
// Computed column
|
|
131
|
+
// Computed column writing back to a real field, and per-row gating.
|
|
157
132
|
columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
|
|
158
133
|
id: "fullName",
|
|
159
134
|
header: "Full name",
|
|
160
135
|
meta: { label: "Full name", editField: "lastName" },
|
|
161
136
|
});
|
|
162
|
-
|
|
163
|
-
// Editable only while the employee is active.
|
|
164
137
|
columnHelper.accessor("salary", {
|
|
165
138
|
header: "Salary",
|
|
166
|
-
meta: {
|
|
167
|
-
type: "number",
|
|
168
|
-
align: "right",
|
|
169
|
-
editable: (row) => row.original.status !== "Terminated",
|
|
170
|
-
},
|
|
139
|
+
meta: { editable: (row) => row.original.status !== "Terminated" },
|
|
171
140
|
});
|
|
172
141
|
```
|
|
173
142
|
|
|
@@ -241,11 +210,8 @@ const grid = useTMDataGrid({
|
|
|
241
210
|
the immediate modes, so a confirmation belongs inside that callback. Under batch
|
|
242
211
|
it toggles a mark instead: the row renders struck through and inert
|
|
243
212
|
(`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.
|
|
213
|
+
the ids in `deleted`. Adds and deletes are applied by you, the same as edits -
|
|
214
|
+
the engine's `tempId` (`__new__1`, …) never needs to become a real id.
|
|
249
215
|
|
|
250
216
|
## The chrome
|
|
251
217
|
|
|
@@ -258,6 +224,22 @@ chrome at all by design.
|
|
|
258
224
|
out while nothing is pending, spins while a submit is in flight, renders nothing
|
|
259
225
|
while editing is off, and works under any mode, not only batch.
|
|
260
226
|
|
|
227
|
+
`renderActions` replaces the pair while handing over its pieces:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<TMDataGrid.EditActions
|
|
231
|
+
renderActions={({ state, Controls }) => (
|
|
232
|
+
<Group>
|
|
233
|
+
{state.pendingCount > 0 && <Badge>{state.pendingCount}</Badge>}
|
|
234
|
+
<Controls.Save />
|
|
235
|
+
<Controls.Discard />
|
|
236
|
+
</Group>
|
|
237
|
+
)}
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`state` is `{ pendingCount, isSubmitting }`, `actions` is `{ save, discard }`.
|
|
242
|
+
|
|
261
243
|
## The engine: `edit`
|
|
262
244
|
|
|
263
245
|
`grid.edit` is public, and everything the chrome does goes through it.
|
|
@@ -290,6 +272,20 @@ list, `clearCell`, `deactivate` and the gates included, is in
|
|
|
290
272
|
in a drawer and it shares values, dirty state and errors with the inline cells,
|
|
291
273
|
because it is the same `FormApi`.
|
|
292
274
|
|
|
275
|
+
## Inside an outer form
|
|
276
|
+
|
|
277
|
+
A `@tanstack/react-form` form can own the row array, with the grid as a
|
|
278
|
+
controlled field: `data` from `field.state.value`, and `onEditCommit` /
|
|
279
|
+
`onRowAdd` / `onRowDelete` composing the next array into `field.handleChange`.
|
|
280
|
+
Use `editMode: "row"` so a row reaches the form on approval, map by row id
|
|
281
|
+
(never index), and mint negative ids for new rows.
|
|
282
|
+
|
|
283
|
+
The validation split is structural, not stylistic: **a rule decidable from one
|
|
284
|
+
row belongs to the grid (`meta.validate`, `rowValidators`); a rule needing the
|
|
285
|
+
other rows or the collection ("has rows", "no duplicates") belongs to the
|
|
286
|
+
form's field validator.** A row's form cannot see the array, and `edit.store`
|
|
287
|
+
publishes field names, never values, so the form cannot see a draft.
|
|
288
|
+
|
|
293
289
|
## Common mistakes
|
|
294
290
|
|
|
295
291
|
### CRITICAL Expecting the grid to write into `data`
|
|
@@ -450,20 +446,31 @@ onEditCommit: async ({ rowId, changes }) => {
|
|
|
450
446
|
|
|
451
447
|
Source: `src/tmdatagrid/useTMDataGrid.tsx` (`onEditCommit`).
|
|
452
448
|
|
|
453
|
-
###
|
|
449
|
+
### HIGH Submitting an outer form while the grid holds a draft
|
|
454
450
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
451
|
+
The outer form's array contains only committed rows. Under any mode a mid-edit
|
|
452
|
+
row is invisible to it, and under `"batch"` *every* edit is until
|
|
453
|
+
`submitAll()` - so a form submit saves stale rows and collection rules pass
|
|
454
|
+
over pending values.
|
|
458
455
|
|
|
459
|
-
|
|
456
|
+
Correct:
|
|
460
457
|
|
|
461
458
|
```tsx
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
const hasOpenDraft = useSelector(grid.edit.store, (s) => s.openRowIds.length > 0);
|
|
460
|
+
|
|
461
|
+
<Button type="submit" disabled={!canSubmit || hasOpenDraft}>Save</Button>
|
|
462
|
+
// or flush instead of blocking:
|
|
463
|
+
const flushed = await grid.edit.submitAll();
|
|
464
|
+
if (flushed) await form.handleSubmit();
|
|
464
465
|
```
|
|
465
466
|
|
|
466
|
-
|
|
467
|
+
Source: `src/docs/query-builder.md` (Which mode, Submitting).
|
|
468
|
+
|
|
469
|
+
### MEDIUM Reading a commit's result as the saved value
|
|
470
|
+
|
|
471
|
+
`edit.commit(rowId)` and `edit.submitAll()` resolve to a `boolean` saying
|
|
472
|
+
whether the form closed, and resolve `false` when validation or a rejected save
|
|
473
|
+
kept it open. Ignoring the result reports a save that did not happen.
|
|
467
474
|
|
|
468
475
|
```tsx
|
|
469
476
|
const saved = await grid.edit.submitAll();
|
|
@@ -85,7 +85,7 @@ type TMDataGridEditState = {
|
|
|
85
85
|
|
|
86
86
|
| Name | Kind | What it is |
|
|
87
87
|
| --- | --- | --- |
|
|
88
|
-
| `TMDataGrid.EditActions` | Component | Save with the pending count, and Discard. Any mode; renders nothing while editing is off. |
|
|
88
|
+
| `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
89
|
| `TMDataGridEditActions` | Export | The same component, for use outside the namespace. |
|
|
90
90
|
| `EDIT_COLUMN_ID` | Export | `"__edit__"`, the generated edit lane's id. |
|
|
91
91
|
| `clearedValueForType` | Export | `(type) => unknown` - what Delete writes per column type. |
|
|
@@ -11,7 +11,7 @@ description: >
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/tmdatagrid'
|
|
14
|
-
library_version: '1.0
|
|
14
|
+
library_version: '1.1.0'
|
|
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
package/skills/rows/SKILL.md
CHANGED
|
@@ -6,17 +6,18 @@ description: >
|
|
|
6
6
|
highlight as state of its own with defaultHighlightedRowId and
|
|
7
7
|
onHighlightedRowChange, showSelectedBackground and --dg-row-selected-bg,
|
|
8
8
|
reading a selection through the table store, onRowClick / onCellClick /
|
|
9
|
-
onCellDoubleClick / onCellContextMenu, the
|
|
10
|
-
rowContextMenuProps, per-row
|
|
11
|
-
the --row-bg rule, the row
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
to an edge, or
|
|
9
|
+
onCellDoubleClick / onCellContextMenu, the renderRowContextMenu slot with its
|
|
10
|
+
internalItems handback, renderColumnMenuItems and rowContextMenuProps, per-row
|
|
11
|
+
styling with rowStyle, rowClassName, striped and the --row-bg rule, the row
|
|
12
|
+
details panel through renderDetails and DETAILS_COLUMN_ID, row pinning with
|
|
13
|
+
enableRowPinning and row.pin, and the row-number gutter through
|
|
14
|
+
enableRowNumbers. Load when selecting rows, reacting to a click, opening a
|
|
15
|
+
detail panel, colouring rows by their data, pinning rows to an edge, or
|
|
16
|
+
numbering them.
|
|
16
17
|
metadata:
|
|
17
18
|
type: core
|
|
18
19
|
library: '@jielga/tmdatagrid'
|
|
19
|
-
library_version: '1.0
|
|
20
|
+
library_version: '1.1.0'
|
|
20
21
|
sources:
|
|
21
22
|
- 'Jielga/TMDataGrid:src/docs/row-selection.md'
|
|
22
23
|
- 'Jielga/TMDataGrid:src/docs/row-interaction.md'
|
|
@@ -117,7 +118,7 @@ not replace selection or the highlight; yours runs in addition.
|
|
|
117
118
|
| `onRowClick` | `row` | Rows show a pointer cursor when set |
|
|
118
119
|
| `onCellClick` | `{ cell, row, column, event }` | The cell cursor still moves |
|
|
119
120
|
| `onCellDoubleClick` | same | A double-click that opens an editor still does |
|
|
120
|
-
| `onCellContextMenu` | same | `
|
|
121
|
+
| `onCellContextMenu` | same | `renderRowContextMenu` still opens |
|
|
121
122
|
|
|
122
123
|
```tsx
|
|
123
124
|
<TMDataGrid.Table<Employee> onRowClick={(row) => open(row.original.id)} />
|
|
@@ -127,13 +128,13 @@ Pass the row type, as above, and `row.original` is typed. Group rows sit out all
|
|
|
127
128
|
four: TanStack builds a group row on top of its first child's record, so a
|
|
128
129
|
handler would receive a real-looking row that is the wrong one.
|
|
129
130
|
|
|
130
|
-
`
|
|
131
|
+
`renderRowContextMenu` is a slot saying what goes inside the menu. The grid
|
|
131
132
|
owns the Mantine `Menu`, opening it at the pointer and closing it on Escape, an
|
|
132
133
|
outside click, a body scroll and after an item is picked.
|
|
133
134
|
|
|
134
135
|
```tsx
|
|
135
136
|
<TMDataGrid.Table<Employee>
|
|
136
|
-
|
|
137
|
+
renderRowContextMenu={({ table, row, cell }) => (
|
|
137
138
|
<>
|
|
138
139
|
<Menu.Label>{row.original.firstName}</Menu.Label>
|
|
139
140
|
<Menu.Item onClick={() => open(row.original.id)}>Open</Menu.Item>
|
|
@@ -156,6 +157,35 @@ a pure function of its arguments and do the work in the item handlers. Return
|
|
|
156
157
|
the row with `data-context-menu` while its menu is open, so an action meant for
|
|
157
158
|
a multi-selection reads `table` and falls back to the clicked row.
|
|
158
159
|
|
|
160
|
+
Under `cellSelection: "range"` the grid has its own items for this menu - copy,
|
|
161
|
+
export, include headers. `internalItems` is those items, and **reading it hands
|
|
162
|
+
the composition over**: place it and the menu is exactly what you returned;
|
|
163
|
+
never mention it and the grid keeps its half above a divider and yours below.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
renderRowContextMenu={({ row, internalItems }) => (
|
|
167
|
+
<>
|
|
168
|
+
<Menu.Item onClick={() => open(row.id)}>Open</Menu.Item>
|
|
169
|
+
<Menu.Divider />
|
|
170
|
+
{internalItems}
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`renderColumnMenuItems` is the same idea for a column's menu, and returns an
|
|
176
|
+
array rather than a node:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<TMDataGrid.Table
|
|
180
|
+
renderColumnMenuItems={({ column, internalItems }) => [
|
|
181
|
+
...internalItems,
|
|
182
|
+
<Menu.Item key="stats" onClick={() => showStats(column.id)}>Stats</Menu.Item>,
|
|
183
|
+
]}
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Returning an empty list leaves the column with no menu button at all.
|
|
188
|
+
|
|
159
189
|
## Row styling
|
|
160
190
|
|
|
161
191
|
```tsx
|
|
@@ -31,11 +31,12 @@ All are props of `TMDataGrid.Table`, not hook options.
|
|
|
31
31
|
| `onCellClick` | `(args) => void` | Cell click. `args` is `TMDataGridCellEventArgs`. |
|
|
32
32
|
| `onCellDoubleClick` | `(args) => void` | Cell double-click. |
|
|
33
33
|
| `onCellContextMenu` | `(args) => void` | Cell right-click. |
|
|
34
|
-
| `
|
|
34
|
+
| `renderRowContextMenu` | `({ table, row, cell, close, internalItems }) => ReactNode` | Contents of the row's context menu. `null` for no menu. Reading `internalItems` hands the composition over. |
|
|
35
|
+
| `renderColumnMenuItems` | `({ column, table, internalItems }) => ReactNode[]` | Contents of a column's menu. An empty list removes the button. |
|
|
35
36
|
| `rowContextMenuProps` | `MenuProps` | Passed to the Mantine `Menu` untouched apart from its open state. |
|
|
36
37
|
|
|
37
38
|
`TMDataGridCellEventArgs` is `{ cell, row, column, event }`. The context-menu
|
|
38
|
-
|
|
39
|
+
slot's `cell` is `null` only when a custom cell renderer stopped the
|
|
39
40
|
event. One `Menu` serves the whole body: a closed Mantine `Popover` still runs
|
|
40
41
|
its hooks on every render, and the virtualized body re-renders every scroll
|
|
41
42
|
frame.
|
package/skills/testing/SKILL.md
CHANGED