@jielga/tmdatagrid 1.0.0 → 1.0.2
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 +8 -8
- package/dist/index.d.ts +225 -225
- package/dist/index.js +58 -50
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -2
- package/skills/appearance/SKILL.md +322 -0
- package/skills/cell-selection/SKILL.md +240 -0
- package/skills/columns/SKILL.md +261 -86
- package/skills/data/SKILL.md +289 -0
- package/skills/editing/SKILL.md +492 -0
- package/skills/editing/references/editing-api.md +124 -0
- package/skills/editing/references/editors-and-validation.md +198 -0
- package/skills/filtering/SKILL.md +344 -0
- package/skills/getting-started/SKILL.md +48 -27
- package/skills/grouping/SKILL.md +264 -0
- package/skills/options/SKILL.md +31 -20
- package/skills/rows/SKILL.md +369 -0
- package/skills/rows/references/rows-api.md +117 -0
- package/skills/server-side/SKILL.md +7 -7
- package/skills/testing/SKILL.md +12 -12
- package/src/tmdatagrid/TMDataGridContext.ts +2 -2
- package/src/tmdatagrid/components/TMDataGrid.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGrid.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +12 -6
- package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +8 -8
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +12 -12
- package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +8 -8
- package/src/tmdatagrid/components/TMDataGridTable.module.css +18 -18
- package/src/tmdatagrid/components/TMDataGridTable.tsx +116 -116
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +5 -5
- package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/editorShared.ts +3 -3
- package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +2 -2
- package/src/tmdatagrid/components/sticky.module.css +8 -8
- package/src/tmdatagrid/core/autosize.ts +4 -4
- package/src/tmdatagrid/core/capabilities.ts +17 -17
- package/src/tmdatagrid/core/cellExport.ts +13 -13
- package/src/tmdatagrid/core/cellNavigation.ts +6 -6
- package/src/tmdatagrid/core/cellRange.ts +4 -4
- package/src/tmdatagrid/core/columnOptions.ts +2 -2
- package/src/tmdatagrid/core/columnOrdering.ts +2 -2
- package/src/tmdatagrid/core/columnUtils.ts +3 -3
- package/src/tmdatagrid/core/editEngine.ts +49 -49
- package/src/tmdatagrid/core/expanding.ts +5 -5
- package/src/tmdatagrid/core/filterControls.ts +5 -5
- package/src/tmdatagrid/core/filterOperators.ts +14 -14
- package/src/tmdatagrid/core/labels.ts +8 -8
- package/src/tmdatagrid/core/matchHighlight.ts +4 -4
- package/src/tmdatagrid/core/persistence.ts +8 -8
- package/src/tmdatagrid/core/quickSearch.ts +8 -8
- package/src/tmdatagrid/core/rowPinning.ts +3 -3
- package/src/tmdatagrid/core/rowSelection.ts +12 -12
- package/src/tmdatagrid/core/sizes.ts +1 -1
- package/src/tmdatagrid/core/summary.ts +3 -3
- package/src/tmdatagrid/useTMDataGrid.tsx +79 -79
- package/skills/features/SKILL.md +0 -352
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rows
|
|
3
|
+
description: >
|
|
4
|
+
Everything a TMDataGrid row does beyond holding cells. Covers selectionMode
|
|
5
|
+
and its four modes (checkbox, row, checkboxAndHighlight, highlight), the
|
|
6
|
+
highlight as state of its own with defaultHighlightedRowId and
|
|
7
|
+
onHighlightedRowChange, showSelectedBackground and --dg-row-selected-bg,
|
|
8
|
+
reading a selection through the table store, onRowClick / onCellClick /
|
|
9
|
+
onCellDoubleClick / onCellContextMenu, the rowContextMenu render prop and
|
|
10
|
+
rowContextMenuProps, per-row styling with rowStyle, rowClassName, striped and
|
|
11
|
+
the --row-bg rule, the row details panel through renderDetails and
|
|
12
|
+
DETAILS_COLUMN_ID, row pinning with enableRowPinning and row.pin, and the
|
|
13
|
+
row-number gutter through enableRowNumbers. Load when selecting rows, reacting
|
|
14
|
+
to a click, opening a detail panel, colouring rows by their data, pinning rows
|
|
15
|
+
to an edge, or numbering them.
|
|
16
|
+
metadata:
|
|
17
|
+
type: core
|
|
18
|
+
library: '@jielga/tmdatagrid'
|
|
19
|
+
library_version: '1.0.2'
|
|
20
|
+
sources:
|
|
21
|
+
- 'Jielga/TMDataGrid:src/docs/row-selection.md'
|
|
22
|
+
- 'Jielga/TMDataGrid:src/docs/row-interaction.md'
|
|
23
|
+
- 'Jielga/TMDataGrid:src/docs/row-styling.md'
|
|
24
|
+
- 'Jielga/TMDataGrid:src/docs/row-details.md'
|
|
25
|
+
- 'Jielga/TMDataGrid:src/docs/row-pinning.md'
|
|
26
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/rowSelection.ts'
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# TMDataGrid - Rows
|
|
30
|
+
|
|
31
|
+
Selecting rows, reacting to clicks, colouring them, opening a panel under one,
|
|
32
|
+
and pinning or numbering them. Column-side concerns are the `columns` skill;
|
|
33
|
+
cell cursors and ranges are `cell-selection`.
|
|
34
|
+
|
|
35
|
+
## Selection
|
|
36
|
+
|
|
37
|
+
`selectionMode` sets what selecting looks like **and** what a bare row click
|
|
38
|
+
does. One option rather than two, because a click can either toggle a
|
|
39
|
+
multi-selection or move a highlight, never both.
|
|
40
|
+
|
|
41
|
+
| Mode | Checkbox column | Row click |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `"checkbox"` (default) | yes, multi-select | nothing |
|
|
44
|
+
| `"row"` | no | selects, with the usual modifiers |
|
|
45
|
+
| `"checkboxAndHighlight"` | yes, multi-select | highlights one row |
|
|
46
|
+
| `"highlight"` | no | highlights one row, no selection at all |
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
const grid = useTMDataGrid({ data, columns, selectionMode: "row" });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The first two write to TanStack's `rowSelection`, so everything downstream - the
|
|
53
|
+
toolbar count, `getSelectedRowModel()`, persistence - is unaffected by the
|
|
54
|
+
choice. Under `"row"` the click follows desktop-list conventions: a plain click
|
|
55
|
+
makes the selection this row, Ctrl/Cmd toggles it and leaves the rest, Shift
|
|
56
|
+
selects the range from the anchor, Ctrl+Shift adds that range. Rows are
|
|
57
|
+
focusable in this mode, and Space or Enter toggles the focused row.
|
|
58
|
+
|
|
59
|
+
`enableRowSelection: false` removes the selection half whatever the mode.
|
|
60
|
+
|
|
61
|
+
### The highlight is not a selection
|
|
62
|
+
|
|
63
|
+
The highlight is state of its own, not a slice of `rowSelection` - which is what
|
|
64
|
+
lets `"checkboxAndHighlight"` run both at once: tick rows for a bulk action,
|
|
65
|
+
click one to open its record beside the grid.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
const grid = useTMDataGrid({
|
|
69
|
+
data,
|
|
70
|
+
columns,
|
|
71
|
+
selectionMode: "highlight",
|
|
72
|
+
defaultHighlightedRowId: routeId,
|
|
73
|
+
onHighlightedRowChange: (rowId) =>
|
|
74
|
+
navigate({ to: "/employees/$id", params: { id: rowId ?? "" } }),
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Reading a selection
|
|
79
|
+
|
|
80
|
+
Read it through the table store, never by calling the method bare. `table` keeps
|
|
81
|
+
one identity across renders, so the React Compiler caches a bare
|
|
82
|
+
`getSelectedRowModel()` and the reader stops updating.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { useSelector } from "@tanstack/react-store";
|
|
86
|
+
|
|
87
|
+
const selected = useSelector(grid.table.store, () =>
|
|
88
|
+
grid.table.getSelectedRowModel().rows,
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### The background
|
|
93
|
+
|
|
94
|
+
`showSelectedBackground` follows the mode: on for `"row"`, where the background
|
|
95
|
+
is the only feedback a click gives, off for `"checkbox"`, where the box already
|
|
96
|
+
says so. Set it to override either way. The colour is `--dg-row-selected-bg`,
|
|
97
|
+
changed on the grid element rather than through the flag:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<TMDataGrid
|
|
101
|
+
{...grid}
|
|
102
|
+
style={{ "--dg-row-selected-bg": "var(--mantine-color-blue-0)" }}
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A group row's checkbox selects every record under it at any depth, shows a tick
|
|
107
|
+
once all are selected and a dash while only some are, and writes only the
|
|
108
|
+
records to `rowSelection`.
|
|
109
|
+
|
|
110
|
+
## Clicks and context menus
|
|
111
|
+
|
|
112
|
+
Every handler **composes** with what the click already does. `onRowClick` does
|
|
113
|
+
not replace selection or the highlight; yours runs in addition.
|
|
114
|
+
|
|
115
|
+
| Handler | Argument | Notes |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| `onRowClick` | `row` | Rows show a pointer cursor when set |
|
|
118
|
+
| `onCellClick` | `{ cell, row, column, event }` | The cell cursor still moves |
|
|
119
|
+
| `onCellDoubleClick` | same | A double-click that opens an editor still does |
|
|
120
|
+
| `onCellContextMenu` | same | `rowContextMenu` still opens |
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<TMDataGrid.Table<Employee> onRowClick={(row) => open(row.original.id)} />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Pass the row type, as above, and `row.original` is typed. Group rows sit out all
|
|
127
|
+
four: TanStack builds a group row on top of its first child's record, so a
|
|
128
|
+
handler would receive a real-looking row that is the wrong one.
|
|
129
|
+
|
|
130
|
+
`rowContextMenu` is a render prop saying what goes inside the menu. The grid
|
|
131
|
+
owns the Mantine `Menu`, opening it at the pointer and closing it on Escape, an
|
|
132
|
+
outside click, a body scroll and after an item is picked.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<TMDataGrid.Table<Employee>
|
|
136
|
+
rowContextMenu={({ table, row, cell }) => (
|
|
137
|
+
<>
|
|
138
|
+
<Menu.Label>{row.original.firstName}</Menu.Label>
|
|
139
|
+
<Menu.Item onClick={() => open(row.original.id)}>Open</Menu.Item>
|
|
140
|
+
<Menu.Item
|
|
141
|
+
onClick={() =>
|
|
142
|
+
navigator.clipboard.writeText(String(cell?.getValue() ?? ""))
|
|
143
|
+
}
|
|
144
|
+
>
|
|
145
|
+
Copy cell value
|
|
146
|
+
</Menu.Item>
|
|
147
|
+
</>
|
|
148
|
+
)}
|
|
149
|
+
rowContextMenuProps={{ width: 260, position: "right-start" }}
|
|
150
|
+
/>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
It is called **during render**, only for the row whose menu is open, so keep it
|
|
154
|
+
a pure function of its arguments and do the work in the item handlers. Return
|
|
155
|
+
`null` to leave a row without a menu. Right-clicking does not select: it marks
|
|
156
|
+
the row with `data-context-menu` while its menu is open, so an action meant for
|
|
157
|
+
a multi-selection reads `table` and falls back to the clicked row.
|
|
158
|
+
|
|
159
|
+
## Row styling
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<TMDataGrid.Table<Employee>
|
|
163
|
+
striped
|
|
164
|
+
rowStyle={(row) =>
|
|
165
|
+
row.original.status === "Terminated"
|
|
166
|
+
? { "--row-bg": "var(--mantine-color-red-0)" }
|
|
167
|
+
: undefined
|
|
168
|
+
}
|
|
169
|
+
rowClassName={(row) => (row.original.overdue ? classes.overdue : undefined)}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
A row is a strip of cells, some sticky in pinned lanes, and hover, selection,
|
|
174
|
+
the highlight, the cell range and striping all paint on top of it. `--row-bg`
|
|
175
|
+
feeds the variable those layers compose against; `background` wins over all of
|
|
176
|
+
them and kills them. `striped` follows position in the view, so it survives
|
|
177
|
+
sorting and filtering rather than sticking to records, and pinned rows sit it
|
|
178
|
+
out.
|
|
179
|
+
|
|
180
|
+
Rows carry `data-selected`, `data-selected-bg`, `data-highlighted`,
|
|
181
|
+
`data-grouped`, `data-depth`, `data-context-menu` and `data-row-id`, so a
|
|
182
|
+
stylesheet can reach any of it without a callback.
|
|
183
|
+
|
|
184
|
+
## The details panel
|
|
185
|
+
|
|
186
|
+
Setting `renderDetails` turns the lane on. There is no flag.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
const grid = useTMDataGrid({
|
|
190
|
+
data,
|
|
191
|
+
columns,
|
|
192
|
+
renderDetails: ({ row }) => <EmployeeCard employee={row.original} />,
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Each panel is measured, so heights need not be uniform;
|
|
197
|
+
`renderDetailsEstHeight` (default `160`) is only what the virtualizer assumes
|
|
198
|
+
for one it has not seen. A generated chevron column, `DETAILS_COLUMN_ID`, is
|
|
199
|
+
prepended and pinned left after the checkbox and tree lanes; it cannot be
|
|
200
|
+
hidden, moved, resized or unpinned, and its header opens and closes every panel.
|
|
201
|
+
|
|
202
|
+
Which rows are open is TanStack's own `expanded` state, so anything can open
|
|
203
|
+
one - `row.toggleExpanded()` from a menu item, `initialState.expanded`,
|
|
204
|
+
`table.toggleAllRowsExpanded()` - and it persists as a `data` slice. The panel
|
|
205
|
+
is a cell spanning the row, not a row: `aria-rowcount` still counts records, and
|
|
206
|
+
a click inside it stops there rather than selecting the row underneath. Group
|
|
207
|
+
rows have no panel; expanding one opens its children.
|
|
208
|
+
|
|
209
|
+
## Pinning and numbering
|
|
210
|
+
|
|
211
|
+
Two independent lanes at the edges.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
const grid = useTMDataGrid({
|
|
215
|
+
data,
|
|
216
|
+
columns,
|
|
217
|
+
enableRowPinning: true,
|
|
218
|
+
enableRowNumbers: true,
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
There is no built-in pin gesture - pin from wherever suits, most naturally the
|
|
223
|
+
row context menu, through TanStack's own `row.pin("top" | "bottom" | false)`,
|
|
224
|
+
`row.getIsPinned()` and `row.getCanPin()`. Pinned rows leave the scrolling order
|
|
225
|
+
and render in sticky blocks: top under the header, bottom above the summary row.
|
|
226
|
+
|
|
227
|
+
Pinned rows are still body rows - selection, editing, details, the context menu
|
|
228
|
+
and per-row styling all behave normally. What they sit out are the statements
|
|
229
|
+
about scrolling order: striping, the cell range, and the number gutter. A pinned
|
|
230
|
+
row stays at its edge even when a filter or the pager would have dropped it, and
|
|
231
|
+
**group rows never pin**.
|
|
232
|
+
|
|
233
|
+
`enableRowNumbers` adds a gutter outermost left that numbers the current view -
|
|
234
|
+
sorted, filtered, continuing across pages. It answers "where am I in what I am
|
|
235
|
+
looking at", not "which record is this"; a stable identifier is a column of your
|
|
236
|
+
own over the record's id.
|
|
237
|
+
|
|
238
|
+
## Common mistakes
|
|
239
|
+
|
|
240
|
+
### CRITICAL Setting `background` in `rowStyle`
|
|
241
|
+
|
|
242
|
+
A coloured row stops responding to hover, selection, the highlight and the cell
|
|
243
|
+
range, because `background` paints over every layer composed on top of the row.
|
|
244
|
+
Nothing errors; the row simply goes inert.
|
|
245
|
+
|
|
246
|
+
Wrong:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
rowStyle={() => ({ background: "pink" })}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Correct:
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
rowStyle={() => ({ "--row-bg": "pink" })}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Source: `src/docs/row-styling.md` (Set `--row-bg`, not `background`).
|
|
259
|
+
|
|
260
|
+
### CRITICAL Reading the selection without subscribing
|
|
261
|
+
|
|
262
|
+
`table` keeps one identity for the life of the grid, so the React Compiler
|
|
263
|
+
memoizes a bare `getSelectedRowModel()` against it. The count renders once and
|
|
264
|
+
then never changes, which reads as a broken toolbar rather than a missing
|
|
265
|
+
subscription.
|
|
266
|
+
|
|
267
|
+
Wrong:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
const selected = grid.table.getSelectedRowModel().rows;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Correct:
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
const selected = useSelector(grid.table.store, () =>
|
|
277
|
+
grid.table.getSelectedRowModel().rows,
|
|
278
|
+
);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Source: `src/docs/row-selection.md` (Acting on a selection).
|
|
282
|
+
|
|
283
|
+
### HIGH Looking for the highlight in `rowSelection`
|
|
284
|
+
|
|
285
|
+
The highlight is separate state, which is what lets
|
|
286
|
+
`"checkboxAndHighlight"` run both at once. It never appears in `rowSelection`,
|
|
287
|
+
`getSelectedRowModel()` or the persisted selection slice.
|
|
288
|
+
|
|
289
|
+
Wrong:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
// Always empty under selectionMode: "highlight".
|
|
293
|
+
const current = Object.keys(grid.table.store.state.rowSelection)[0];
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Correct:
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
const current = useSelector(grid.ui, (state) => state.highlightedRowId);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Source: `src/docs/row-selection.md` (The highlight is not a selection).
|
|
303
|
+
|
|
304
|
+
### HIGH Expecting a click handler to replace the built-in behaviour
|
|
305
|
+
|
|
306
|
+
`onRowClick` composes. Under `selectionMode: "row"` a click both selects and
|
|
307
|
+
runs the handler, so a handler that navigates away will also have changed the
|
|
308
|
+
selection on the way out.
|
|
309
|
+
|
|
310
|
+
Correct, when the click should only navigate:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
// "highlight" moves a marker and selects nothing.
|
|
314
|
+
useTMDataGrid({ data, columns, selectionMode: "highlight" });
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Source: `src/docs/row-interaction.md`.
|
|
318
|
+
|
|
319
|
+
### HIGH Reading `row.getIsExpanded()` in a cell without subscribing
|
|
320
|
+
|
|
321
|
+
Same mechanism as the selection above: the compiler caches the call against the
|
|
322
|
+
`row` identity, so a custom expand control renders its initial state forever.
|
|
323
|
+
|
|
324
|
+
Correct:
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
const expanded = useSelector(row.table.store, () => row.getIsExpanded());
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Source: `src/docs/row-details.md` (Opening a row from elsewhere).
|
|
331
|
+
|
|
332
|
+
### MEDIUM Assuming group rows behave like data rows
|
|
333
|
+
|
|
334
|
+
Group rows are built on their first child's record. They sit out `onRowClick`,
|
|
335
|
+
`onCellClick`, `onCellDoubleClick` and `onCellContextMenu`, they never pin, they
|
|
336
|
+
take no row number, and they have no details panel. A handler written as though
|
|
337
|
+
every row reaches it will silently skip them.
|
|
338
|
+
|
|
339
|
+
Source: `src/docs/row-interaction.md`, `src/docs/row-pinning.md`.
|
|
340
|
+
|
|
341
|
+
### MEDIUM Open panels closing when `data` is replaced
|
|
342
|
+
|
|
343
|
+
TanStack resets `expanded` when the row structure changes, so a refetch collapses
|
|
344
|
+
every open panel.
|
|
345
|
+
|
|
346
|
+
Correct:
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
useTMDataGrid({ data, columns, renderDetails, autoResetExpanded: false });
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Source: `src/docs/row-details.md`.
|
|
353
|
+
|
|
354
|
+
### MEDIUM Expecting pinned rows to persist
|
|
355
|
+
|
|
356
|
+
`rowPinning` is deliberately left out of `settingsKey`: row ids are data, and a
|
|
357
|
+
layout store outlives any one data set. A pinned id whose row leaves `data` is
|
|
358
|
+
simply not shown, and returns to its edge if the data comes back.
|
|
359
|
+
|
|
360
|
+
Source: `src/docs/row-pinning.md`.
|
|
361
|
+
|
|
362
|
+
## References
|
|
363
|
+
|
|
364
|
+
- [Rows API](references/rows-api.md) - every option, table prop, callback,
|
|
365
|
+
export, CSS variable and data attribute these five topics own.
|
|
366
|
+
|
|
367
|
+
See also: the `grouping` skill for group rows and the tree lane, the
|
|
368
|
+
`cell-selection` skill for the cell cursor and ranges, and the `appearance`
|
|
369
|
+
skill for the CSS variables named here.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Rows API
|
|
2
|
+
|
|
3
|
+
Reference for the `rows` skill.
|
|
4
|
+
|
|
5
|
+
## Selection
|
|
6
|
+
|
|
7
|
+
| Name | Kind | Type | Default | What it does |
|
|
8
|
+
| --- | --- | --- | --- | --- |
|
|
9
|
+
| `selectionMode` | Option | `"checkbox" \| "row" \| "checkboxAndHighlight" \| "highlight"` | `"checkbox"` | What selecting looks like and what a row click does. |
|
|
10
|
+
| `enableRowSelection` | Table option | `boolean \| ((row) => boolean)` | `true` | `false` removes the checkbox column and row-click selection. |
|
|
11
|
+
| `enableMultiRowSelection` | Table option | `boolean` | `true` | `false` limits the selection to one row and drops group checkboxes. |
|
|
12
|
+
| `showSelectedBackground` | Option | `boolean` | Follows the mode | Whether selected rows take a background tint. |
|
|
13
|
+
| `defaultHighlightedRowId` | Option | `string \| null` | `null` | Row highlighted at mount. |
|
|
14
|
+
| `onHighlightedRowChange` | Callback | `(rowId: string \| null) => void` | – | Fires when the highlight moves. |
|
|
15
|
+
| `SELECT_COLUMN_ID` | Export | `"__select__"` | – | Id of the generated checkbox column. |
|
|
16
|
+
| `getSelectableRowIds` | Export | `(table) => string[]` | – | Ids the header checkbox would select. |
|
|
17
|
+
| `resolveRowSelectionClick` | Export | `(args) => ResolvedRowSelection` | – | The desktop-list click rules, for a custom surface. |
|
|
18
|
+
| `getDisplayedRows` | Export | `(table) => Row[]` | – | The rows currently on screen, paging respected. |
|
|
19
|
+
| `isPagingActive` | Export | `(table) => boolean` | – | Whether a pager is in play. |
|
|
20
|
+
|
|
21
|
+
The highlight lives on the ui store, not in `rowSelection`:
|
|
22
|
+
`useSelector(grid.ui, (state) => state.highlightedRowId)`.
|
|
23
|
+
|
|
24
|
+
## Clicks and context menus
|
|
25
|
+
|
|
26
|
+
All are props of `TMDataGrid.Table`, not hook options.
|
|
27
|
+
|
|
28
|
+
| Name | Type | What it does |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `onRowClick` | `(row) => void` | Row click. Adds a pointer cursor. |
|
|
31
|
+
| `onCellClick` | `(args) => void` | Cell click. `args` is `TMDataGridCellEventArgs`. |
|
|
32
|
+
| `onCellDoubleClick` | `(args) => void` | Cell double-click. |
|
|
33
|
+
| `onCellContextMenu` | `(args) => void` | Cell right-click. |
|
|
34
|
+
| `rowContextMenu` | `({ table, row, cell, close }) => ReactNode` | Contents of the row's context menu. `null` for no menu. |
|
|
35
|
+
| `rowContextMenuProps` | `MenuProps` | Passed to the Mantine `Menu` untouched apart from its open state. |
|
|
36
|
+
|
|
37
|
+
`TMDataGridCellEventArgs` is `{ cell, row, column, event }`. The context-menu
|
|
38
|
+
render prop's `cell` is `null` only when a custom cell renderer stopped the
|
|
39
|
+
event. One `Menu` serves the whole body: a closed Mantine `Popover` still runs
|
|
40
|
+
its hooks on every render, and the virtualized body re-renders every scroll
|
|
41
|
+
frame.
|
|
42
|
+
|
|
43
|
+
On touch devices a long press (500 ms) opens the same menu. Mantine sets
|
|
44
|
+
`user-select: none` on the element it hangs a context menu off, so body cell
|
|
45
|
+
text stops being mouse-selectable in a grid that has one.
|
|
46
|
+
|
|
47
|
+
## Styling
|
|
48
|
+
|
|
49
|
+
| Name | Kind | Type | Default | What it does |
|
|
50
|
+
| --- | --- | --- | --- | --- |
|
|
51
|
+
| `rowStyle` | Table prop | `TMDataGridRowStyle \| ((row) => TMDataGridRowStyle)` | – | Inline style for a body row. |
|
|
52
|
+
| `rowClassName` | Table prop | `string \| ((row) => string \| undefined)` | – | Class added after the grid's own. |
|
|
53
|
+
| `striped` | Table prop | `boolean` | `false` | Every second row takes `--dg-row-striped-bg`. |
|
|
54
|
+
| `TMDataGridRowStyle` | Export | type | – | `CSSProperties` or an object of `--*` custom properties. |
|
|
55
|
+
| `--row-bg` | CSS variable | colour | – | The row's own background, composed under hover, selection and range. |
|
|
56
|
+
| `--dg-row-striped-bg` | CSS variable | colour | Themed | The stripe colour. |
|
|
57
|
+
| `--dg-row-selected-bg` | CSS variable | colour | `--mantine-primary-color-light` | Selected row background. |
|
|
58
|
+
| `--dg-row-highlight-bg` | CSS variable | colour | Themed | Highlighted row background. |
|
|
59
|
+
| `--dg-row-height` | CSS variable | length | From `size` | Row height. `meta.rowHeight` is the supported way to change it. |
|
|
60
|
+
|
|
61
|
+
Row data attributes:
|
|
62
|
+
|
|
63
|
+
| Attribute | On |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `data-selected` | Selected rows |
|
|
66
|
+
| `data-selected-bg` | Selected rows that also take the background |
|
|
67
|
+
| `data-highlighted` | The highlighted row |
|
|
68
|
+
| `data-grouped` | Group rows |
|
|
69
|
+
| `data-depth` | Every row - the nesting level |
|
|
70
|
+
| `data-context-menu` | The row whose context menu is open |
|
|
71
|
+
| `data-deleted` | Rows marked for deletion under batch editing |
|
|
72
|
+
| `data-row-id` | Every row - its id |
|
|
73
|
+
|
|
74
|
+
## Details
|
|
75
|
+
|
|
76
|
+
| Name | Kind | Type | Default | What it does |
|
|
77
|
+
| --- | --- | --- | --- | --- |
|
|
78
|
+
| `renderDetails` | Option | `({ row, table }) => ReactNode` | – | Contents of the panel. Setting it adds the lane. |
|
|
79
|
+
| `renderDetailsEstHeight` | Option | `number` | `160` | Height the virtualizer assumes for an unmeasured panel. |
|
|
80
|
+
| `initialState.expanded` | Table option | `ExpandedState` | `{}` | Rows open at mount. A `data` slice, so it persists. |
|
|
81
|
+
| `autoResetExpanded` | Table option | `boolean` | `true` | `false` keeps panels open when `data` changes. |
|
|
82
|
+
| `DETAILS_COLUMN_ID` | Export | `"__details__"` | – | Id of the generated chevron column. |
|
|
83
|
+
| `resolveExpandAll` | Export | `(args) => ExpandedState` | – | Expand or collapse every group, or every panel, but not both. |
|
|
84
|
+
| `areAllRowsExpanded` | Export | `(args) => boolean` | – | Whether every row of one target is open. |
|
|
85
|
+
| `data-dg-part="details"` | Data attribute | – | – | The panel element, carrying the row's `data-row-id`. |
|
|
86
|
+
|
|
87
|
+
One `expanded` state opens two unrelated things - a group row into its children,
|
|
88
|
+
a data row into its panel. The controls stay apart: the details header only
|
|
89
|
+
opens panels, "Expand all groups" only unfolds the tree.
|
|
90
|
+
`table.toggleAllRowsExpanded()` is the one that does not distinguish them, which
|
|
91
|
+
is what `resolveExpandAll({ rows, expanded, target, expand })` exists to avoid:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
table.setExpanded(
|
|
95
|
+
resolveExpandAll({
|
|
96
|
+
rows: table.getPrePaginatedRowModel().flatRows,
|
|
97
|
+
expanded: table.store.state.expanded,
|
|
98
|
+
target: "details",
|
|
99
|
+
expand: true,
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Pinning and numbering
|
|
105
|
+
|
|
106
|
+
| Name | Kind | Type | Default | What it does |
|
|
107
|
+
| --- | --- | --- | --- | --- |
|
|
108
|
+
| `enableRowPinning` | Table option | `boolean \| ((row) => boolean)` | `false` | Whether rows can be pinned. |
|
|
109
|
+
| `initialState.rowPinning` | Table option | `{ top: string[], bottom: string[] }` | empty | Rows pinned at mount. Not persisted. |
|
|
110
|
+
| `enableRowNumbers` | Option | `boolean` | `false` | Adds the row-number gutter, outermost left. |
|
|
111
|
+
| `row.pin` | Row method | `("top" \| "bottom" \| false) => void` | – | Pins or unpins one row. |
|
|
112
|
+
| `row.getIsPinned` | Row method | `() => "top" \| "bottom" \| false` | – | Where a row is pinned. |
|
|
113
|
+
| `row.getCanPin` | Row method | `() => boolean` | – | Whether this row may pin. |
|
|
114
|
+
| `ROW_NUMBER_COLUMN_ID` | Export | `"__rowNumber__"` | – | Id of the generated number gutter. |
|
|
115
|
+
|
|
116
|
+
Lane order, left to right: row number, checkbox, tree, details, your columns,
|
|
117
|
+
edit.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-side
|
|
3
3
|
description: >
|
|
4
|
-
Drive TMDataGrid from a server with TanStack manual modes
|
|
4
|
+
Drive TMDataGrid from a server with TanStack manual modes - manualPagination,
|
|
5
5
|
manualSorting, manualFiltering, rowCount, controlled state and onXChange
|
|
6
6
|
callbacks. Covers the loading and totalRowCount meta fields, forwarding the
|
|
7
7
|
plain-JSON columnFilters model to an API with isFilterActive, persistence
|
|
@@ -10,18 +10,18 @@ description: >
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: core
|
|
12
12
|
library: '@jielga/tmdatagrid'
|
|
13
|
-
library_version: '1.0.
|
|
13
|
+
library_version: '1.0.2'
|
|
14
14
|
sources:
|
|
15
15
|
- 'Jielga/TMDataGrid:src/docs/server-side.md'
|
|
16
16
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
# TMDataGrid
|
|
19
|
+
# TMDataGrid - Server-side data
|
|
20
20
|
|
|
21
21
|
The grid reads rows through `getPaginatedRowModel()` and totals through
|
|
22
22
|
`getRowCount()` and `getPageCount()`, all of which respect TanStack's manual
|
|
23
23
|
modes. A server-driven grid therefore needs only the standard `manual*`
|
|
24
|
-
configuration
|
|
24
|
+
configuration - no grid-specific options. `manualPagination: true` also
|
|
25
25
|
switches the grid's pagination flag on, so `TMDataGrid.Footer` renders its
|
|
26
26
|
pager without `enablePagination`. Declare `pageCount: -1` when the total is
|
|
27
27
|
unknown; the next button then stays enabled.
|
|
@@ -85,7 +85,7 @@ Filter values are plain JSON, so `columnFilters` forwards without transformation
|
|
|
85
85
|
]
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Translate at the API boundary, and skip entries whose value is still empty
|
|
88
|
+
Translate at the API boundary, and skip entries whose value is still empty -
|
|
89
89
|
those match all rows:
|
|
90
90
|
|
|
91
91
|
```ts
|
|
@@ -122,8 +122,8 @@ const selectedIds = useSelector(grid.table.store, (state) =>
|
|
|
122
122
|
|
|
123
123
|
### Omitting rowCount under manualPagination
|
|
124
124
|
|
|
125
|
-
Without `rowCount` the table derives the total from the rows it was handed
|
|
126
|
-
one page
|
|
125
|
+
Without `rowCount` the table derives the total from the rows it was handed -
|
|
126
|
+
one page - so `getPageCount()` returns 1. The footer shows "1–25 of 25" and the
|
|
127
127
|
next-page button is disabled, with no error. Pass the server total.
|
|
128
128
|
|
|
129
129
|
### Filters sent without isFilterActive
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: testing
|
|
3
3
|
description: >
|
|
4
|
-
Write tests against TMDataGrid from a consuming app
|
|
4
|
+
Write tests against TMDataGrid from a consuming app - Playwright or React
|
|
5
5
|
Testing Library. Covers the data-dg-part contract, data-row-id/data-column-id
|
|
6
6
|
coordinates, naming a grid with data-testid, the roles and ARIA the grid
|
|
7
7
|
publishes, the cell/gridcell role flip under cell selection, reaching rows
|
|
@@ -11,25 +11,25 @@ description: >
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/tmdatagrid'
|
|
14
|
-
library_version: '1.0.
|
|
14
|
+
library_version: '1.0.2'
|
|
15
15
|
sources:
|
|
16
16
|
- 'Jielga/TMDataGrid:src/docs/testing.md'
|
|
17
17
|
- 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'
|
|
18
18
|
- 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGridTable.tsx'
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
# TMDataGrid
|
|
21
|
+
# TMDataGrid - Testing
|
|
22
22
|
|
|
23
23
|
The grid names its own pieces so a suite can be written against structure
|
|
24
24
|
rather than against copy or class names. Three hooks carry it:
|
|
25
25
|
|
|
26
|
-
- **`data-dg-part`**
|
|
27
|
-
- **`data-row-id` / `data-column-id`**
|
|
28
|
-
- **roles and ARIA**
|
|
26
|
+
- **`data-dg-part`** - *what* an element is (`"row"`, `"filter-button"`)
|
|
27
|
+
- **`data-row-id` / `data-column-id`** - *which* one, using your own ids
|
|
28
|
+
- **roles and ARIA** - framework-neutral, and what a screen reader reads
|
|
29
29
|
|
|
30
30
|
Selectors compose: `[data-dg-part="save-row"][data-row-id="42"]`.
|
|
31
31
|
|
|
32
|
-
The grid mints no `data-testid` of its own
|
|
32
|
+
The grid mints no `data-testid` of its own - that attribute belongs to the app,
|
|
33
33
|
and Playwright's `testIdAttribute` is configurable. `@mantine/core` and
|
|
34
34
|
`@tanstack/*` ship none either.
|
|
35
35
|
|
|
@@ -56,7 +56,7 @@ accessible name belongs to the element carrying the `grid` role.
|
|
|
56
56
|
| Body cell | `cell`, or `gridcell` under cell selection | `data-row-id`, `data-column-id`, `data-editing`, `data-dirty`, `data-invalid`, `data-focused` |
|
|
57
57
|
| Header cell | `columnheader` | `data-column-id`, `aria-sort` |
|
|
58
58
|
|
|
59
|
-
Body cells carry no `data-dg-part`
|
|
59
|
+
Body cells carry no `data-dg-part` - the coordinate pair already names them.
|
|
60
60
|
|
|
61
61
|
## Parts
|
|
62
62
|
|
|
@@ -79,7 +79,7 @@ Body cells carry no `data-dg-part` — the coordinate pair already names them.
|
|
|
79
79
|
**Keyed by both**: `editor`.
|
|
80
80
|
|
|
81
81
|
Inside a `filter-row` the controls are `filter-column`, `filter-operator` and
|
|
82
|
-
`filter-value`
|
|
82
|
+
`filter-value` - or `filter-value-from` / `filter-value-to` for `between`.
|
|
83
83
|
|
|
84
84
|
A column declaring `meta.filterControl` or `meta.editor` renders your own
|
|
85
85
|
component in that slot, so `filter-value` and `editor-input` cover the built-ins
|
|
@@ -131,10 +131,10 @@ Only the rows in the viewport plus overscan are in the DOM. A row at index 500
|
|
|
131
131
|
has no element, and Playwright cannot scroll to what it cannot find.
|
|
132
132
|
|
|
133
133
|
**Count rows off the grid.** `data-dg-row-count` is the body rows the grid is
|
|
134
|
-
showing
|
|
134
|
+
showing - the current page under pagination, everything the filters left
|
|
135
135
|
otherwise. (`aria-rowcount` also counts the header and summary rows.)
|
|
136
136
|
|
|
137
|
-
**Reach a row by narrowing to it**
|
|
137
|
+
**Reach a row by narrowing to it** - filter or search. Faster, more stable, and
|
|
138
138
|
what a user would do. Where the row must be reached in place,
|
|
139
139
|
`grid.scrollToRow({ rowId, align })` moves the virtualizer and answers whether
|
|
140
140
|
the row was reachable; from Playwright that needs the app to expose the api.
|
|
@@ -164,7 +164,7 @@ layout, so the count depends on the stubbed element size. Assert
|
|
|
164
164
|
### getByRole("cell") on a grid with cell selection
|
|
165
165
|
|
|
166
166
|
`enableCellSelection` turns every `cell` into a `gridcell`, and the grid's
|
|
167
|
-
`table` into a `grid`
|
|
167
|
+
`table` into a `grid` - a widget with a keyboard cursor is not a table of
|
|
168
168
|
content. Tests written on the role break when the feature is switched on. Query
|
|
169
169
|
cells by `[data-row-id][data-column-id]`, which do not move.
|
|
170
170
|
|
|
@@ -5,7 +5,7 @@ import type { TMDataGridApi } from "./useTMDataGrid";
|
|
|
5
5
|
/**
|
|
6
6
|
* Row shape the chrome components work against. They only ever touch columns,
|
|
7
7
|
* rows and cells generically, so the concrete row type is erased at the context
|
|
8
|
-
* boundary
|
|
8
|
+
* boundary - the same trick Mantine uses for its compound components.
|
|
9
9
|
*/
|
|
10
10
|
export type TMDataGridRowData = Record<string, unknown>;
|
|
11
11
|
|
|
@@ -36,7 +36,7 @@ export function useTMDataGridContext(): TMDataGridContextValue {
|
|
|
36
36
|
*
|
|
37
37
|
* `-1` once cell selection is on, and this is what makes the promise of one tab
|
|
38
38
|
* stop true. Without it the browser walks Tab into the checkbox of every
|
|
39
|
-
* mounted row
|
|
39
|
+
* mounted row - a grid showing twenty rows would be twenty tab stops, and
|
|
40
40
|
* scrolling would change how many. Enter or F2 steps into the cell instead,
|
|
41
41
|
* which reaches a `-1` control perfectly well.
|
|
42
42
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
them, and the pinned-column boundary shared by header and body cells lives
|
|
4
4
|
in sticky.module.css. */
|
|
5
5
|
|
|
6
|
-
/* Size scale. Row heights mirror SIZE_ROW_HEIGHT in core/sizes.ts
|
|
6
|
+
/* Size scale. Row heights mirror SIZE_ROW_HEIGHT in core/sizes.ts - the
|
|
7
7
|
virtualizer needs the number in JS, so the two have to agree. */
|
|
8
8
|
.root[data-size="xs"] {
|
|
9
9
|
--dg-row-height: 34px;
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
Stronger than the selected background by default, since under
|
|
55
55
|
"checkboxAndHighlight" the two show up together. */
|
|
56
56
|
--dg-row-highlight-bg: var(--mantine-primary-color-light-hover);
|
|
57
|
-
/* Background of a group row. Transparent by default
|
|
57
|
+
/* Background of a group row. Transparent by default - the bold label and the
|
|
58
58
|
chevron are enough, and with the tree collapsed every row on screen is a
|
|
59
59
|
group row, so a tint would grey out the whole grid. Set it to give groups a
|
|
60
60
|
band of their own. */
|