@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.
Files changed (78) hide show
  1. package/README.md +8 -8
  2. package/dist/index.d.ts +225 -225
  3. package/dist/index.js +58 -50
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/package.json +3 -2
  7. package/skills/appearance/SKILL.md +322 -0
  8. package/skills/cell-selection/SKILL.md +240 -0
  9. package/skills/columns/SKILL.md +261 -86
  10. package/skills/data/SKILL.md +289 -0
  11. package/skills/editing/SKILL.md +492 -0
  12. package/skills/editing/references/editing-api.md +124 -0
  13. package/skills/editing/references/editors-and-validation.md +198 -0
  14. package/skills/filtering/SKILL.md +344 -0
  15. package/skills/getting-started/SKILL.md +48 -27
  16. package/skills/grouping/SKILL.md +264 -0
  17. package/skills/options/SKILL.md +31 -20
  18. package/skills/rows/SKILL.md +369 -0
  19. package/skills/rows/references/rows-api.md +117 -0
  20. package/skills/server-side/SKILL.md +7 -7
  21. package/skills/testing/SKILL.md +12 -12
  22. package/src/tmdatagrid/TMDataGridContext.ts +2 -2
  23. package/src/tmdatagrid/components/TMDataGrid.module.css +2 -2
  24. package/src/tmdatagrid/components/TMDataGrid.tsx +5 -5
  25. package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +4 -4
  26. package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +2 -2
  27. package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +6 -6
  28. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +2 -2
  29. package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +4 -4
  30. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +6 -6
  31. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +6 -6
  32. package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +2 -2
  33. package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +1 -1
  34. package/src/tmdatagrid/components/TMDataGridFooter.module.css +1 -1
  35. package/src/tmdatagrid/components/TMDataGridFooter.tsx +12 -6
  36. package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +1 -1
  37. package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +5 -5
  38. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +8 -8
  39. package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +12 -12
  40. package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +4 -4
  41. package/src/tmdatagrid/components/TMDataGridSearch.tsx +5 -5
  42. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +8 -8
  43. package/src/tmdatagrid/components/TMDataGridTable.module.css +18 -18
  44. package/src/tmdatagrid/components/TMDataGridTable.tsx +116 -116
  45. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +5 -5
  46. package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +1 -1
  47. package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +2 -2
  48. package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +1 -1
  49. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +1 -1
  50. package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +2 -2
  51. package/src/tmdatagrid/components/editors/editorShared.ts +3 -3
  52. package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +1 -1
  53. package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +1 -1
  54. package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +1 -1
  55. package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +2 -2
  56. package/src/tmdatagrid/components/sticky.module.css +8 -8
  57. package/src/tmdatagrid/core/autosize.ts +4 -4
  58. package/src/tmdatagrid/core/capabilities.ts +17 -17
  59. package/src/tmdatagrid/core/cellExport.ts +13 -13
  60. package/src/tmdatagrid/core/cellNavigation.ts +6 -6
  61. package/src/tmdatagrid/core/cellRange.ts +4 -4
  62. package/src/tmdatagrid/core/columnOptions.ts +2 -2
  63. package/src/tmdatagrid/core/columnOrdering.ts +2 -2
  64. package/src/tmdatagrid/core/columnUtils.ts +3 -3
  65. package/src/tmdatagrid/core/editEngine.ts +49 -49
  66. package/src/tmdatagrid/core/expanding.ts +5 -5
  67. package/src/tmdatagrid/core/filterControls.ts +5 -5
  68. package/src/tmdatagrid/core/filterOperators.ts +14 -14
  69. package/src/tmdatagrid/core/labels.ts +8 -8
  70. package/src/tmdatagrid/core/matchHighlight.ts +4 -4
  71. package/src/tmdatagrid/core/persistence.ts +8 -8
  72. package/src/tmdatagrid/core/quickSearch.ts +8 -8
  73. package/src/tmdatagrid/core/rowPinning.ts +3 -3
  74. package/src/tmdatagrid/core/rowSelection.ts +12 -12
  75. package/src/tmdatagrid/core/sizes.ts +1 -1
  76. package/src/tmdatagrid/core/summary.ts +3 -3
  77. package/src/tmdatagrid/useTMDataGrid.tsx +79 -79
  78. 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 — manualPagination,
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.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 — Server-side data
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 — no grid-specific options. `manualPagination: true` also
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 — so `getPageCount()` returns 1. The footer shows "1–25 of 25" and the
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
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: testing
3
3
  description: >
4
- Write tests against TMDataGrid from a consuming app — Playwright or React
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.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 — Testing
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`** — *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
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 — that attribute belongs to the app,
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` — the coordinate pair already names them.
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` — or `filter-value-from` / `filter-value-to` for `between`.
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 — the current page under pagination, everything the filters left
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** — filter or search. Faster, more stable, and
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` — a widget with a keyboard cursor is not a table of
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 — the same trick Mantine uses for its compound components.
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 — a grid showing twenty rows would be twenty tab stops, and
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 — the
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 — the bold label and the
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. */