@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "1.0.2",
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",
@@ -16,7 +16,7 @@ description: >
16
16
  metadata:
17
17
  type: core
18
18
  library: '@jielga/tmdatagrid'
19
- library_version: '1.0.2'
19
+ library_version: '1.1.0'
20
20
  sources:
21
21
  - 'Jielga/TMDataGrid:src/docs/styling.md'
22
22
  - 'Jielga/TMDataGrid:src/docs/toolbar.md'
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '1.0.2'
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 `rowContextMenu` items are appended
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 `rowContextMenu`, whose items are
240
- appended below the copy and export ones.
239
+ Enter and F2, and the `rows` skill for `renderRowContextMenu`, whose
240
+ `internalItems` are exactly the copy and export items described here.
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: core
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '1.0.2'
18
+ library_version: '1.1.0'
19
19
  sources:
20
20
  - 'Jielga/TMDataGrid:src/docs/columns.md'
21
21
  - 'Jielga/TMDataGrid:src/docs/column-layout.md'
@@ -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 pagination render prop and
7
- getTMDataGridPaginationApi, isPagingActive versus canPaginate, always-on
8
- virtualization with overscan and meta.rowHeight, scrollToRow and scrollerRef,
9
- the edge callbacks onScrollToBottom / onScrollToRight and why onReachEnd is
10
- better for loading more, the header and pinned-lane depth shadows, and the
11
- four empty states in precedence order with meta.loading, renderEmptyState,
12
- hasActiveFilters, TMDataGrid.LoadingIndicator and TMDataGrid.SummaryCount.
13
- Load when adding a pager, tuning scrolling, scrolling to a row, or deciding
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.2'
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 `pagination` render prop handed the same API the
57
- built-in pager is built on, and `getTMDataGridPaginationApi(table)` returns that
58
- object outside the Footer:
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
- pagination={(api) => (
62
+ renderPagination={({ state, actions, Controls }) => (
63
63
  <Group>
64
- <Button onClick={api.previousPage} disabled={!api.canPreviousPage}>
64
+ <Controls.PageSize />
65
+ <Button onClick={actions.previousPage} disabled={!state.canPreviousPage}>
65
66
  Back
66
67
  </Button>
67
68
  <Text>
68
- {api.pageIndex + 1} / {api.pageCount}
69
+ {state.pageIndex + 1} / {state.pageCount}
69
70
  </Text>
70
- <Button onClick={api.nextPage} disabled={!api.canNextPage}>
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
- | `getTMDataGridPaginationApi` | Export | `(table) => TMDataGridPaginationApi` | – | The pager API, outside the Footer. |
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. |
@@ -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, and the public
12
- edit engine (begin, commit, cancel, submitAll, getForm, store). Load when
13
- making a grid editable, choosing an edit mode, wiring a save, writing a cell
14
- editor, validating an edit, or when cells will not open for editing.
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.2'
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
- import { useState } from "react";
45
- import {
46
- createTMDataGridColumnHelper,
47
- TMDataGrid,
48
- useTMDataGrid,
49
- } from "@jielga/tmdatagrid";
50
-
51
- type Employee = { id: number; firstName: string; salary: number };
52
-
53
- const columnHelper = createTMDataGridColumnHelper<Employee>();
54
-
55
- const columns = columnHelper.columns([
56
- columnHelper.accessor("firstName", { header: "First name", minSize: 140 }),
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
- return (
83
- <TMDataGrid {...grid} style={{ flex: 1, minHeight: 0 }}>
84
- <TMDataGrid.Table<Employee> />
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
- - One field at a time, saved as you go, spreadsheet feel: `"cell"`.
109
- - The same, but every commit costs a request you do not want fired by a stray
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: writes back to a real field.
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
- ### MEDIUM Reading a commit's result as the saved value
449
+ ### HIGH Submitting an outer form while the grid holds a draft
454
450
 
455
- `edit.commit(rowId)` and `edit.submitAll()` resolve to a `boolean` saying
456
- whether the form closed, and resolve `false` when validation or a rejected save
457
- kept it open. Ignoring the result reports a save that did not happen.
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
- Wrong:
456
+ Correct:
460
457
 
461
458
  ```tsx
462
- await grid.edit.submitAll();
463
- notifications.show({ message: "Saved" });
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
- Correct:
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. |
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: core
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '1.0.2'
18
+ library_version: '1.1.0'
19
19
  sources:
20
20
  - 'Jielga/TMDataGrid:src/docs/filtering.md'
21
21
  - 'Jielga/TMDataGrid:src/docs/quick-search.md'
@@ -11,7 +11,7 @@ description: >
11
11
  metadata:
12
12
  type: core
13
13
  library: '@jielga/tmdatagrid'
14
- library_version: '1.0.2'
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)`, `rowContextMenu` render prop, `rowContextMenuProps` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `selectionMode: "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` | - | Save with the pending count, and Discard. Renders nothing while editing is off - see the `editing` skill. |
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
- `rowContextMenu` fills the dropdown of a Mantine `Menu` the grid opens at the
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
- rowContextMenu={({ table, row, cell, close }) => (
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 `pagination` render prop replaces the built-in pager with your own
208
- UI, fed by the distilled pagination API:
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
- pagination={(api) => (
213
- <Pagination
214
- total={api.pageCount}
215
- value={api.pageIndex + 1}
216
- onChange={(page) => api.setPageIndex(page - 1)}
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
- `TMDataGridPaginationApi` carries `pageIndex`, `pageSize`, `pageCount`,
223
- `rowCount`, `canPreviousPage`, `canNextPage` and the actions `setPageIndex`,
224
- `setPageSize`, `previousPage`, `nextPage`, `firstPage`, `lastPage`. Outside the
225
- Footer, `getTMDataGridPaginationApi(grid.table)` returns the same object.
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
 
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '1.0.2'
17
+ library_version: '1.1.0'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:src/docs/grouping.md'
20
20
  - 'Jielga/TMDataGrid:src/docs/summary-row.md'
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '1.0.2'
17
+ library_version: '1.1.0'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
20
20
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -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 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.
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.2'
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 | `rowContextMenu` still opens |
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
- `rowContextMenu` is a render prop saying what goes inside the menu. The grid
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
- rowContextMenu={({ table, row, cell }) => (
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
- | `rowContextMenu` | `({ table, row, cell, close }) => ReactNode` | Contents of the row's context menu. `null` for no menu. |
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
- render prop's `cell` is `null` only when a custom cell renderer stopped the
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.
@@ -10,7 +10,7 @@ description: >
10
10
  metadata:
11
11
  type: core
12
12
  library: '@jielga/tmdatagrid'
13
- library_version: '1.0.2'
13
+ library_version: '1.1.0'
14
14
  sources:
15
15
  - 'Jielga/TMDataGrid:src/docs/server-side.md'
16
16
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -11,7 +11,7 @@ description: >
11
11
  metadata:
12
12
  type: core
13
13
  library: '@jielga/tmdatagrid'
14
- library_version: '1.0.2'
14
+ library_version: '1.1.0'
15
15
  sources:
16
16
  - 'Jielga/TMDataGrid:src/docs/testing.md'
17
17
  - 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'