@jielga/tmdatagrid 2.0.0-beta.2 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -212
- package/dist/index.d.ts +1664 -632
- package/dist/index.js +5226 -3223
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/anatomy.md +102 -0
- package/docs/cell-selection.md +154 -0
- package/docs/column-layout.md +204 -0
- package/docs/columns.md +262 -0
- package/docs/components.md +304 -0
- package/docs/editing.md +603 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +326 -0
- package/docs/filtering.md +358 -0
- package/docs/getting-started.md +123 -0
- package/docs/grouping.md +165 -0
- package/docs/loading-and-empty.md +92 -0
- package/docs/localization.md +79 -0
- package/docs/menu.md +143 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +111 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +175 -0
- package/docs/quick-search.md +83 -0
- package/docs/row-details.md +113 -0
- package/docs/row-interaction.md +148 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +134 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +111 -0
- package/docs/server-query.md +246 -0
- package/docs/server-side.md +206 -0
- package/docs/sorting.md +101 -0
- package/docs/styling.md +126 -0
- package/docs/summary-row.md +76 -0
- package/docs/testing.md +309 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +21 -45
- package/skills/appearance/SKILL.md +70 -17
- package/skills/cell-selection/SKILL.md +70 -76
- package/skills/columns/SKILL.md +131 -32
- package/skills/data/SKILL.md +100 -23
- package/skills/editing/SKILL.md +217 -96
- package/skills/editing/references/common-mistakes.md +111 -24
- package/skills/editing/references/editing-api.md +63 -39
- package/skills/editing/references/editors-and-validation.md +77 -19
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +18 -16
- package/skills/grouping/SKILL.md +32 -15
- package/skills/options/SKILL.md +39 -9
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +10 -7
- package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
- package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +39 -23
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +106 -38
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +4 -4
- package/src/components/TMDataGridDraftActions.tsx +307 -0
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +58 -50
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +150 -115
- package/src/components/TMDataGridExportPicker.module.css +77 -0
- package/src/components/TMDataGridExportPicker.tsx +234 -0
- package/src/components/TMDataGridFilterPanel.module.css +54 -0
- package/src/components/TMDataGridFilterPanel.tsx +348 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +7 -5
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -13
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +4 -3
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +10 -0
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +100 -28
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +354 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +12 -7
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +90 -67
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +678 -156
- package/src/components/TMDataGridToolbar.module.css +21 -0
- package/src/components/TMDataGridToolbar.tsx +181 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
- package/src/components/editors/TMDataGridNumberEditor.tsx +70 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/editorShared.ts +17 -31
- package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
- package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
- package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
- package/src/components/filters/controlLayout.ts +32 -0
- package/src/components/filters/filterControlFor.ts +65 -0
- package/src/{tmdatagrid/components → components}/icons.ts +1 -0
- package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
- package/src/components/useHideableColumns.ts +52 -0
- package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
- package/src/{tmdatagrid/core → core}/capabilities.ts +14 -6
- package/src/{tmdatagrid/core → core}/columnOptions.ts +46 -0
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledState.ts +179 -0
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/core/editEngine.ts +2476 -0
- package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
- package/src/core/export.ts +843 -0
- package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
- package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
- package/src/core/filterSurface.ts +99 -0
- package/src/{tmdatagrid/core → core}/labels.ts +66 -8
- package/src/{tmdatagrid/core → core}/labelsSv.ts +26 -3
- package/src/core/pageReset.ts +120 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/core/resizePreview.ts +141 -0
- package/src/core/summary.ts +59 -0
- package/src/core/useSettledTableState.ts +36 -0
- package/src/{tmdatagrid/index.ts → index.ts} +75 -12
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +734 -135
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -162
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +0 -12
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -162
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +0 -40
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- package/src/tmdatagrid/core/editEngine.ts +0 -1006
- package/src/tmdatagrid/core/summary.ts +0 -35
- /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
- /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/grouping.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Server-side data
|
|
2
|
+
|
|
3
|
+
When the client holds one page and the server does the work, paging, sorting and
|
|
4
|
+
filtering all become round trips and the grid stops doing them itself.
|
|
5
|
+
|
|
6
|
+
The grid reads rows through `getPaginatedRowModel()` and totals through
|
|
7
|
+
`getRowCount()` and `getPageCount()`, all of which respect TanStack's manual
|
|
8
|
+
modes. A server-driven grid therefore requires only the standard `manual*`
|
|
9
|
+
configuration. `manualPagination: true` also switches the grid's pagination flag
|
|
10
|
+
on, so `TMDataGrid.Footer` renders its pager without `enablePagination`.
|
|
11
|
+
|
|
12
|
+
When the total is unknown, declare `pageCount: -1`: the next button stays
|
|
13
|
+
enabled and the `pagination` render prop receives `pageCount: -1`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 });
|
|
19
|
+
const [sorting, setSorting] = useState([]);
|
|
20
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
21
|
+
|
|
22
|
+
const { data, isFetching } = useQuery({
|
|
23
|
+
queryKey: ["employees", pagination, sorting, columnFilters],
|
|
24
|
+
queryFn: () => fetchEmployees({ pagination, sorting, columnFilters }),
|
|
25
|
+
placeholderData: keepPreviousData,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const grid = useTMDataGrid({
|
|
29
|
+
columns,
|
|
30
|
+
data: data?.rows ?? [],
|
|
31
|
+
getRowId: (row) => String(row.id),
|
|
32
|
+
|
|
33
|
+
manualPagination: true,
|
|
34
|
+
manualSorting: true,
|
|
35
|
+
manualFiltering: true,
|
|
36
|
+
rowCount: data?.total ?? 0,
|
|
37
|
+
|
|
38
|
+
state: { pagination, sorting, columnFilters },
|
|
39
|
+
onPaginationChange: setPagination,
|
|
40
|
+
onSortingChange: setSorting,
|
|
41
|
+
onColumnFiltersChange: setColumnFilters,
|
|
42
|
+
|
|
43
|
+
meta: { loading: isFetching, totalRowCount: data?.totalUnfiltered },
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```demo
|
|
48
|
+
file: data/ServerSide.tsx
|
|
49
|
+
hint: Sorting, searching and paging are all round trips against a server with 500 ms of latency.
|
|
50
|
+
extraSources: data/orders.ts
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Differences from client-side data
|
|
54
|
+
|
|
55
|
+
| Concern | Client-side | Server-side |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Rendered rows | The current page, sliced locally | The rows returned by the server |
|
|
58
|
+
| Footer total | Pre-paginated row count | `options.rowCount` |
|
|
59
|
+
| `SummaryCount` total | Pre-filtered row count | `meta.totalRowCount`; without it, the count renders alone |
|
|
60
|
+
| Loading state | Not applicable | `meta.loading` |
|
|
61
|
+
|
|
62
|
+
No additional configuration is required. Column menus, the filter panel and the
|
|
63
|
+
column manager behave identically in both modes.
|
|
64
|
+
|
|
65
|
+
## The page index
|
|
66
|
+
|
|
67
|
+
A column filter, the quick search or a sort changes what page 3 means.
|
|
68
|
+
The result set is a different one, and it may not have a page 3 at all - so the grid resets `pageIndex` to 0 whenever the query changes, on every grid.
|
|
69
|
+
The reset is applied in the same event as the change, so one request goes out, for the first page of the new query.
|
|
70
|
+
|
|
71
|
+
`resetPageOnQueryChange: false` switches it off.
|
|
72
|
+
|
|
73
|
+
TanStack's `autoResetPageIndex` is a different rule and does not cover this.
|
|
74
|
+
It fires on a change to `data` - which server-side is the response landing, after the request was sent, and under `editing.draft` every commit - so the grid switches it off everywhere and resets on the query change itself.
|
|
75
|
+
|
|
76
|
+
## Column options
|
|
77
|
+
|
|
78
|
+
`meta.options: "faceted"` reads the distinct values present in `data`, which server-side is one page of them.
|
|
79
|
+
The dropdown then offers whatever happened to be on the page the user is looking at, and looks correct while being wrong.
|
|
80
|
+
Declare the set instead, as a list or a function.
|
|
81
|
+
The grid warns once per column when a faceted column resolves under `manualFiltering` or `manualPagination`.
|
|
82
|
+
|
|
83
|
+
## Sending filters
|
|
84
|
+
|
|
85
|
+
Filter values are plain JSON:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
[
|
|
89
|
+
{ "id": "lastName", "value": { "operator": "contains", "value": "holm" } },
|
|
90
|
+
{ "id": "age", "value": { "operator": "greaterThan", "value": "30" } },
|
|
91
|
+
{ "id": "status", "value": { "operator": "isAnyOf", "value": ["Paid", "Pending"] } },
|
|
92
|
+
{ "id": "hired", "value": { "operator": "onOrAfter", "value": "2026-01-01" } }
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Forward `columnFilters` unchanged and translate it at the API boundary.
|
|
97
|
+
`activeColumnFilters` hands back the entries that are narrowing the grid, typed:
|
|
98
|
+
`ColumnFiltersState` types `value` as `unknown`, and an entry whose value is
|
|
99
|
+
still empty matches every row.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { activeColumnFilters } from "@jielga/tmdatagrid";
|
|
103
|
+
|
|
104
|
+
const active = activeColumnFilters(columnFilters);
|
|
105
|
+
// [{ id: "lastName", value: { operator: "contains", value: "holm" } }]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
It takes the `columnFilters` array, or the table where the grid owns the slice.
|
|
109
|
+
`isFilterActive(value)` is the single-value test it is built on.
|
|
110
|
+
Only the grid's own `{ operator, value }` shape is read: an entry holding some
|
|
111
|
+
other value - a custom filter control writing raw values - is dropped.
|
|
112
|
+
|
|
113
|
+
Debounce requests. The filter value input updates on every keystroke.
|
|
114
|
+
|
|
115
|
+
An endpoint that answers only some operators - `contains` and `equals` but no
|
|
116
|
+
`startsWith` - should not have the rest offered. `meta.filter.operators` on the
|
|
117
|
+
column narrows the panel and the header funnel to the operators the query can
|
|
118
|
+
express; see [Operators](/docs/filtering#operators).
|
|
119
|
+
|
|
120
|
+
For an endpoint that speaks its own query language rather than taking the
|
|
121
|
+
grid's filter model, see [A server-backed search](/docs/server-query): one
|
|
122
|
+
mapping layer turning filters, sorting and the page index into a request body,
|
|
123
|
+
and the response envelope back into rows.
|
|
124
|
+
|
|
125
|
+
## Persistence
|
|
126
|
+
|
|
127
|
+
`persist` works unchanged. `dataKey` restores filters, sorting and pagination
|
|
128
|
+
before the first request, so reloading the page repeats the query the user last
|
|
129
|
+
ran.
|
|
130
|
+
|
|
131
|
+
If the same state is also held in your own `useState`, initialise it from the
|
|
132
|
+
same source or let the grid own it. Do not maintain both independently.
|
|
133
|
+
|
|
134
|
+
## Row selection
|
|
135
|
+
|
|
136
|
+
Row selection is keyed by `getRowId`, so ids must be stable across pages. With
|
|
137
|
+
`manualPagination`, rows selected on an earlier page remain in `rowSelection`
|
|
138
|
+
even though they are no longer mounted. Read the state rather than the row
|
|
139
|
+
models:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const selectedIds = Object.keys(grid.table.store.state.rowSelection);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Infinite scroll
|
|
146
|
+
|
|
147
|
+
An alternative to the pager: keep every fetched row in `data` and load more as
|
|
148
|
+
the user scrolls. `onReachEnd` on `TMDataGrid.Table` fires as the scroll nears
|
|
149
|
+
the last row. Append the next page and the virtualizer keeps its position:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
const [rows, setRows] = useState<Order[]>([]);
|
|
153
|
+
|
|
154
|
+
const grid = useTMDataGrid({
|
|
155
|
+
data: rows,
|
|
156
|
+
columns,
|
|
157
|
+
getRowId: (row) => String(row.id),
|
|
158
|
+
meta: { loading: isFetching, totalRowCount: total },
|
|
159
|
+
enableSorting: false,
|
|
160
|
+
enableColumnFilters: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
<TMDataGrid.Table onReachEnd={() => void fetchNextPage()} />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```demo
|
|
167
|
+
file: data/InfiniteScroll.tsx
|
|
168
|
+
hint: Scroll to the bottom and keep going. 100 rows arrive at a time and the scroll position holds.
|
|
169
|
+
extraSources: data/orders.ts
|
|
170
|
+
height: 460
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Fetch page zero yourself on mount. `onReachEnd` does not fire on an empty grid.
|
|
174
|
+
|
|
175
|
+
`onReachEnd` fires once per row count, so a pending fetch is not requested again
|
|
176
|
+
until its rows land. `reachEndThreshold` (default 10) sets how many rows before
|
|
177
|
+
the end it fires. `TMDataGrid.LoadingIndicator` in the toolbar shows the fetch,
|
|
178
|
+
since the body keeps showing the rows it already has.
|
|
179
|
+
|
|
180
|
+
Two constraints apply:
|
|
181
|
+
|
|
182
|
+
- **Sorting and filtering must be server-side** (`manualSorting` /
|
|
183
|
+
`manualFiltering`) or disabled. The client only holds a prefix of the data, so
|
|
184
|
+
a client-side sort would order that prefix and present it as the whole. When a
|
|
185
|
+
server-side sort or filter changes, reset the accumulated rows and start from
|
|
186
|
+
page zero.
|
|
187
|
+
- **Not compatible with `enablePagination`.** The pager slices the same scroll
|
|
188
|
+
the callback watches, so the end reached is the page's rather than the data's.
|
|
189
|
+
The grid warns once if both are set.
|
|
190
|
+
|
|
191
|
+
## Reference
|
|
192
|
+
|
|
193
|
+
| Name | Kind | Type | Default | What it does |
|
|
194
|
+
| --- | --- | --- | --- | --- |
|
|
195
|
+
| `manualPagination` | Table option | `boolean` | `false` | The server pages. Implies `enablePagination`. |
|
|
196
|
+
| `manualSorting` | Table option | `boolean` | `false` | The server sorts. |
|
|
197
|
+
| `manualFiltering` | Table option | `boolean` | `false` | The server filters, column filters and quick search alike. |
|
|
198
|
+
| `manualGrouping` | Table option | `boolean` | `false` | The rows arrive grouped. See [Grouping](/docs/grouping#server-side-grids). |
|
|
199
|
+
| `rowCount` | Table option | `number` | – | The true total. `pageCount: -1` when it is unknown. |
|
|
200
|
+
| `meta.loading` | Option | `boolean` | `false` | A fetch is in flight. See [Loading and empty states](/docs/loading-and-empty). |
|
|
201
|
+
| `meta.totalRowCount` | Option | `number` | – | The unfiltered total, for `SummaryCount`. |
|
|
202
|
+
| `resetPageOnQueryChange` | Option | `boolean` | `true` | Back to page 1 when a filter, the quick search, the sort or the grouping changes. |
|
|
203
|
+
| `onReachEnd` | Table prop | `() => void` | – | Fires as the scroll nears the last row. Latches per row count. |
|
|
204
|
+
| `reachEndThreshold` | Table prop | `number` | `10` | How many rows before the end it fires. |
|
|
205
|
+
| `activeColumnFilters` | Export | `(columnFilters \| table) => Array<{ id, value }>` | – | The filters in the grid's own value shape that narrow anything, typed. |
|
|
206
|
+
| `isFilterActive` | Export | `(value) => boolean` | – | Whether one filter value narrows anything. |
|
package/docs/sorting.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Sorting
|
|
2
|
+
|
|
3
|
+
On by default. Click a header to sort it, click again to reverse, click a third
|
|
4
|
+
time to clear. The column menu has the same three actions.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
const grid = useTMDataGrid({ data, columns });
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```demo
|
|
11
|
+
file: columns/Sorting.tsx
|
|
12
|
+
hint: Click a header to sort, Shift+click a second to append - the badge beside the arrow is its priority.
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Sorting by more than one column
|
|
16
|
+
|
|
17
|
+
Shift+click a second header to **add** it to the sort rather than replace it.
|
|
18
|
+
While more than one column sorts, each sorted header shows its priority - 1, 2,
|
|
19
|
+
… - beside the arrow, so the order they are applied in is visible.
|
|
20
|
+
|
|
21
|
+
A plain click still replaces the whole sort, and the menu's Sort items do the
|
|
22
|
+
same.
|
|
23
|
+
|
|
24
|
+
This is TanStack's own `isMultiSortEvent`, so `enableMultiSort`,
|
|
25
|
+
`maxMultiSortColCount` and a custom `isMultiSortEvent` all pass straight
|
|
26
|
+
through:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
const grid = useTMDataGrid({
|
|
30
|
+
data,
|
|
31
|
+
columns,
|
|
32
|
+
maxMultiSortColCount: 3,
|
|
33
|
+
// Ctrl rather than Shift, say.
|
|
34
|
+
isMultiSortEvent: (event) => event.ctrlKey,
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Turning it off
|
|
39
|
+
|
|
40
|
+
`enableSorting: false` on the table removes click-to-sort, the indicator and
|
|
41
|
+
the menu items everywhere; on a column it removes them for that column alone.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
columnHelper.accessor("avatar", { header: "", enableSorting: false });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A column whose menu has no remaining items renders no menu button and does not
|
|
48
|
+
handle right-click, so the browser's own menu opens instead.
|
|
49
|
+
|
|
50
|
+
## Where the state lives
|
|
51
|
+
|
|
52
|
+
Sorting writes TanStack's `sorting` state, an array of `{ id, desc }` in
|
|
53
|
+
priority order. Seed it, control it, or read it like any other slice:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const grid = useTMDataGrid({
|
|
57
|
+
data,
|
|
58
|
+
columns,
|
|
59
|
+
initialState: { sorting: [{ id: "lastName", desc: false }] },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
It is a **data** slice: it names a column and a direction over the data itself,
|
|
64
|
+
so a [persisted](/docs/use-tm-data-grid#persist) grid comes back sorted the way
|
|
65
|
+
it was left, under `dataKey`. For a server that does the sorting, see
|
|
66
|
+
[Server-side data](/docs/server-side).
|
|
67
|
+
|
|
68
|
+
Grouping runs first, so a grouped grid sorts rows within each group and orders
|
|
69
|
+
the groups by their aggregated value. See
|
|
70
|
+
[Grouping](/docs/grouping#sorting-a-grouped-grid).
|
|
71
|
+
|
|
72
|
+
## Custom comparators
|
|
73
|
+
|
|
74
|
+
`sortFn` on the column takes any of TanStack's registered names, or a function
|
|
75
|
+
of two rows and the column id. It is `sortFn` in v9, not v8's `sortingFn`.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
columnHelper.accessor("priority", {
|
|
79
|
+
header: "Priority",
|
|
80
|
+
sortFn: (rowA, rowB) =>
|
|
81
|
+
RANK[rowA.original.priority] - RANK[rowB.original.priority],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## The header menu
|
|
86
|
+
|
|
87
|
+
The menu opens from the ⋮ button on the header, or from a right-click anywhere
|
|
88
|
+
on it. The items are the same either way; a right-click opens it at the
|
|
89
|
+
pointer.
|
|
90
|
+
|
|
91
|
+
## Reference
|
|
92
|
+
|
|
93
|
+
| Name | Kind | Type | Default | What it does |
|
|
94
|
+
| --- | --- | --- | --- | --- |
|
|
95
|
+
| `enableSorting` | Table option | `boolean` | `true` | Also a column option. `false` removes indicator, menu items and click-to-sort. |
|
|
96
|
+
| `enableMultiSort` | Table option | `boolean` | `true` | Whether Shift+click appends instead of replacing. |
|
|
97
|
+
| `maxMultiSortColCount` | Table option | `number` | `Infinity` | How many columns may sort at once. |
|
|
98
|
+
| `isMultiSortEvent` | Table option | `(event) => boolean` | Shift held | What counts as "append to the sort". |
|
|
99
|
+
| `sortFn` | Column option | name \| `(rowA, rowB, columnId) => number` | `"auto"` | The comparator for one column. v9's name for v8's `sortingFn`. |
|
|
100
|
+
| `initialState.sorting` | Table option | `Array<{ id, desc }>` | `[]` | Sort at mount. A settings slice, so it persists. |
|
|
101
|
+
| `manualSorting` | Table option | `boolean` | `false` | The server sorts; the grid stops. |
|
package/docs/styling.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Size, styling and theming
|
|
2
|
+
|
|
3
|
+
The grid is themed through CSS custom properties, and sized through Mantine's
|
|
4
|
+
standard `size` scale. Both are set on the root element, so a grid can be
|
|
5
|
+
themed per instance without a provider.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<TMDataGrid {...grid} size="sm" style={{ "--dg-row-selected-bg": "color-mix(in srgb, var(--mantine-color-blue-6) 12%, transparent)" }} />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```demo
|
|
12
|
+
file: customization/Styling.tsx
|
|
13
|
+
hint: Every value the controls change is a CSS variable set on the grid element.
|
|
14
|
+
extraSources: data/employeeColumns.tsx
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The size scale
|
|
18
|
+
|
|
19
|
+
`size` drives row height, header height, font size and cell padding together,
|
|
20
|
+
and selects the size of every Mantine control the grid renders - the page-size
|
|
21
|
+
select, the filter inputs, the column checkboxes.
|
|
22
|
+
|
|
23
|
+
| `size` | Row height | Header height | Font size | Cell padding |
|
|
24
|
+
| --- | --- | --- | --- | --- |
|
|
25
|
+
| `xs` | 34px | 32px | `xs` | 6px |
|
|
26
|
+
| `sm` | 42px | 38px | `sm` | 8px |
|
|
27
|
+
| `md` (default) | 52px | 44px | `sm` | 10px |
|
|
28
|
+
| `lg` | 62px | 52px | `md` | 14px |
|
|
29
|
+
| `xl` | 72px | 60px | `lg` | 18px |
|
|
30
|
+
|
|
31
|
+
```demo
|
|
32
|
+
file: getting-started/DensityAndLayout.tsx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Row height is also required by the virtualizer **as a number**, so it cannot be
|
|
36
|
+
defined in CSS alone. `SIZE_ROW_HEIGHT` is the exported source of these values,
|
|
37
|
+
and the stylesheet mirrors them. To use a height outside the scale, set
|
|
38
|
+
`meta.rowHeight` rather than the variable.
|
|
39
|
+
|
|
40
|
+
## CSS variables
|
|
41
|
+
|
|
42
|
+
`style` accepts custom properties, and `className` reaches the same element
|
|
43
|
+
from a stylesheet.
|
|
44
|
+
The wrapper components - `Toolbar`, `Spacer`, `Footer`, `FilterPanel`, `FilterPills` and `ColumnsPanel` - take Mantine's `BoxProps` on top of their own props, so `mb="sm"` or `hiddenFrom="sm"` on any of them sets the element itself; see [Toolbar](/docs/toolbar#style-props).
|
|
45
|
+
|
|
46
|
+
### Metrics
|
|
47
|
+
|
|
48
|
+
| Variable | Default | Applies to |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `--dg-row-height` | From `size` | Row height. Prefer `meta.rowHeight` - the virtualizer needs the number. |
|
|
51
|
+
| `--dg-header-height` | From `size` | Header row height |
|
|
52
|
+
| `--dg-summary-height` | From `size` | [Summary row](/docs/summary-row) height |
|
|
53
|
+
| `--dg-entry-height` | From `size` | The sticky [entry block](/docs/editing#adding-and-deleting-rows) |
|
|
54
|
+
| `--dg-font-size` | From `size` | Cell and header font size |
|
|
55
|
+
| `--dg-padding` | From `size` | Horizontal cell padding. The generated lanes are excluded: they are fixed 36px tracks that centre their control. |
|
|
56
|
+
| `--dg-radius` | `--mantine-radius-md` | The frame's corner radius. `0` squares the grid off. The root clips its overflow, so the header and the last row follow it. |
|
|
57
|
+
|
|
58
|
+
### Colours
|
|
59
|
+
|
|
60
|
+
Both colour schemes are supported. The grid's own stylesheet resolves its
|
|
61
|
+
colours with `light-dark()`, so it follows Mantine's scheme without a prop or a
|
|
62
|
+
second import. A default of **Themed** below means exactly that: the variable
|
|
63
|
+
resolves to one value under the light scheme and another under the dark one.
|
|
64
|
+
Set such a variable and you take over both schemes, so give it a value that
|
|
65
|
+
reads in each - `light-dark()` works in your own value too.
|
|
66
|
+
|
|
67
|
+
| Variable | Default | Applies to |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `--row-bg` | – | One row's own background. Set this, never `background`. See [Row styling](/docs/row-styling#set-the-row-background). |
|
|
70
|
+
| `--dg-row-selected-bg` | `--mantine-primary-color-light` | [Selected](/docs/row-selection) rows |
|
|
71
|
+
| `--dg-row-highlight-bg` | Themed | The highlighted row |
|
|
72
|
+
| `--dg-row-striped-bg` | Themed | Every second row under `striped` |
|
|
73
|
+
| `--dg-row-group-bg` | Themed | [Group](/docs/grouping) rows |
|
|
74
|
+
| `--dg-row-new-bg` | Green tint | New rows entered into the [draft store](/docs/editing#the-draft-store) |
|
|
75
|
+
| `--dg-match-highlight-bg` | Themed yellow | [Marked](/docs/quick-search#match-highlighting) text |
|
|
76
|
+
| `--dg-header-shadow-color` | Themed | The shadow under the sticky header |
|
|
77
|
+
|
|
78
|
+
### Layout internals
|
|
79
|
+
|
|
80
|
+
| Variable | Default | Applies to |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `--dg-sticky-edge-range` | `20px` | How far the pinned-lane band takes to fade in |
|
|
83
|
+
| `--dg-edge-top` · `-bottom` · `-left` · `-right` | – | Set by the grid to mark [cell-range](/docs/cell-selection) borders |
|
|
84
|
+
| `--dg-z-header` · `-pinned-cell` · `-summary-row` · … | – | The stacking order. Change these only to place something of your own between two layers. |
|
|
85
|
+
|
|
86
|
+
## The stylesheet
|
|
87
|
+
|
|
88
|
+
One import, once, anywhere in your app:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import "@jielga/tmdatagrid/styles.css";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`@jielga/tmdatagrid/styles.layer.css` is the same stylesheet wrapped in a
|
|
95
|
+
`@layer`, for an application that orders its own layers and needs the grid to
|
|
96
|
+
sit at a known place in that order. Import **one** of the two, never both.
|
|
97
|
+
|
|
98
|
+
## Layout
|
|
99
|
+
|
|
100
|
+
The grid fills the box you give it and scrolls inside it. It does not size
|
|
101
|
+
itself to its content: a virtualized grid has no content height to measure.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
|
|
105
|
+
<TMDataGrid {...grid} style={{ flex: 1, minHeight: 0 }}>
|
|
106
|
+
<TMDataGrid.Table />
|
|
107
|
+
</TMDataGrid>
|
|
108
|
+
</div>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`minHeight: 0` is required. A flex item's default `min-height: auto` will not
|
|
112
|
+
shrink below its content, so without it the grid grows past the viewport instead
|
|
113
|
+
of scrolling.
|
|
114
|
+
|
|
115
|
+
## Reference
|
|
116
|
+
|
|
117
|
+
| Name | Kind | Type | Default | What it does |
|
|
118
|
+
| --- | --- | --- | --- | --- |
|
|
119
|
+
| `size` | Prop | `MantineSize` | `"md"` | The whole density scale. |
|
|
120
|
+
| `className` | Prop | `string` | – | Added to the root element's classes. |
|
|
121
|
+
| `style` | Prop | `CSSProperties` + `--*` | – | Root element styles, including the variables above. |
|
|
122
|
+
| `id` | Prop | `string` | – | Set on the root element. |
|
|
123
|
+
| `meta.rowHeight` | Option | `number` | From `size` | A row height outside the scale. |
|
|
124
|
+
| `SIZE_ROW_HEIGHT` | Export | `Record<MantineSize, number>` | – | The row heights listed in the table above. |
|
|
125
|
+
| `SIZE_CONTROL_SIZE` | Export | `Record<MantineSize, MantineSize>` | – | Which control size each grid size uses. |
|
|
126
|
+
| `DEFAULT_TMDATAGRID_SIZE` | Export | `"md"` | – | The default. |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Summary row
|
|
2
|
+
|
|
3
|
+
A sticky row along the bottom edge holding totals for the whole grid: a count,
|
|
4
|
+
a sum, or any other single number per column. It is independent of
|
|
5
|
+
[grouping](/docs/grouping), which totals each category instead of everything.
|
|
6
|
+
|
|
7
|
+
There is no flag. Give a column a `footer` and the row appears. The row exists
|
|
8
|
+
whenever at least one visible column defines one.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
columnHelper.accessor("salary", {
|
|
12
|
+
header: "Salary",
|
|
13
|
+
footer: ({ table }) =>
|
|
14
|
+
sek(Number(aggregateColumn({ table, columnId: "salary" }))),
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```demo
|
|
19
|
+
file: rows/SummaryRow.tsx
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`footer` is TanStack's own column option, rendered the way the header is: each
|
|
23
|
+
cell renders that column's renderer with the header context. It can render
|
|
24
|
+
anything: a static label, a count, or its own calculation.
|
|
25
|
+
|
|
26
|
+
## Totalling a column
|
|
27
|
+
|
|
28
|
+
`aggregateColumn({ table, columnId, fn })` computes over every **filtered** row
|
|
29
|
+
(all pages, following the filters live) through the registered aggregation
|
|
30
|
+
functions. `fn` defaults to `"sum"`.
|
|
31
|
+
|
|
32
|
+
Every data row counts once.
|
|
33
|
+
Grouping builds its group rows from this model rather than into it, so a grouped grid totals its records and not its records plus their subtotals.
|
|
34
|
+
A tree built with `getSubRows` counts parents and children alike.
|
|
35
|
+
|
|
36
|
+
It totals `data`, so under [`editing.draft`](/docs/editing) a committed edit is not in the total until `edit.saveDrafts()` sends it and the new `data` arrives.
|
|
37
|
+
An edited cell shows its draft and the summary row does not follow it.
|
|
38
|
+
The same holds for a group row's `aggregatedCell`: no group row has a form, so aggregates read the committed values throughout.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
aggregateColumn({ table, columnId: "salary" }); // sum
|
|
42
|
+
aggregateColumn({ table, columnId: "age", fn: "mean" }); // average
|
|
43
|
+
aggregateColumn({ table, columnId: "location", fn: "uniqueCount" });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It takes `table`, so the same total can be read anywhere the table is in
|
|
47
|
+
reach - a toolbar readout as much as a `footer`:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
const { table } = useTMDataGrid({ data, columns });
|
|
51
|
+
|
|
52
|
+
<TMDataGrid.Toolbar>
|
|
53
|
+
<Text size="xs">
|
|
54
|
+
Payroll {sek(Number(aggregateColumn({ table, columnId: "salary" })))}
|
|
55
|
+
</Text>
|
|
56
|
+
</TMDataGrid.Toolbar>;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Layout
|
|
60
|
+
|
|
61
|
+
Pinned columns keep their lanes in the summary row, and the row sits under the
|
|
62
|
+
pinned-lane gradients in the stacking order. The generated lanes - checkbox,
|
|
63
|
+
tree, details, row numbers - define no `footer`, so their summary cells stay
|
|
64
|
+
blank.
|
|
65
|
+
|
|
66
|
+
The row is sticky, so it stays put while the body scrolls, and its height is
|
|
67
|
+
`--dg-summary-height`.
|
|
68
|
+
|
|
69
|
+
## Reference
|
|
70
|
+
|
|
71
|
+
| Name | Kind | Type | Default | What it does |
|
|
72
|
+
| --- | --- | --- | --- | --- |
|
|
73
|
+
| `footer` | Column option | `(ctx) => ReactNode` | – | Renders this column's summary cell. Defining one on any column adds the row. |
|
|
74
|
+
| `aggregateColumn` | Export | `({ table, columnId, fn }) => unknown` | `fn: "sum"` | Aggregates a column over every filtered row, all pages. |
|
|
75
|
+
| `TMDataGridAggregationName` | Export | type | – | The registered function names - `sum`, `mean`, `count`, `uniqueCount`, … |
|
|
76
|
+
| `--dg-summary-height` | CSS variable | length | From `size` | Height of the summary row. |
|