@jielga/tmdatagrid 2.1.3 → 2.2.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/README.md +1 -1
- package/dist/index.d.ts +38 -2
- package/dist/index.js +1329 -1246
- package/dist/index.js.map +1 -1
- package/docs/cell-selection.md +1 -1
- package/docs/column-layout.md +13 -8
- package/docs/columns.md +57 -2
- package/docs/export.md +2 -2
- package/docs/filtering.md +105 -15
- package/docs/getting-started.md +40 -0
- package/docs/grouping.md +9 -6
- package/docs/loading-and-empty.md +29 -9
- package/docs/localization.md +130 -3
- package/docs/menu.md +1 -1
- package/docs/pagination.md +26 -7
- package/docs/row-details.md +36 -4
- package/docs/row-interaction.md +1 -1
- package/docs/row-selection.md +6 -7
- package/docs/scrolling.md +1 -1
- package/docs/server-side.md +3 -1
- package/docs/testing.md +18 -0
- package/docs/use-tm-data-grid.md +69 -34
- package/package.json +2 -2
- package/skills/appearance/SKILL.md +6 -4
- package/skills/appearance/references/labels.md +126 -0
- package/skills/cell-selection/SKILL.md +2 -2
- package/skills/columns/SKILL.md +42 -41
- package/skills/columns/references/columns-api.md +15 -1
- package/skills/data/SKILL.md +77 -50
- package/skills/editing/SKILL.md +1 -1
- package/skills/filtering/SKILL.md +71 -49
- package/skills/filtering/references/custom-filters.md +118 -0
- package/skills/getting-started/SKILL.md +37 -2
- package/skills/grouping/SKILL.md +16 -9
- package/skills/migrating-to-2/SKILL.md +1 -1
- package/skills/options/SKILL.md +131 -51
- package/skills/rows/SKILL.md +53 -31
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +12 -1
- package/skills/testing-components/SKILL.md +1 -1
- package/skills/testing-editing/SKILL.md +1 -1
- package/src/components/TMDataGridTable.tsx +241 -41
- package/src/core/autosize.ts +19 -7
- package/src/core/cellRange.ts +11 -2
- package/src/core/colSpan.ts +78 -0
- package/src/core/columnUtils.ts +7 -0
- package/src/core/filterOperators.ts +16 -1
- package/src/index.ts +4 -0
- package/src/useTMDataGrid.tsx +38 -0
package/docs/cell-selection.md
CHANGED
|
@@ -180,7 +180,7 @@ Unlike the Export cells item, `resolveRangeBounds` does not fall back to the foc
|
|
|
180
180
|
| `cellSelection` | Option | `"none" \| "single" \| "range"` | `"none"` | Turns the cell cursor, and the rectangle, on. |
|
|
181
181
|
| `TMDataGridCellSelectionMode` | Type | – | – | The type of `cellSelection`. |
|
|
182
182
|
| `onFocusedCellChange` | Callback | `(cell \| null) => void` | – | Follows the cursor. |
|
|
183
|
-
| `exportOptions` | Option | `TMDataGridExportOptions` | `
|
|
183
|
+
| `exportOptions` | Option | `TMDataGridExportOptions` | `csvExcelFormat()`, `"export"`, headers on, `"visible"` columns | Format, file name and header row of the Export cells item. See [Export](/docs/export). |
|
|
184
184
|
| `ui.state.focusedCell` | UI state | `{ rowId, columnId } \| null` | `null` | The cursor. |
|
|
185
185
|
| `ui.state.cellRange` | UI state | `{ anchor, focus } \| null` | `null` | The rectangle's two corners. |
|
|
186
186
|
| `ui.actions.setFocusedCell` · `setCellRange` | UI actions | – | – | Move either from your own code. |
|
package/docs/column-layout.md
CHANGED
|
@@ -14,7 +14,11 @@ hint: Drag a header to reorder · pin or hide from a column menu · drag a divid
|
|
|
14
14
|
## Hiding
|
|
15
15
|
|
|
16
16
|
**Hide column** in any column menu, and the column chooser for the whole list at once: a search field, one checkbox per column, show/hide all, and **Reset layout**.
|
|
17
|
-
The chooser
|
|
17
|
+
The chooser comes in three forms:
|
|
18
|
+
|
|
19
|
+
- `TMDataGrid.Menu.Columns` in the [grid menu](/docs/menu).
|
|
20
|
+
- **Manage columns**, a submenu of every column menu with the same items.
|
|
21
|
+
- `TMDataGrid.ColumnsPanel`, plain controls for a host that is not a menu.
|
|
18
22
|
|
|
19
23
|
`enableHiding: false` removes all of it; on a column it removes that column's
|
|
20
24
|
menu item and leaves it out of the panel, which lists what can be hidden and
|
|
@@ -42,8 +46,8 @@ computed from an `fr` value. The grid writes the column's rendered width into
|
|
|
42
46
|
To start pinned, set `initialState.columnPinning`. The slice is TanStack's
|
|
43
47
|
full `ColumnPinningState`, so a partial does not compile - name both sides.
|
|
44
48
|
A column pinned at mount has no rendered width to store, so it takes its
|
|
45
|
-
`size
|
|
46
|
-
|
|
49
|
+
`size`, clamped between `minSize` and `maxSize`; with none of them set that is
|
|
50
|
+
TanStack's default of `150`. Give such a column an explicit `size`:
|
|
47
51
|
|
|
48
52
|
```tsx
|
|
49
53
|
columnHelper.accessor("registration", { header: "Registration", size: 220 });
|
|
@@ -114,10 +118,11 @@ Columns are fluid by default. Each track is `minmax(minSize, flex fr)`.
|
|
|
114
118
|
|
|
115
119
|
| Column option | Effect |
|
|
116
120
|
| --- | --- |
|
|
117
|
-
| `minSize` | Minimum width, and the column's contribution to the grid minimum width. Defaults to `80`. |
|
|
121
|
+
| `minSize` | Minimum width, and the column's contribution to the grid minimum width. Also the floor of a resize and of a pinned width. Defaults to `80`, through `defaultColumn.minSize`. |
|
|
122
|
+
| `maxSize` | Ceiling of a resize, an autosize and a pinned width. Does not cap a fluid track. No default. |
|
|
118
123
|
| `meta.flex` | Share of the remaining width. Defaults to `1`. |
|
|
119
124
|
| `minSize === maxSize` | Fixed width. The column is never fluid. |
|
|
120
|
-
| `size` |
|
|
125
|
+
| `size` | Width of a column pinned at mount, clamped by `minSize` and `maxSize`. Ignored on a fluid column; a resize or an interactive pin stores the rendered width instead. Defaults to `150`. |
|
|
121
126
|
|
|
122
127
|
Drag a divider to resize. A column switches to a fixed pixel width the moment
|
|
123
128
|
it is resized or pinned, and the width is stored in `columnSizing`. The drag
|
|
@@ -148,8 +153,8 @@ the column's cells.
|
|
|
148
153
|
|
|
149
154
|
## Reset the layout
|
|
150
155
|
|
|
151
|
-
`resetSettings()` from the hook clears visibility, order, pinning
|
|
152
|
-
one call. The columns panel offers it as **Reset layout**.
|
|
156
|
+
`resetSettings()` from the hook clears visibility, order, pinning, widths and
|
|
157
|
+
grouping in one call. The columns panel offers it as **Reset layout**.
|
|
153
158
|
|
|
154
159
|
```tsx
|
|
155
160
|
const { resetSettings } = useTMDataGrid({ data, columns });
|
|
@@ -169,7 +174,7 @@ const { resetSettings } = useTMDataGrid({ data, columns });
|
|
|
169
174
|
| `meta.flex` | Column meta | `number` | `1` | Share of the remaining width. |
|
|
170
175
|
| `meta.autoSize` | Column meta | `boolean` | `false` | Autosize once, on the render the column's first cells appear in. |
|
|
171
176
|
| `minSize` / `maxSize` / `size` | Column options | `number` | `80` / – / – | Width bounds, and the fixed width once one applies. |
|
|
172
|
-
| `resetSettings` | Hook return | `() => void` | – | Clears visibility, order, pinning and
|
|
177
|
+
| `resetSettings` | Hook return | `() => void` | – | Clears visibility, order, pinning, widths and grouping. |
|
|
173
178
|
| `moveColumn` | Export | `({ table, columnId, targetId, side }) => void` | – | Moves a column beside another. |
|
|
174
179
|
| `moveColumnByStep` | Export | `({ table, columnId, direction }) => void` | – | Moves it one place. |
|
|
175
180
|
| `MoveColumnArgs` · `ColumnStepArgs` | Types | – | – | What `moveColumn` takes, and what `moveColumnByStep` and `getStepTargetColumn` take. |
|
package/docs/columns.md
CHANGED
|
@@ -88,6 +88,7 @@ Its type is `TMDataGridColumnMeta`.
|
|
|
88
88
|
| `options` | `TMDataGridOptionsSource` | - | The choices of a `select` / `multiSelect` column. See [Options](#options). |
|
|
89
89
|
| `flex` | `number` | `1` | Share of the remaining width. |
|
|
90
90
|
| `align` | `"left" \| "right" \| "center"` | `"left"` | Alignment, applied to both header and cells. |
|
|
91
|
+
| `colSpan` | `number \| ({ row, table }) => number` | `1` | Columns the cell covers on a row, counting itself. See [Spanning columns](#spanning-columns). |
|
|
91
92
|
| `autoSize` | `boolean` | `false` | Fit the column to its content once, on the render its first cells appear in. |
|
|
92
93
|
| `enableOrdering` | `boolean` | `true` | `false` keeps the column where it is. |
|
|
93
94
|
| `filter` | `TMDataGridColumnFilterOptions` | - | How this column filters. See [meta.filter](#metafilter). |
|
|
@@ -185,6 +186,57 @@ A select column that declares no options still filters: the panel falls back to
|
|
|
185
186
|
the faceted values. Mantine's dropdowns are not virtualized, so use the function
|
|
186
187
|
form for very large sets.
|
|
187
188
|
|
|
189
|
+
## Spanning columns
|
|
190
|
+
|
|
191
|
+
`meta.colSpan` makes a column's cell cover the columns to its right.
|
|
192
|
+
A cell spanning `n` is one cell `n` columns wide, with one background and one
|
|
193
|
+
border, and the `n - 1` cells it covers do not render on that row.
|
|
194
|
+
A function decides per row:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
columnHelper.accessor("name", {
|
|
198
|
+
header: "Name",
|
|
199
|
+
meta: {
|
|
200
|
+
colSpan: ({ row }) => (row.getIsExpanded() ? Infinity : 1),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```demo
|
|
206
|
+
file: columns/SpanningCells.tsx
|
|
207
|
+
hint: Open a row with its chevron - its name takes the rest of the row, and the panel opens below.
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- The span is counted over the displayed columns. Hiding or moving a column
|
|
211
|
+
changes what a span covers, so `Infinity` - "to the last of my columns" - is
|
|
212
|
+
usually safer than a count.
|
|
213
|
+
- A span stops at the last of your columns. It never covers the checkbox,
|
|
214
|
+
tree, details or edit lane.
|
|
215
|
+
- A span stops at the edge of its pinned block.
|
|
216
|
+
- A column covered by a span ignores its own `colSpan` on that row.
|
|
217
|
+
- Group rows ignore `colSpan`, and so does a row open in
|
|
218
|
+
[row edit mode](/docs/editing), where every column holds an editor.
|
|
219
|
+
- Sorting, filtering, copy and export still see every column. Autosize leaves
|
|
220
|
+
a spanning cell out of its column's measurement, and a column whose every
|
|
221
|
+
mounted cell spans fits its header.
|
|
222
|
+
- Under [cell selection](/docs/cell-selection) the spanning cell is one cell:
|
|
223
|
+
the arrow keys step over it, and a move onto a column it covers lands on it.
|
|
224
|
+
It is addressed by its first column: a range holds it when it holds that
|
|
225
|
+
column, and a range over only the columns it covers leaves it out. Copy and
|
|
226
|
+
export take every column of the range either way. When the column the focus
|
|
227
|
+
is on becomes covered, the focus moves to the spanning cell.
|
|
228
|
+
- Under [cell editing](/docs/editing) a covered column has no cell to open,
|
|
229
|
+
and Tab from the spanning cell's editor moves past the columns it covers.
|
|
230
|
+
A row keeps its cells while an editor is open in a column a span would
|
|
231
|
+
cover, and the span lands when the edit ends.
|
|
232
|
+
|
|
233
|
+
A cell renderer that reads `row.getIsExpanded()`, as the demo's does, has to
|
|
234
|
+
subscribe to the store - see
|
|
235
|
+
[Opening a row from elsewhere](/docs/row-details#opening-a-row-from-elsewhere).
|
|
236
|
+
|
|
237
|
+
The cell carries `aria-colspan` and `data-col-span` with the number of columns
|
|
238
|
+
it covers.
|
|
239
|
+
|
|
188
240
|
## Header groups
|
|
189
241
|
|
|
190
242
|
`columnHelper.group` nests columns under a shared header. The group is a header
|
|
@@ -231,8 +283,9 @@ Sizing, pinning, ordering and visibility are covered on
|
|
|
231
283
|
## The generated columns
|
|
232
284
|
|
|
233
285
|
Five lanes the grid adds when a feature needs them. All are **system lanes**:
|
|
234
|
-
|
|
235
|
-
|
|
286
|
+
pinned, with no column menu, never exported and never listed in the columns
|
|
287
|
+
panel. Four of them are fixed width with no resize handle; the tree lane can be
|
|
288
|
+
resized, and it is shown only while a grouping is active.
|
|
236
289
|
|
|
237
290
|
| Id | Appears when | Where |
|
|
238
291
|
| --- | --- | --- |
|
|
@@ -259,6 +312,8 @@ column can be placed in front of it.
|
|
|
259
312
|
| `meta.options` | Column meta | array \| `"faceted"` \| `(args) => …` | – | The choices of an option column. |
|
|
260
313
|
| `TMDataGridOptionsArgs` | Type | – | – | What a `meta.options` function receives: `{ table, column, row? }`. `row` is absent when the filter panel asks. |
|
|
261
314
|
| `meta.align` | Column meta | `"left" \| "right" \| "center"` | `"left"` | Header and cell alignment. |
|
|
315
|
+
| `meta.colSpan` | Column meta | `number \| (args) => number` | `1` | Columns the cell covers on a row. See [Spanning columns](#spanning-columns). |
|
|
316
|
+
| `TMDataGridColSpanArgs` | Type | `{ row, table }` | – | What a `meta.colSpan` function receives. |
|
|
262
317
|
| `meta.filter` | Column meta | `TMDataGridColumnFilterOptions` | – | How the column filters: `defaultOperator`, `control`. |
|
|
263
318
|
| `meta.edit` | Column meta | `TMDataGridColumnEditOptions` | – | How the column edits: `enabled`, `field`, `editor`, `validate`, `mapValue`. |
|
|
264
319
|
| `resolveColumnOptions` | Export | `({ table, column, row? }) => options` | – | Normalises all three `meta.options` forms. |
|
package/docs/export.md
CHANGED
|
@@ -80,7 +80,7 @@ const grid = useTMDataGrid({
|
|
|
80
80
|
| `includeHeaders` | `boolean` | `true` | Determines whether the column labels are written as the first row. |
|
|
81
81
|
| `columns` | `"visible" \| "all" \| Array<string>` | `"visible"` | Which columns are written. See [What is written](#what-is-written). |
|
|
82
82
|
|
|
83
|
-
The option is read field by field, so
|
|
83
|
+
The option is read field by field, so an inline object literal needs no `useMemo`.
|
|
84
84
|
Every export of the grid reads it: the menu items, the hook, and the export item of the cell-range menu.
|
|
85
85
|
|
|
86
86
|
## Formats
|
|
@@ -290,7 +290,7 @@ See [Testing](/docs/testing).
|
|
|
290
290
|
|
|
291
291
|
| Name | Kind | Type | Default | What it does |
|
|
292
292
|
| --- | --- | --- | --- | --- |
|
|
293
|
-
| `exportOptions` | Option | `TMDataGridExportOptions` | `
|
|
293
|
+
| `exportOptions` | Option | `TMDataGridExportOptions` | `csvExcelFormat()`, `"export"`, headers on, `"visible"` columns | Format, file name and header row for every export of the grid. |
|
|
294
294
|
| `meta.enableExport` | Column meta | `boolean` | `true` | `false` leaves the column out of every export and of Ctrl+C. |
|
|
295
295
|
| `meta.exportValue` | Column meta | `({ value, row, column }) => unknown` | – | The value written in place of `row.getValue`. |
|
|
296
296
|
| `TMDataGridExportValueGetter` | Type | – | – | The type of `meta.exportValue`. |
|
package/docs/filtering.md
CHANGED
|
@@ -32,8 +32,8 @@ array only under `isAnyOf` / `isNoneOf` (the set the cell is tested against) and
|
|
|
32
32
|
|
|
33
33
|
A filter with an empty value stays in state, so a panel row survives while the
|
|
34
34
|
user types. (A header filter control has no row to keep alive and drops its
|
|
35
|
-
entry instead - see [inHeader](#inheader).)
|
|
36
|
-
indicator
|
|
35
|
+
entry instead - see [inHeader](#inheader).) The built-in matcher lets every row through, and
|
|
36
|
+
the filter does not set the header's filter indicator and produces no pill. `isFilterActive(value)` tests for that
|
|
37
37
|
state, and `activeColumnFilters` applies it across the whole slice, handing
|
|
38
38
|
back the entries that narrow the grid with their values typed as
|
|
39
39
|
`TMDataGridFilterValue` rather than as `unknown`. Entries in any other value
|
|
@@ -117,7 +117,7 @@ useTMDataGrid({ data, columns, filters: { surface: "sidebar", inHeader: true } }
|
|
|
117
117
|
`surface` and `inHeader` are two separate choices, not one list: `inHeader` composes with all three surfaces.
|
|
118
118
|
`{ surface: "none", inHeader: true }` is header filters and nothing else; `{ inHeader: true }` keeps the popup as well, for the multi-column work a header row has no room for.
|
|
119
119
|
|
|
120
|
-
The option is read field by field, so
|
|
120
|
+
The option is read field by field, so an inline object literal needs no `useMemo` - unlike `labels` and `persist`, it does not have to be referentially stable.
|
|
121
121
|
|
|
122
122
|
### surface: "popup"
|
|
123
123
|
|
|
@@ -132,7 +132,7 @@ Closing only hides the popup; the filters stay.
|
|
|
132
132
|
The same panel beside the rows, inside the grid frame and under the toolbar.
|
|
133
133
|
It is a column of the frame rather than a layer over it: the rows give up the width instead of being covered, a click in the table does not dismiss it, and clearing the filters leaves it standing with its **Add filter** button.
|
|
134
134
|
Escape closes it.
|
|
135
|
-
It starts open (`defaultOpen` defaults to `true` here) and renders the panel with `layout="stacked"`, because
|
|
135
|
+
It starts open (`defaultOpen` defaults to `true` here) and renders the panel with `layout="stacked"`, because the default 280px cannot fit the column, operator and value fields side by side.
|
|
136
136
|
|
|
137
137
|
```demo
|
|
138
138
|
file: columns/FilterSidebar.tsx
|
|
@@ -182,7 +182,7 @@ No props.
|
|
|
182
182
|
|
|
183
183
|
The panel of filter rows - one column / operator / value triple per filter, over an "Add filter" / "Clear all" footer.
|
|
184
184
|
It is a plain block with no title, no close button and no open state: it renders wherever it is mounted, and the popup and sidebar surfaces are wrappers around it.
|
|
185
|
-
It must be inside `<TMDataGrid>`, since it reads the grid from context, and it only reads and writes the table's `columnFilters` state, so
|
|
185
|
+
It must be inside `<TMDataGrid>`, since it reads the grid from context, and it only reads and writes the table's `columnFilters` state, so the same panel works unchanged under `manualFiltering`.
|
|
186
186
|
|
|
187
187
|
Pair it with `surface: "none"` so it is the only panel on the page, and drive it off `ui.state.filterPanelOpen` if it belongs behind a control of your own - `defaultOpen` sets where that state starts.
|
|
188
188
|
|
|
@@ -302,8 +302,8 @@ const SalaryFilter: TMDataGridFilterControlComponent = ({ layout, ...rest }) =>
|
|
|
302
302
|
layout === "header" ? <NumberInput {...} /> : <RangeSlider {...} />;
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
-
A control that ignores `layout` still works
|
|
306
|
-
|
|
305
|
+
A control that ignores `layout` still works and looks the same in every
|
|
306
|
+
surface, including a header cell, where the room is smallest. Every built-in
|
|
307
307
|
control honours it.
|
|
308
308
|
|
|
309
309
|
```demo
|
|
@@ -311,24 +311,111 @@ file: columns/CustomFilterControl.tsx
|
|
|
311
311
|
hint: Open the filter panel - Status offers chips, every other column the built-in control.
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
+
## Writing filters from your own interface
|
|
315
|
+
|
|
316
|
+
The grid's controls write plain TanStack state, so a control of your own does
|
|
317
|
+
the same. Write an envelope with `column.setFilterValue` or
|
|
318
|
+
`table.setColumnFilters`; the matcher, the pills, the header indicator and
|
|
319
|
+
`activeColumnFilters` all read it:
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
import { emptyValueForOperator, type TMDataGridFilterValue } from "@jielga/tmdatagrid";
|
|
323
|
+
|
|
324
|
+
const status = grid.table.getColumn("status")!;
|
|
325
|
+
|
|
326
|
+
// A set filter, as the panel would write it.
|
|
327
|
+
status.setFilterValue({
|
|
328
|
+
operator: "isAnyOf",
|
|
329
|
+
value: ["active", "paused"],
|
|
330
|
+
} satisfies TMDataGridFilterValue);
|
|
331
|
+
|
|
332
|
+
// An empty envelope stays in state and matches every row.
|
|
333
|
+
status.setFilterValue({ operator: "isAnyOf", value: emptyValueForOperator("isAnyOf") });
|
|
334
|
+
|
|
335
|
+
// `undefined` removes the entry.
|
|
336
|
+
status.setFilterValue(undefined);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`getColumnOperators(column)` returns the operators a column offers and
|
|
340
|
+
`getColumnDefaultOperator(column)` the one a fresh filter opens on;
|
|
341
|
+
`emptyValueForOperator(operator)` returns the empty value of the right shape.
|
|
342
|
+
|
|
343
|
+
To keep the grid's panel and place it yourself, set `filters: { surface: "none" }`
|
|
344
|
+
and render [TMDataGrid.FilterPanel](#tmdatagridfilterpanel) where it belongs.
|
|
345
|
+
To keep filtering on and drop only the column menu's Filter item, pass
|
|
346
|
+
`renderColumnMenuItems` to `TMDataGrid.Table` and leave out the item keyed
|
|
347
|
+
`"filter"` (see [Column menu](/docs/column-menu)). To replace every filter
|
|
348
|
+
control, set `enableColumnFilters: false` and write the envelope yourself;
|
|
349
|
+
filters in state still apply.
|
|
350
|
+
|
|
314
351
|
## Custom matching
|
|
315
352
|
|
|
316
353
|
To give a column its own matching logic instead of its own control, set
|
|
317
|
-
`filterFn` on the column definition.
|
|
318
|
-
|
|
354
|
+
`filterFn` on the column definition. A column `filterFn` replaces the shared
|
|
355
|
+
matcher for that column, so its third argument is the stored
|
|
356
|
+
`{ operator, value }` envelope, not the unwrapped value. Type it as
|
|
357
|
+
`TMDataGridFilterFn<TData>` and delegate to `tmDataGridFilterFn` for the
|
|
358
|
+
operators it does not handle itself. A column `filterFn` is also called with
|
|
359
|
+
half-typed envelopes, which the built-in matcher treats as matching every row,
|
|
360
|
+
so return `true` for `!isFilterActive(filter)` first:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import {
|
|
364
|
+
isFilterActive,
|
|
365
|
+
tmDataGridFilterFn,
|
|
366
|
+
type TMDataGridFilterFn,
|
|
367
|
+
} from "@jielga/tmdatagrid";
|
|
368
|
+
|
|
369
|
+
// Reads "contains" as "has this tag", so "ops" matches the tag "ops" and not
|
|
370
|
+
// "devops"; every other operator keeps the built-in matching.
|
|
371
|
+
const matchesTags: TMDataGridFilterFn<Employee> = (row, columnId, filter) => {
|
|
372
|
+
if (!isFilterActive(filter)) return true;
|
|
373
|
+
if (filter.operator === "contains") {
|
|
374
|
+
const wanted = String(filter.value).toLowerCase();
|
|
375
|
+
return row.original.tags.some((tag) => tag.toLowerCase() === wanted);
|
|
376
|
+
}
|
|
377
|
+
return tmDataGridFilterFn(row, columnId, filter);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
columnHelper.accessor((row) => row.tags.join(", "), {
|
|
381
|
+
id: "tags",
|
|
382
|
+
header: "Tags",
|
|
383
|
+
filterFn: matchesTags,
|
|
384
|
+
meta: { type: "string" },
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`filter.value` is a string under the scalar operators, a `ReadonlyArray<string>`
|
|
389
|
+
under `isAnyOf` / `isNoneOf` and a `[min, max]` pair under `between`;
|
|
390
|
+
`filterValueShape(operator)` tells which. `matchesFilter(cellValue, filter)`
|
|
391
|
+
applies the built-in matching to a value derived by the function itself.
|
|
392
|
+
|
|
393
|
+
The column's filter controls do not change: the operators still come from
|
|
394
|
+
`meta.type` and `meta.filter.operators`, and the panel, the pills and the header
|
|
395
|
+
indicator keep reading the envelope. A filter function named by string also
|
|
396
|
+
receives the envelope and does not unwrap it, so TanStack's built-ins such as
|
|
397
|
+
`"includesString"` do not work as a column `filterFn`. Write custom matching as
|
|
398
|
+
a function.
|
|
319
399
|
|
|
320
400
|
## Turning it off
|
|
321
401
|
|
|
322
|
-
`enableColumnFilters: false` removes the
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
402
|
+
`enableColumnFilters: false` removes the Filter item from the column menu, the
|
|
403
|
+
`FilterButton`, the header filter row and the panel's column list, and disables
|
|
404
|
+
the panel's "Add filter" button. It does not clear `columnFilters`: an entry already in
|
|
405
|
+
state, or one written with `setFilterValue`, still narrows the rows, still shows
|
|
406
|
+
as a pill and still sets the header indicator. Call `table.resetColumnFilters()`
|
|
407
|
+
when switching the interface off. Under `filters.surface: "sidebar"`, set
|
|
408
|
+
`surface: "none"` as well, as the sidebar does not follow the switch.
|
|
409
|
+
|
|
410
|
+
`enableColumnFilter: false` on a column removes that column's menu item, its
|
|
411
|
+
entry in the panel's column list and its header control - the header cell
|
|
412
|
+
stays, empty.
|
|
326
413
|
|
|
327
414
|
## Reference
|
|
328
415
|
|
|
329
416
|
| Name | Kind | Type | Default | What it does |
|
|
330
417
|
| --- | --- | --- | --- | --- |
|
|
331
|
-
| `enableColumnFilters` | Table option | `boolean` | `true` | `false` removes the
|
|
418
|
+
| `enableColumnFilters` | Table option | `boolean` | `true` | `false` removes the menu item, the button, the header row and the panel's column list, and disables the panel's "Add filter" button. Filters already in state still apply. |
|
|
332
419
|
| `filters.surface` | Table option | `"popup" \| "sidebar" \| "none"` | `"popup"` | Which surface the table renders. |
|
|
333
420
|
| `filters.sidebarSide` | Table option | `"left" \| "right"` | `"right"` | Which side the sidebar sits on. |
|
|
334
421
|
| `filters.sidebarWidth` | Table option | `string` | `"280px"` | Width of the sidebar. |
|
|
@@ -339,7 +426,10 @@ header control - the header cell stays, empty.
|
|
|
339
426
|
| `meta.filter.operators` | Column meta | `readonly TMDataGridFilterOperator[]` | The type's list | The operators this column offers, a subset of its type's. |
|
|
340
427
|
| `meta.filter.defaultOperator` | Column meta | `TMDataGridFilterOperator` | The type's default, else the first offered | The operator a fresh filter opens on. |
|
|
341
428
|
| `meta.filter.control` | Column meta | `TMDataGridFilterControlComponent` | By type and operator | Replaces the value control. |
|
|
342
|
-
| `filterFn` | Column option |
|
|
429
|
+
| `filterFn` | Column option | `TMDataGridFilterFn<TData>` \| name | `"tmDataGrid"` | Custom matching for one column. Receives the `{ operator, value }` envelope. |
|
|
430
|
+
| `TMDataGridFilterFn<TData>` | Type | `(row, columnId, filter: TMDataGridFilterValue, addMeta?) => boolean` | – | The signature of a column's own `filterFn`. |
|
|
431
|
+
| `tmDataGridFilterFn` | Export | `(row, columnId, filterValue: unknown) => boolean`, assignable to `TMDataGridFilterFn<TData>` | – | The shared matcher, for a custom `filterFn` to delegate to. |
|
|
432
|
+
| `matchesFilter` | Export | `(cellValue, filter: TMDataGridFilterValue) => boolean` | – | The built-in matching applied to one value. |
|
|
343
433
|
| `TMDataGrid.FilterPanel` | Component | `layout: "row" \| "stacked"` | `"row"` | The panel of filter rows, as a plain block. |
|
|
344
434
|
| `TMDataGrid.FilterButton` | Component | – | – | Toolbar button toggling the surface, with an active count. Seeds a filter row only when the panel is empty. |
|
|
345
435
|
| `TMDataGrid.FilterPills` | Component | takes `api` | – | Active filters as removable pills, renderable anywhere. |
|
package/docs/getting-started.md
CHANGED
|
@@ -112,6 +112,46 @@ file: getting-started/ToolbarAndFooter.tsx
|
|
|
112
112
|
[Grid anatomy](/docs/anatomy) for what each part is, and
|
|
113
113
|
[Toolbar](/docs/toolbar) for adding your own buttons among them.
|
|
114
114
|
|
|
115
|
+
## Coming from TanStack Table
|
|
116
|
+
|
|
117
|
+
`useTMDataGrid` takes TanStack's `TableOptions`, so a page built on
|
|
118
|
+
`useReactTable` keeps its `data`, `columns`, `state` and `onXChange` callbacks.
|
|
119
|
+
Go through these differences:
|
|
120
|
+
|
|
121
|
+
- **`getRowId`** - return the entity id. The index default points at another
|
|
122
|
+
record after a sort or a refetch, which breaks selection, expansion and
|
|
123
|
+
editing. Required once `editing` is set.
|
|
124
|
+
- **Stable `data` and `columns`** - define `columns` outside the component and
|
|
125
|
+
keep `data` referentially stable; a fresh `[]` for the empty case belongs in
|
|
126
|
+
a module-scope constant. Under `editing`, a transient `[]` drops every open
|
|
127
|
+
draft.
|
|
128
|
+
- **Sizing** - columns are fluid, `minmax(minSize, flex fr)`. `size` applies
|
|
129
|
+
only to a column pinned at mount, `maxSize` only to resizing and autosizing,
|
|
130
|
+
and `minSize` defaults to `80`. See
|
|
131
|
+
[Visibility, pinning, ordering and size](/docs/column-layout#sizing).
|
|
132
|
+
- **Defaults the grid changes** - `aggregationFn` is cleared, so grouping does
|
|
133
|
+
not sum anything on its own; `groupedColumnMode` is `"remove"`;
|
|
134
|
+
`autoResetExpanded` and `autoResetPageIndex` are off; `globalFilterFn` is the
|
|
135
|
+
fuzzy matcher. The full list is on
|
|
136
|
+
[useTMDataGrid](/docs/use-tm-data-grid#options).
|
|
137
|
+
- **Filters are envelopes** - `columnFilters` holds `{ operator, value }`, and
|
|
138
|
+
a column's own `filterFn` receives that envelope. See
|
|
139
|
+
[Filtering](/docs/filtering#custom-matching).
|
|
140
|
+
- **Interface the page already owns** - `enableGlobalFilter: false`, or no
|
|
141
|
+
`TMDataGrid.Search`, for the search; `enableColumnFilters: false`, or
|
|
142
|
+
`filters: { surface: "none" }` to keep filtering without the grid's panel.
|
|
143
|
+
See [Filtering](/docs/filtering#turning-it-off).
|
|
144
|
+
- **Page reset** - `resetPageOnQueryChange` (default `true`) returns to the
|
|
145
|
+
first page when a filter, the search, the sorting or the grouping changes
|
|
146
|
+
through the table. A data source re-filtered outside table state does not
|
|
147
|
+
trigger it; call `table.setPageIndex(0)` yourself, or use `manualFiltering`.
|
|
148
|
+
See [Server-side data](/docs/server-side).
|
|
149
|
+
- **State reads** - there is no `table.getState()`; read slices with
|
|
150
|
+
`useSelector(table.store, ...)`. See
|
|
151
|
+
[useTMDataGrid](/docs/use-tm-data-grid#reading-state).
|
|
152
|
+
- **Tests** - the grid publishes `data-dg-part`, `data-row-id` and
|
|
153
|
+
`data-column-id`, and no `data-testid`. See [Testing](/docs/testing).
|
|
154
|
+
|
|
115
155
|
## Where to go next
|
|
116
156
|
|
|
117
157
|
- **[Defining columns](/docs/columns)** - accessors, `meta.type`, and what each
|
package/docs/grouping.md
CHANGED
|
@@ -100,8 +100,8 @@ records inside collapsed sub-groups. It shows a tick once all of them are
|
|
|
100
100
|
selected and a dash while only some are.
|
|
101
101
|
|
|
102
102
|
Only the records are written to `rowSelection`. A group row is never in it, so
|
|
103
|
-
`getSelectedRowModel()` and
|
|
104
|
-
arranged.
|
|
103
|
+
`getSelectedRowModel()` and `onRowSelectionChange` do not depend on how the tree
|
|
104
|
+
is arranged.
|
|
105
105
|
|
|
106
106
|
Under `enableMultiRowSelection: false` group rows carry no checkbox.
|
|
107
107
|
|
|
@@ -125,8 +125,11 @@ While a column is grouped the grid renders the whole tree and relies on
|
|
|
125
125
|
virtualization. `TMDataGrid.Footer` greys its pager out and replaces the range
|
|
126
126
|
with `Grouped · all N rows`. Ungroup and paging resumes where it left off.
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
There is no option to keep the built-in pager active while grouped, and
|
|
129
|
+
`manualGrouping` does not change that: the pager greys out whenever `grouping`
|
|
130
|
+
is non-empty. To page a grouped grid, group and page on the server with
|
|
131
|
+
`manualPagination` and `manualGrouping`, and render the pager through
|
|
132
|
+
`renderPagination`, ignoring `isPagingActive`.
|
|
130
133
|
|
|
131
134
|
`isPagingActive(table, features)` is exported, so a custom pager can grey itself
|
|
132
135
|
out the same way:
|
|
@@ -150,12 +153,12 @@ server-side can set `enableGrouping: true` alongside `manualGrouping: true`. See
|
|
|
150
153
|
|
|
151
154
|
| Name | Kind | Type | Default | What it does |
|
|
152
155
|
| --- | --- | --- | --- | --- |
|
|
153
|
-
| `enableGrouping` | Table option | `boolean` | `true` | Group by and Ungroup menu items. Also a column option. |
|
|
156
|
+
| `enableGrouping` | Table option | `boolean` | `true`; `false` under `manualPagination` | Group by and Ungroup menu items, and the tree lane. Also a column option. |
|
|
154
157
|
| `groupedColumnMode` | Table option | `"reorder" \| "remove" \| false` | `"remove"` | Whether a grouped column leaves the grid or moves to the front. |
|
|
155
158
|
| `manualGrouping` | Table option | `boolean` | `false` | The rows arrive grouped. Required to group a server-paged grid. |
|
|
156
159
|
| `initialState.grouping` | Table option | `string[]` | `[]` | Column ids to group on at mount. A settings slice, so it persists. |
|
|
157
160
|
| `aggregationFn` | Column option | `TMDataGridAggregationName \| fn` | – | How a column fills in its group cells. Unset leaves them blank. |
|
|
158
|
-
| `aggregatedCell` | Column option | `(ctx) => ReactNode` |
|
|
161
|
+
| `aggregatedCell` | Column option | `(ctx) => ReactNode` | `cell` over the aggregate when `aggregationFn` is set, else blank | Renders a group row's value differently from a data row's. |
|
|
159
162
|
| `GROUP_COLUMN_ID` | Export | `"__group__"` | – | Id of the generated tree column. |
|
|
160
163
|
| `formatGroupValue` | Export | `(value) => string` | – | How the tree lane renders a group's value. |
|
|
161
164
|
| `getGroupDataRows` | Export | `(row) => Row[]` | – | Every record under a group row, at any depth. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Loading and empty states
|
|
2
2
|
|
|
3
|
-
A grid can have nothing to show for
|
|
3
|
+
A grid can have nothing to show for five different reasons, and the body says
|
|
4
4
|
which one it is: still fetching, emptied by a filter, or holding no data.
|
|
5
5
|
|
|
6
6
|
```demo
|
|
@@ -18,9 +18,29 @@ An empty body shows exactly one thing, decided in this order:
|
|
|
18
18
|
2. **Entry rows** - an open entry row from `edit.addRow()`: the entry block
|
|
19
19
|
only, with no message beside the form.
|
|
20
20
|
3. **`renderEmptyState`** - your node, centred where the message would be.
|
|
21
|
-
4. **Filtered-empty** - a filter
|
|
22
|
-
`labels.noResults` ("No rows
|
|
23
|
-
|
|
21
|
+
4. **Filtered-empty** - a column filter is active (`isFilterActive`) or the
|
|
22
|
+
quick search is not blank: a search icon and `labels.noResults` ("No rows
|
|
23
|
+
match your filters").
|
|
24
|
+
5. **Truly-empty** - no column filter is active and the quick search is blank:
|
|
25
|
+
`labels.noRows` ("No rows to show").
|
|
26
|
+
|
|
27
|
+
"Empty" means no displayed row and no pinned row: the current page under
|
|
28
|
+
pagination, otherwise every filtered row. States 4 and 5 are decided by the
|
|
29
|
+
filter state alone, so an empty `data` array shows `noResults` while a filter is
|
|
30
|
+
active. A half-typed filter, such as `{ operator: "contains", value: "" }`, is
|
|
31
|
+
not active, so it does not count.
|
|
32
|
+
|
|
33
|
+
## While data loads
|
|
34
|
+
|
|
35
|
+
The loader shows only while `meta.loading` is `true` and the view is empty. It
|
|
36
|
+
is the only "no data yet" signal the grid has, so set `meta.loading` during the
|
|
37
|
+
first fetch as well; without it the empty message shows until the rows arrive.
|
|
38
|
+
|
|
39
|
+
`renderEmptyState` is called only once `meta.loading` is not `true`. During a
|
|
40
|
+
refetch that returns no rows, the loader mounts when `loading` turns `true`,
|
|
41
|
+
`renderEmptyState` mounts when it turns `false`, and it unmounts again on the
|
|
42
|
+
next refetch; the two branches never render together. Rows on screen stay on
|
|
43
|
+
screen until `data` changes, whatever `loading` says.
|
|
24
44
|
|
|
25
45
|
## Replacing the message
|
|
26
46
|
|
|
@@ -70,8 +90,8 @@ typically after `Spacer`.
|
|
|
70
90
|
|
|
71
91
|
`TMDataGrid.SummaryCount` shows visible rows out of the total. The total is
|
|
72
92
|
`meta.totalRowCount` when you provide it, and the pre-filtered row count
|
|
73
|
-
otherwise. A [server-side](/docs/server-side) grid
|
|
74
|
-
|
|
93
|
+
otherwise. A [server-side](/docs/server-side) grid holds one page, so without
|
|
94
|
+
`meta.totalRowCount` it shows the shown count alone, with no total.
|
|
75
95
|
|
|
76
96
|
```tsx
|
|
77
97
|
<TMDataGrid.SummaryCount>
|
|
@@ -83,10 +103,10 @@ client only holds one page.
|
|
|
83
103
|
|
|
84
104
|
| Name | Kind | Type | Default | What it does |
|
|
85
105
|
| --- | --- | --- | --- | --- |
|
|
86
|
-
| `meta.loading` | Option | `boolean` | `false` | A fetch is in flight.
|
|
106
|
+
| `meta.loading` | Option | `boolean` | `false` | A fetch is in flight. While `true` and the view is empty, the loader replaces every empty message and `renderEmptyState` is not called. |
|
|
87
107
|
| `meta.noResultsLabel` | Option | `string` | `labels.noResults` | The filtered-empty message, without a render prop. |
|
|
88
|
-
| `meta.totalRowCount` | Option | `number` | Pre-filtered count | The total `SummaryCount` reports. |
|
|
89
|
-
| `renderEmptyState` | Table prop | `({ hasActiveFilters, table }) => ReactNode` | – | Replaces both built-in empty messages. |
|
|
108
|
+
| `meta.totalRowCount` | Option | `number` | Pre-filtered count; none on a server-driven grid | The total `SummaryCount` reports. |
|
|
109
|
+
| `renderEmptyState` | Table prop | `({ hasActiveFilters, table }) => ReactNode` | – | Replaces both built-in empty messages. `hasActiveFilters` is `true` when a column filter is active (`isFilterActive`) or the quick search is not blank. |
|
|
90
110
|
| `TMDataGrid.LoadingIndicator` | Component | – | – | Spinner while `meta.loading`, for when the body has rows. |
|
|
91
111
|
| `TMDataGrid.SummaryCount` | Component | `children` replaces the text | – | Visible rows out of total. |
|
|
92
112
|
| `labels.noRows` · `labels.noResults` | Labels | `string` | English defaults | The two built-in messages. See [Localization](/docs/localization). |
|