@jielga/tmdatagrid 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "A React data grid built on TanStack Table v9 and Mantine - always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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.1'
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.1'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:src/docs/cell-selection.md'
20
20
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/cellNavigation.ts'
@@ -125,7 +125,7 @@ Excel's own copy carries no header row either.
125
125
 
126
126
  Right-clicking inside the selection opens Copy, "Export as CSV for Excel" and an
127
127
  "Include headers" toggle. A right-click outside it moves the selection there
128
- first, the way a spreadsheet does. Your own `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.1'
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.1'
19
19
  sources:
20
20
  - 'Jielga/TMDataGrid:src/docs/pagination.md'
21
21
  - 'Jielga/TMDataGrid:src/docs/scrolling.md'
@@ -53,28 +53,39 @@ Initial page size is 25, through `initialState.pagination`. `enablePagination`
53
53
  is one of the two switches the grid defines itself, and the one switch that
54
54
  defaults to off.
55
55
 
56
- `TMDataGrid.Footer` takes a `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.1'
19
20
  sources:
20
21
  - 'Jielga/TMDataGrid:src/docs/editing.md'
22
+ - 'Jielga/TMDataGrid:src/docs/query-builder.md'
21
23
  - 'Jielga/TMDataGrid:src/docs/editors.md'
22
24
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/editEngine.ts'
23
25
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -41,50 +43,26 @@ every wiring question below:
41
43
  ## Setup
42
44
 
43
45
  ```tsx
44
- 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,19 +81,23 @@ One axis, four policies over the same engine.
103
81
  | `"row"` | Save in the edit lane, or Ctrl+Enter | Cancel, or Escape | generated edit lane |
104
82
  | `"batch"` | `edit.submitAll()` | `edit.cancelAll()` | `TMDataGrid.EditActions` |
105
83
 
106
- Which to pick:
107
-
108
- - 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
116
91
  clears the value and commits without opening an editor. Editing brings cell
117
92
  selection with it: `cellSelection` defaults to `"single"` while `editMode` is
118
- set.
93
+ set. Every open gesture puts the caret in the cell it named, and the grid places
94
+ it rather than the editor - a `meta.editor` needs no `autoFocus` wiring.
95
+
96
+ Under `"row"` the pencil - or a double-click on any cell, which puts the caret
97
+ in the cell clicked - opens every editable cell of the row, and ✓ saves them as
98
+ one commit. Rows accumulate: opening a second row leaves the first open, and
99
+ each row's ✓ and ✕ act on that row alone, so no row is closed or saved to make
100
+ space for the next.
119
101
 
120
102
  Under `"batch"` nothing leaves the grid until `submitAll`. Enter and Tab park
121
103
  the draft, dirty-marked, Escape drops that one draft, and drafts accumulate
@@ -153,37 +135,25 @@ Group rows and the generated lanes (checkbox, row number, details, edit) never
153
135
  edit.
154
136
 
155
137
  ```tsx
156
- // Computed column: writes back to a real field.
138
+ // Computed column writing back to a real field, and per-row gating.
157
139
  columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
158
140
  id: "fullName",
159
141
  header: "Full name",
160
142
  meta: { label: "Full name", editField: "lastName" },
161
143
  });
162
-
163
- // Editable only while the employee is active.
164
144
  columnHelper.accessor("salary", {
165
145
  header: "Salary",
166
- meta: {
167
- type: "number",
168
- align: "right",
169
- editable: (row) => row.original.status !== "Terminated",
170
- },
146
+ meta: { editable: (row) => row.original.status !== "Terminated" },
171
147
  });
172
148
  ```
173
149
 
174
150
  ## Editors and validation
175
151
 
176
- `meta.type` picks the editor, and `meta.options` feeds the select editors from
177
- the same declaration the filter panel reads.
178
-
179
- | `meta.type` | Editor |
180
- | --- | --- |
181
- | `"string"` (default) | Text input |
182
- | `"number"` | Number input |
183
- | `"boolean"` | Checkbox |
184
- | `"date"` | Native `<input type="date">`, ISO `YYYY-MM-DD` |
185
- | `"select"` | Searchable select from `meta.options`; commits on pick under `"cell"` |
186
- | `"multiSelect"` | Multi-select, same source |
152
+ `meta.type` picks the editor - `"string"` (the default), `"number"`,
153
+ `"boolean"`, `"date"`, `"select"` and `"multiSelect"` - and `meta.options` feeds
154
+ the two select editors from the same declaration the filter panel reads. Each
155
+ one, with the export that wraps it, is in
156
+ [references/editors-and-validation.md](references/editors-and-validation.md#the-built-in-editors).
187
157
 
188
158
  `meta.editor` replaces one, `meta.validate` guards the field, and
189
159
  `rowValidators` carries cross-field rules. The validators are TanStack Form's
@@ -241,11 +211,8 @@ const grid = useTMDataGrid({
241
211
  the immediate modes, so a confirmation belongs inside that callback. Under batch
242
212
  it toggles a mark instead: the row renders struck through and inert
243
213
  (`data-deleted`), the lane's trash becomes a restore, and `submitAll` reports
244
- the ids in `deleted`.
245
-
246
- Adds and deletes are applied by you, the same as edits. The engine's `tempId`
247
- (`__new__1`, `__new__2`, …) never needs to become a real id: mint one when you
248
- create the record.
214
+ the ids in `deleted`. Adds and deletes are applied by you, the same as edits -
215
+ the engine's `tempId` (`__new__1`, …) never needs to become a real id.
249
216
 
250
217
  ## The chrome
251
218
 
@@ -258,19 +225,29 @@ chrome at all by design.
258
225
  out while nothing is pending, spins while a submit is in flight, renders nothing
259
226
  while editing is off, and works under any mode, not only batch.
260
227
 
261
- ## The engine: `edit`
228
+ `renderActions` replaces the pair while handing over its pieces:
262
229
 
263
- `grid.edit` is public, and everything the chrome does goes through it.
230
+ ```tsx
231
+ <TMDataGrid.EditActions
232
+ renderActions={({ state, Controls }) => (
233
+ <Group>
234
+ {state.pendingCount > 0 && <Badge>{state.pendingCount}</Badge>}
235
+ <Controls.Save />
236
+ <Controls.Discard />
237
+ </Group>
238
+ )}
239
+ />
240
+ ```
264
241
 
265
- | Member | Does |
266
- | --- | --- |
267
- | `edit.begin({ rowId, columnId })` | Opens an editor. `columnId: null` opens the whole row |
268
- | `edit.commit(rowId)` | Commits one row. `Promise<boolean>`, `false` when blocked |
269
- | `edit.cancel(rowId)` / `edit.cancelAll()` | Drops one draft, or every draft |
270
- | `edit.submitAll()` | Commits every open row. `Promise<boolean>` |
271
- | `edit.addRow()` / `edit.deleteRow(rowId)` | Rows in and out |
272
- | `edit.getForm(rowId)` | The row's live TanStack Form `FormApi` |
273
- | `edit.store` | The projection store, for `useSelector` |
242
+ `state` is `{ pendingCount, isSubmitting }`, `actions` is `{ save, discard }`.
243
+
244
+ ## The engine: `edit`
245
+
246
+ `grid.edit` is public, and everything the chrome does goes through it:
247
+ `begin` and `commit`, `cancel` / `cancelAll`, `submitAll`, `addRow` /
248
+ `deleteRow`, `getForm`, and `store` for `useSelector`. Every member with its
249
+ signature, the gates, `clearCell`, `deactivate`, and the `edit.store` shape are
250
+ in [references/editing-api.md](references/editing-api.md#the-edit-engine).
274
251
 
275
252
  ```tsx
276
253
  import { useSelector } from "@tanstack/react-store";
@@ -281,15 +258,24 @@ const pendingCount = useSelector(
281
258
  );
282
259
  ```
283
260
 
284
- `edit.store` holds `active`, `openRowIds`, `rows` (per row `dirtyFields`,
285
- `errorFields`, `isSubmitting`), `newRows` and `deletedRowIds`. The full member
286
- list, `clearCell`, `deactivate` and the gates included, is in
287
- [references/editing-api.md](references/editing-api.md).
288
-
289
261
  `getForm` is the *one row, one form* promise made public: render that same form
290
262
  in a drawer and it shares values, dirty state and errors with the inline cells,
291
263
  because it is the same `FormApi`.
292
264
 
265
+ ## Inside an outer form
266
+
267
+ A `@tanstack/react-form` form can own the row array, with the grid as a
268
+ controlled field: `data` from `field.state.value`, and `onEditCommit` /
269
+ `onRowAdd` / `onRowDelete` composing the next array into `field.handleChange`.
270
+ Use `editMode: "row"` so a row reaches the form on approval, map by row id
271
+ (never index), and mint negative ids for new rows.
272
+
273
+ The validation split is structural, not stylistic: **a rule decidable from one
274
+ row belongs to the grid (`meta.validate`, `rowValidators`); a rule needing the
275
+ other rows or the collection ("has rows", "no duplicates") belongs to the
276
+ form's field validator.** A row's form cannot see the array, and `edit.store`
277
+ publishes field names, never values, so the form cannot see a draft.
278
+
293
279
  ## Common mistakes
294
280
 
295
281
  ### CRITICAL Expecting the grid to write into `data`
@@ -450,20 +436,31 @@ onEditCommit: async ({ rowId, changes }) => {
450
436
 
451
437
  Source: `src/tmdatagrid/useTMDataGrid.tsx` (`onEditCommit`).
452
438
 
453
- ### MEDIUM Reading a commit's result as the saved value
439
+ ### HIGH Submitting an outer form while the grid holds a draft
454
440
 
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.
441
+ The outer form's array contains only committed rows. Under any mode a mid-edit
442
+ row is invisible to it, and under `"batch"` *every* edit is until
443
+ `submitAll()` - so a form submit saves stale rows and collection rules pass
444
+ over pending values.
458
445
 
459
- Wrong:
446
+ Correct:
460
447
 
461
448
  ```tsx
462
- await grid.edit.submitAll();
463
- notifications.show({ message: "Saved" });
449
+ const hasOpenDraft = useSelector(grid.edit.store, (s) => s.openRowIds.length > 0);
450
+
451
+ <Button type="submit" disabled={!canSubmit || hasOpenDraft}>Save</Button>
452
+ // or flush instead of blocking:
453
+ const flushed = await grid.edit.submitAll();
454
+ if (flushed) await form.handleSubmit();
464
455
  ```
465
456
 
466
- Correct:
457
+ Source: `src/docs/query-builder.md` (Which mode, Submitting).
458
+
459
+ ### MEDIUM Reading a commit's result as the saved value
460
+
461
+ `edit.commit(rowId)` and `edit.submitAll()` resolve to a `boolean` saying
462
+ whether the form closed, and resolve `false` when validation or a rejected save
463
+ kept it open. Ignoring the result reports a save that did not happen.
467
464
 
468
465
  ```tsx
469
466
  const saved = await grid.edit.submitAll();
@@ -46,7 +46,7 @@ path, which may be dotted.
46
46
 
47
47
  | Member | Signature | Notes |
48
48
  | --- | --- | --- |
49
- | `begin` | `({ rowId, columnId }) => void` | `columnId: null` opens the whole row (row mode). |
49
+ | `begin` | `({ rowId, columnId }) => void` | Row mode opens the whole row either way; `columnId` says which cell takes the caret, `null` (the pencil) its first editable one. |
50
50
  | `commit` | `(rowId) => Promise<boolean>` | `false` keeps the form open with its errors. |
51
51
  | `cancel` | `(rowId) => void` | Drops one draft. |
52
52
  | `cancelAll` | `() => void` | Drops every draft. |
@@ -66,7 +66,10 @@ path, which may be dotted.
66
66
 
67
67
  ```ts
68
68
  type TMDataGridEditState = {
69
+ // The cell the last open gesture named - where the caret goes.
69
70
  active: { rowId: string; columnId: string | null } | null;
71
+ // Rows with a live form. One under `"cell"`; as many as the user opened
72
+ // under `"row"`, `"cellConfirm"` and `"batch"` - which rows are editing.
70
73
  openRowIds: ReadonlyArray<string>;
71
74
  rows: Record<
72
75
  string,
@@ -85,7 +88,7 @@ type TMDataGridEditState = {
85
88
 
86
89
  | Name | Kind | What it is |
87
90
  | --- | --- | --- |
88
- | `TMDataGrid.EditActions` | Component | Save with the pending count, and Discard. Any mode; renders nothing while editing is off. |
91
+ | `TMDataGrid.EditActions` | Component | Save with the pending count, and Discard. Any mode; renders nothing while editing is off. Takes `renderActions` over `{ state, actions, Controls }`. |
89
92
  | `TMDataGridEditActions` | Export | The same component, for use outside the namespace. |
90
93
  | `EDIT_COLUMN_ID` | Export | `"__edit__"`, the generated edit lane's id. |
91
94
  | `clearedValueForType` | Export | `(type) => unknown` - what Delete writes per column type. |
@@ -43,11 +43,17 @@ type TMDataGridEditorArgs = {
43
43
  commit: () => Promise<boolean>; // what Enter would do
44
44
  cancel: () => void; // what Escape would do
45
45
  size: TMDataGridSize;
46
- autoFocus: boolean; // take focus on mount
46
+ autoFocus: boolean; // deprecated - the grid places the caret itself
47
47
  seedText?: string; // set when typing opened the editor
48
48
  };
49
49
  ```
50
50
 
51
+ The grid places the caret itself, once per open gesture: it focuses
52
+ `data-dg-part="editor-input"` when the editor publishes it, and the first
53
+ focusable element inside the editor otherwise. So a custom editor gets the caret
54
+ without honouring `autoFocus` - set `editor-input` only to name which of several
55
+ inputs should take it.
56
+
51
57
  Bind any control to `field` exactly as inside any TanStack Form:
52
58
  `field.state.value`, `field.state.meta.errors`, `field.handleChange`,
53
59
  `field.handleBlur`.
@@ -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.1'
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.1'
15
15
  sources:
16
16
  - 'Jielga/TMDataGrid:src/docs/getting-started.md'
17
17
  - 'Jielga/TMDataGrid:src/docs/anatomy.md'
@@ -149,14 +149,14 @@ flex column.
149
149
  | Component | Props | Notes |
150
150
  | --- | --- | --- |
151
151
  | `TMDataGrid` | `table`, `ui`, `features`, `size`, `className`, `style` | Root. Provides context. `style` also takes CSS variables: `--dg-row-selected-bg`, `--dg-row-height`, `--dg-header-height`, `--dg-font-size`, `--dg-padding`. |
152
- | `TMDataGrid.Table` | `onRowClick(row)`, `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.1'
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.1'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
20
20
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'