@jielga/tmdatagrid 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +322 -0
- package/skills/cell-selection/SKILL.md +240 -0
- package/skills/columns/SKILL.md +260 -85
- package/skills/data/SKILL.md +289 -0
- package/skills/editing/SKILL.md +492 -0
- package/skills/editing/references/editing-api.md +124 -0
- package/skills/editing/references/editors-and-validation.md +198 -0
- package/skills/filtering/SKILL.md +344 -0
- package/skills/getting-started/SKILL.md +33 -12
- package/skills/grouping/SKILL.md +264 -0
- package/skills/options/SKILL.md +24 -13
- package/skills/rows/SKILL.md +369 -0
- package/skills/rows/references/rows-api.md +117 -0
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/skills/features/SKILL.md +0 -352
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Editors and validation
|
|
2
|
+
|
|
3
|
+
Reference for the `editing` skill. Which control a cell opens, and what stops a
|
|
4
|
+
bad value being committed. Both follow from the column.
|
|
5
|
+
|
|
6
|
+
## The built-in editors
|
|
7
|
+
|
|
8
|
+
`meta.type` picks one, `meta.options` feeds the two select editors from the same
|
|
9
|
+
declaration the filter panel reads.
|
|
10
|
+
|
|
11
|
+
| `meta.type` | Editor | Export |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `"string"` (default) | Text input | `TMDataGridStringEditor` |
|
|
14
|
+
| `"number"` | Number input | `TMDataGridNumberEditor` |
|
|
15
|
+
| `"boolean"` | Checkbox | `TMDataGridBooleanEditor` |
|
|
16
|
+
| `"date"` | Native `<input type="date">`, ISO `YYYY-MM-DD` | `TMDataGridDateEditor` |
|
|
17
|
+
| `"select"` | Searchable select, commits on pick under `"cell"` | `TMDataGridSelectEditor` |
|
|
18
|
+
| `"multiSelect"` | Multi-select, same source | `TMDataGridMultiSelectEditor` |
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
columnHelper.accessor("department", {
|
|
22
|
+
header: "Department",
|
|
23
|
+
meta: { type: "select", options: ["Engineering", "Sales", "Support"] },
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`meta.options` takes a static array, `"faceted"` (the distinct values present in
|
|
28
|
+
the data), or a function of the table, column and row.
|
|
29
|
+
|
|
30
|
+
## The editor contract
|
|
31
|
+
|
|
32
|
+
`meta.editor` fills the same slot the built-ins fill. It is a **component**,
|
|
33
|
+
rendered as JSX, so hooks are legal inside it.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
type TMDataGridEditorArgs = {
|
|
37
|
+
field: TMDataGridEditField; // the live TanStack Form field
|
|
38
|
+
form: TMDataGridRowEditForm; // the whole row's form, for sibling fields
|
|
39
|
+
cell: Cell;
|
|
40
|
+
row: Row;
|
|
41
|
+
column: Column;
|
|
42
|
+
table: TMDataGridTable;
|
|
43
|
+
commit: () => Promise<boolean>; // what Enter would do
|
|
44
|
+
cancel: () => void; // what Escape would do
|
|
45
|
+
size: TMDataGridSize;
|
|
46
|
+
autoFocus: boolean; // take focus on mount
|
|
47
|
+
seedText?: string; // set when typing opened the editor
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Bind any control to `field` exactly as inside any TanStack Form:
|
|
52
|
+
`field.state.value`, `field.state.meta.errors`, `field.handleChange`,
|
|
53
|
+
`field.handleBlur`.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { Slider } from "@mantine/core";
|
|
57
|
+
import type { TMDataGridEditorComponent } from "@jielga/tmdatagrid";
|
|
58
|
+
|
|
59
|
+
// Module scope. A new identity per render remounts the editor mid-edit.
|
|
60
|
+
const SalaryEditor: TMDataGridEditorComponent = ({ field, commit, cancel }) => (
|
|
61
|
+
<Slider
|
|
62
|
+
value={Number(field.state.value ?? 0)}
|
|
63
|
+
min={0}
|
|
64
|
+
max={100_000}
|
|
65
|
+
step={1_000}
|
|
66
|
+
onChange={(next) => field.handleChange(next)}
|
|
67
|
+
onChangeEnd={() => void commit()}
|
|
68
|
+
onKeyDown={(event) => {
|
|
69
|
+
if (event.key === "Escape") cancel();
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
columnHelper.accessor("salary", {
|
|
75
|
+
header: "Salary",
|
|
76
|
+
meta: { type: "number", editor: SalaryEditor },
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Wrapping a built-in
|
|
81
|
+
|
|
82
|
+
The built-ins take `TMDataGridEditorArgs` as their props, so a custom editor can
|
|
83
|
+
pass the whole object through and add around it rather than start over.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { Group, Text } from "@mantine/core";
|
|
87
|
+
import {
|
|
88
|
+
TMDataGridNumberEditor,
|
|
89
|
+
TMDataGridSelectEditor,
|
|
90
|
+
TMDataGridStringEditor,
|
|
91
|
+
type TMDataGridEditorComponent,
|
|
92
|
+
} from "@jielga/tmdatagrid";
|
|
93
|
+
|
|
94
|
+
// Decorate one.
|
|
95
|
+
const SalaryEditor: TMDataGridEditorComponent = (args) => (
|
|
96
|
+
<Group gap={4} wrap="nowrap" w="100%">
|
|
97
|
+
<TMDataGridNumberEditor {...args} />
|
|
98
|
+
<Text size="xs" c="dimmed">
|
|
99
|
+
kr
|
|
100
|
+
</Text>
|
|
101
|
+
</Group>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Or choose between them per row.
|
|
105
|
+
const DepartmentEditor: TMDataGridEditorComponent = (args) =>
|
|
106
|
+
args.row.original.status === "Terminated" ? (
|
|
107
|
+
<TMDataGridStringEditor {...args} />
|
|
108
|
+
) : (
|
|
109
|
+
<TMDataGridSelectEditor {...args} />
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Pass `args` through whole. `field` is a live TanStack Form `FieldApi`, not a
|
|
114
|
+
plain object, so spreading or rebuilding it loses the binding its methods rely
|
|
115
|
+
on.
|
|
116
|
+
|
|
117
|
+
## Field validation
|
|
118
|
+
|
|
119
|
+
`meta.validate` takes TanStack Form's field-validator vocabulary. A bare schema
|
|
120
|
+
(Zod, or any Standard Schema, or a plain function) means `{ onChange: it }`; the
|
|
121
|
+
object form takes every trigger Form defines.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { z } from "zod";
|
|
125
|
+
|
|
126
|
+
// Bare schema: validates on change.
|
|
127
|
+
meta: { validate: z.string().min(2, "At least two characters") }
|
|
128
|
+
|
|
129
|
+
// Object form: pick the trigger.
|
|
130
|
+
meta: { validate: { onBlur: z.string().email("Not an email address") } }
|
|
131
|
+
|
|
132
|
+
// A plain function works too.
|
|
133
|
+
meta: { validate: ({ value }) => (value > 0 ? undefined : "Must be positive") }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`normalizeFieldValidate(validate)` is exported for consumers building their own
|
|
137
|
+
column factories: it turns a bare schema into Form's validator shape.
|
|
138
|
+
|
|
139
|
+
## Row validation
|
|
140
|
+
|
|
141
|
+
`rowValidators` is form-level, and where cross-field rules live.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
const grid = useTMDataGrid({
|
|
145
|
+
data: employees,
|
|
146
|
+
columns,
|
|
147
|
+
getRowId: (row) => String(row.id),
|
|
148
|
+
editMode: "row",
|
|
149
|
+
rowValidators: {
|
|
150
|
+
onSubmit: z
|
|
151
|
+
.object({ salary: z.number().positive(), status: z.string() })
|
|
152
|
+
.refine((row) => row.status !== "Terminated" || row.salary === 0, {
|
|
153
|
+
message: "A terminated employee has no salary",
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
onEditCommit,
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Issues with a path land on the matching column's cell; pathless issues land on
|
|
161
|
+
the row. A nested schema's issues follow the same rule, so a `address.city`
|
|
162
|
+
issue lands on the column whose `editField` is `"address.city"`.
|
|
163
|
+
|
|
164
|
+
Cross-field rules need a mode that commits the whole row at once. Under `"cell"`
|
|
165
|
+
each cell commits alone, so the rule is evaluated against the other column's
|
|
166
|
+
unedited value and can never pass. Use `"row"` or `"batch"`.
|
|
167
|
+
|
|
168
|
+
## Server-side errors
|
|
169
|
+
|
|
170
|
+
`rowValidators.onSubmitAsync` returns TanStack Form's `{ form, fields }` shape,
|
|
171
|
+
so an API's field errors land on the right cells without translation.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
rowValidators: {
|
|
175
|
+
onSubmitAsync: async ({ value }) => {
|
|
176
|
+
const response = await api.validateEmployee(value);
|
|
177
|
+
if (response.ok) return undefined;
|
|
178
|
+
return {
|
|
179
|
+
form: response.message,
|
|
180
|
+
fields: response.fieldErrors, // { salary: "Above the band ceiling" }
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A commit blocked by validation keeps the editor open with the message on the
|
|
187
|
+
input. A rejected `onEditCommit` keeps the draft too, with the error on the row.
|
|
188
|
+
|
|
189
|
+
## Where the state shows
|
|
190
|
+
|
|
191
|
+
| Marker | Means |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| Blue cell corner | The field is dirty against its original value |
|
|
194
|
+
| Red cell corner | The field carries a validation error |
|
|
195
|
+
| Row error text | A pathless rule failed, or a commit was rejected |
|
|
196
|
+
|
|
197
|
+
The same information is readable from `edit.store`: `rows[rowId].dirtyFields`,
|
|
198
|
+
`rows[rowId].errorFields`, `rows[rowId].isSubmitting`.
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: filtering
|
|
3
|
+
description: >
|
|
4
|
+
Narrow the rows a TMDataGrid shows. Covers the shared tmDataGrid filter
|
|
5
|
+
function and its {operator, value} model, the eighteen operators and which
|
|
6
|
+
meta.type offers each, meta.defaultFilterOperator, isFilterActive and the
|
|
7
|
+
half-typed filter, the filter panel, TMDataGrid.FilterButton,
|
|
8
|
+
TMDataGrid.FilterPills with its api prop, openColumnFilter, replacing a value
|
|
9
|
+
control with DgRangeSliderFilter / DgDateRangeFilter / DgAutocompleteFilter /
|
|
10
|
+
DgTriStateFilter or a meta.filterControl component, per-column filterFn, and
|
|
11
|
+
the quick search: TMDataGrid.Search, quickSearchMode fuzzy or contains,
|
|
12
|
+
fuzzyGlobalFilterFn, enableMatchHighlighting and enableGlobalFilter. Load when
|
|
13
|
+
adding filters, choosing operators, building a filter control, showing active
|
|
14
|
+
filters outside the grid, or wiring a search box.
|
|
15
|
+
metadata:
|
|
16
|
+
type: core
|
|
17
|
+
library: '@jielga/tmdatagrid'
|
|
18
|
+
library_version: '1.0.2'
|
|
19
|
+
sources:
|
|
20
|
+
- 'Jielga/TMDataGrid:src/docs/filtering.md'
|
|
21
|
+
- 'Jielga/TMDataGrid:src/docs/quick-search.md'
|
|
22
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/filterOperators.ts'
|
|
23
|
+
- 'Jielga/TMDataGrid:src/tmdatagrid/core/quickSearch.ts'
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# TMDataGrid - Filtering
|
|
27
|
+
|
|
28
|
+
Two independent narrowings: per-column filters built from an operator and a
|
|
29
|
+
value, and one quick-search box across every column. Both write standard
|
|
30
|
+
TanStack state, so a `manualFiltering` grid forwards either to a server.
|
|
31
|
+
|
|
32
|
+
## How a filter is stored
|
|
33
|
+
|
|
34
|
+
All columns share one filter function. The operator lives **in the value**
|
|
35
|
+
rather than being selected through `filterFn`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
type TMDataGridFilterValue = {
|
|
39
|
+
operator: TMDataGridFilterOperator;
|
|
40
|
+
value: string | ReadonlyArray<string>;
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The model is therefore plain JSON: dates as ISO `YYYY-MM-DD`, booleans as
|
|
45
|
+
`"true"` / `"false"`, and an array only under `isAnyOf` / `isNoneOf` (the set the
|
|
46
|
+
cell is tested against) and `between` (a `[min, max]` pair, an empty string
|
|
47
|
+
leaving that end open).
|
|
48
|
+
|
|
49
|
+
A filter with an empty value **stays in state** so the panel keeps showing its
|
|
50
|
+
row while the reader types. It matches every row, does not light the header
|
|
51
|
+
indicator and gets no pill. `isFilterActive(value)` is the test for that state -
|
|
52
|
+
presence in `columnFilters` is not.
|
|
53
|
+
|
|
54
|
+
## Operators
|
|
55
|
+
|
|
56
|
+
`meta.type` selects which operators a column offers.
|
|
57
|
+
|
|
58
|
+
| Column type | Operators |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `string` | `contains`, `equals`, `notEquals`, `startsWith`, `endsWith` |
|
|
61
|
+
| `number` | `equals`, `notEquals`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `between` |
|
|
62
|
+
| `date` | `equals`, `notEquals`, `before`, `after`, `onOrBefore`, `onOrAfter`, `between` |
|
|
63
|
+
| `boolean` | `equals`, `notEquals` |
|
|
64
|
+
| `select`, `multiSelect` | `isAnyOf`, `isNoneOf` |
|
|
65
|
+
|
|
66
|
+
Every type also offers `isEmpty` and `isNotEmpty`, which ignore the value input.
|
|
67
|
+
|
|
68
|
+
String comparisons are case-insensitive. Date comparisons are by calendar day,
|
|
69
|
+
and `equals` on a `date` column plays the role of "is". On a `multiSelect`
|
|
70
|
+
column, whose cells hold arrays, `isAnyOf` is an intersection test and `isNoneOf`
|
|
71
|
+
its complement. `between` is inclusive at both ends.
|
|
72
|
+
|
|
73
|
+
`meta.defaultFilterOperator` sets which one a fresh filter opens on, and must be
|
|
74
|
+
one of the type's own:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
columnHelper.accessor("salary", {
|
|
78
|
+
header: "Salary",
|
|
79
|
+
meta: { type: "number", defaultFilterOperator: "between" },
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## The panel and the pills
|
|
84
|
+
|
|
85
|
+
`TMDataGrid.FilterPanel` is column, operator and value rows under a "Filters"
|
|
86
|
+
header, above "Add filter" and "Clear all". It is rendered by
|
|
87
|
+
`TMDataGrid.Table`; Escape and a click outside close it, with `FilterButton`
|
|
88
|
+
exempt from the click-away so it stays a toggle. Closing only hides it - the
|
|
89
|
+
filters stay. **Clear all** drops every filter, half-typed ones included.
|
|
90
|
+
|
|
91
|
+
`TMDataGrid.FilterPills` takes the grid as an `api` prop rather than reading
|
|
92
|
+
context, so active filters can live in a page header or anywhere else:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { TMDataGridFilterPills } from "@jielga/tmdatagrid";
|
|
96
|
+
|
|
97
|
+
<TMDataGridFilterPills api={grid} onPillClick={(columnId) => focus(columnId)} />;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
One pill per **active** filter - `First name: Sofia ✕` - where ✕ clears it and a
|
|
101
|
+
click on the label reopens the panel on its column. The label spells the
|
|
102
|
+
operator out unless it is the type's default: `Age is greater than 30`, but
|
|
103
|
+
`First name: Sofia`. `openColumnFilter(api, columnId)` does the same reopening
|
|
104
|
+
from anywhere, seeding an empty row if the column has none.
|
|
105
|
+
|
|
106
|
+
## Replacing the value control
|
|
107
|
+
|
|
108
|
+
The built-in control follows the type and operator. Four alternatives ship as
|
|
109
|
+
named exports:
|
|
110
|
+
|
|
111
|
+
| Export | For | Renders |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| `DgRangeSliderFilter` | `number` | Range slider seeded from the data's min/max, writing the `between` pair |
|
|
114
|
+
| `DgDateRangeFilter` | `date` | A From/To pair of native date inputs |
|
|
115
|
+
| `DgAutocompleteFilter` | `string` | Free text with faceted values as suggestions |
|
|
116
|
+
| `DgTriStateFilter` | `boolean` | All / Yes / No segments, All clearing the filter |
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
meta: {
|
|
120
|
+
type: "number",
|
|
121
|
+
filterControl: DgRangeSliderFilter,
|
|
122
|
+
defaultFilterOperator: "between",
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Pair the range-shaped ones with `defaultFilterOperator: "between"` so the filter
|
|
127
|
+
opens on them. For operators outside their shape every built-in falls back to
|
|
128
|
+
`TMDataGridFilterValueInput`, which is exported so a custom control can take the
|
|
129
|
+
same escape.
|
|
130
|
+
|
|
131
|
+
`meta.filterControl` is a **component**, rendered as JSX, so hooks are legal
|
|
132
|
+
inside. It is a **value-only contract**: it reads `operator` to shape itself and
|
|
133
|
+
writes the bare value through `onChange`, and the grid composes the stored
|
|
134
|
+
`{ operator, value }` around it. The column and operator dropdowns stay the
|
|
135
|
+
panel's.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import type { TMDataGridFilterControlComponent } from "@jielga/tmdatagrid";
|
|
139
|
+
|
|
140
|
+
// Module scope: a new identity per render remounts the control mid-typing.
|
|
141
|
+
const StatusFilter: TMDataGridFilterControlComponent = ({
|
|
142
|
+
operator,
|
|
143
|
+
value,
|
|
144
|
+
options,
|
|
145
|
+
onChange,
|
|
146
|
+
}) => (
|
|
147
|
+
<Chip.Group
|
|
148
|
+
multiple
|
|
149
|
+
value={Array.isArray(value) ? [...value] : []}
|
|
150
|
+
onChange={onChange}
|
|
151
|
+
>
|
|
152
|
+
{options.map((option) => (
|
|
153
|
+
<Chip key={option.value} value={option.value}>
|
|
154
|
+
{option.label}
|
|
155
|
+
</Chip>
|
|
156
|
+
))}
|
|
157
|
+
</Chip.Group>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
meta: { type: "select", filterControl: StatusFilter }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`args.options` arrives pre-resolved for a column that declares `meta.options` or
|
|
164
|
+
is select-shaped; `args.table` is the escape hatch for a control that must reach
|
|
165
|
+
further.
|
|
166
|
+
|
|
167
|
+
To give a column its own *matching* rather than its own control, set `filterFn`
|
|
168
|
+
on the column definition. The grid only provides `"tmDataGrid"` as the default.
|
|
169
|
+
|
|
170
|
+
## Quick search
|
|
171
|
+
|
|
172
|
+
`TMDataGrid.Search` is a debounced input writing TanStack's `globalFilter`. No
|
|
173
|
+
option turns it on: you render it or you do not.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<TMDataGrid.Toolbar>
|
|
177
|
+
<TMDataGrid.Search placeholder="Search employees" debounce={0} />
|
|
178
|
+
</TMDataGrid.Toolbar>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Matching is **fuzzy by default**: typos and skipped characters are forgiven, and
|
|
182
|
+
while the search is the only thing narrowing the grid (no sort, no grouping) the
|
|
183
|
+
rows order by match quality, best first. That ordering is derived, never written
|
|
184
|
+
into `sorting` - no column claims `aria-sort`, nothing lands in the persisted
|
|
185
|
+
slices, and the reader's next sort click takes over just by existing.
|
|
186
|
+
|
|
187
|
+
`quickSearchMode: "contains"` restores plain substring matching. An explicit
|
|
188
|
+
`globalFilterFn` overrides both, and switches the rank ordering off with it.
|
|
189
|
+
`fuzzyGlobalFilterFn` is exported for a custom input over the same matching.
|
|
190
|
+
|
|
191
|
+
`enableMatchHighlighting: true` marks the matched slice of a cell's text, under
|
|
192
|
+
the quick search or a `contains` / `startsWith` / `endsWith` column filter. What
|
|
193
|
+
gets marked is the contiguous, case-insensitive occurrence, so a fuzzy
|
|
194
|
+
typo-match with no contiguous occurrence shows no highlight. It applies to
|
|
195
|
+
**default-rendered cells only**: a column with its own `cell` renderer opts out
|
|
196
|
+
by existing.
|
|
197
|
+
|
|
198
|
+
## Turning it off
|
|
199
|
+
|
|
200
|
+
`enableColumnFilters: false` removes the panel, the button and the menu item;
|
|
201
|
+
`enableColumnFilter: false` on a column takes that column out.
|
|
202
|
+
`enableGlobalFilter: false` removes the search input, or on a column removes its
|
|
203
|
+
participation in the search. Generated lanes already opt out.
|
|
204
|
+
|
|
205
|
+
## Common mistakes
|
|
206
|
+
|
|
207
|
+
### CRITICAL Treating presence in `columnFilters` as an active filter
|
|
208
|
+
|
|
209
|
+
A half-typed filter stays in state on purpose, so the panel keeps its row while
|
|
210
|
+
the reader types. Counting entries reports filters that are narrowing nothing,
|
|
211
|
+
and forwarding them to a server sends `{ operator: "contains", value: "" }` as a
|
|
212
|
+
real constraint.
|
|
213
|
+
|
|
214
|
+
Wrong:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
const activeCount = columnFilters.length;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Correct:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { isFilterActive } from "@jielga/tmdatagrid";
|
|
224
|
+
|
|
225
|
+
const active = columnFilters.filter((filter) => isFilterActive(filter.value));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Source: `src/docs/filtering.md` (How a filter is stored).
|
|
229
|
+
|
|
230
|
+
### HIGH Assuming `value` is always a string
|
|
231
|
+
|
|
232
|
+
`value` is a `[min, max]` pair under `between` and an array under `isAnyOf` /
|
|
233
|
+
`isNoneOf`. Code written against the single-string case compiles against the
|
|
234
|
+
union and fails at runtime on the first range filter.
|
|
235
|
+
|
|
236
|
+
Correct:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import {
|
|
240
|
+
operatorTakesArrayValue,
|
|
241
|
+
operatorTakesRangeValue,
|
|
242
|
+
} from "@jielga/tmdatagrid";
|
|
243
|
+
|
|
244
|
+
if (operatorTakesRangeValue(operator)) {
|
|
245
|
+
const [min, max] = value as ReadonlyArray<string>;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Source: `src/tmdatagrid/core/filterOperators.ts`.
|
|
250
|
+
|
|
251
|
+
### HIGH A numeric column with no `meta.type`
|
|
252
|
+
|
|
253
|
+
`getColumnType` defaults to `"string"`, so a numeric column that omits
|
|
254
|
+
`meta: { type: "number" }` offers only the string operators. `greaterThan` and
|
|
255
|
+
`between` never appear in the panel, and comparisons run as text - `"9"` sorts
|
|
256
|
+
above `"10"`.
|
|
257
|
+
|
|
258
|
+
Source: `src/docs/filtering.md` (Operators).
|
|
259
|
+
|
|
260
|
+
### HIGH A filter control defined inside the component
|
|
261
|
+
|
|
262
|
+
`meta.filterControl` is rendered as JSX, so its identity is its component type.
|
|
263
|
+
An inline arrow is a new type every render, which remounts the control and loses
|
|
264
|
+
what was being typed into it.
|
|
265
|
+
|
|
266
|
+
Correct:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
// Module scope, referenced by name.
|
|
270
|
+
const StatusFilter: TMDataGridFilterControlComponent = (args) => { /* … */ };
|
|
271
|
+
|
|
272
|
+
meta: { filterControl: StatusFilter }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Source: `src/docs/filtering.md` (Writing your own).
|
|
276
|
+
|
|
277
|
+
### MEDIUM Writing the whole filter from a custom control
|
|
278
|
+
|
|
279
|
+
The contract is value-only: `onChange` takes the bare value, and the grid
|
|
280
|
+
composes `{ operator, value }` around it. Passing an object writes a filter
|
|
281
|
+
whose `value` is itself an object, which no operator can match against.
|
|
282
|
+
|
|
283
|
+
Wrong:
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
onChange({ operator: "isAnyOf", value: picked });
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Correct:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
onChange(picked);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Source: `src/docs/filtering.md` (Writing your own).
|
|
296
|
+
|
|
297
|
+
### MEDIUM Expecting match highlighting in a custom cell
|
|
298
|
+
|
|
299
|
+
The grid replicates the default value-to-string render with marks added, and
|
|
300
|
+
never rummages inside a custom renderer's output. A column with a `cell`
|
|
301
|
+
renderer shows no marks however `enableMatchHighlighting` is set. Equality
|
|
302
|
+
operators highlight nothing either.
|
|
303
|
+
|
|
304
|
+
Source: `src/docs/quick-search.md` (Match highlighting).
|
|
305
|
+
|
|
306
|
+
### MEDIUM Expecting fuzzy ranking to survive a sort
|
|
307
|
+
|
|
308
|
+
The rank ordering applies only while the search is the sole narrowing. Any sort
|
|
309
|
+
or grouping takes over, by design - the ordering is derived and never written
|
|
310
|
+
into `sorting`, so there is nothing to clear afterwards.
|
|
311
|
+
|
|
312
|
+
Source: `src/docs/quick-search.md` (Fuzzy by default).
|
|
313
|
+
|
|
314
|
+
## Reference
|
|
315
|
+
|
|
316
|
+
| Name | Kind | Type | Default | What it does |
|
|
317
|
+
| --- | --- | --- | --- | --- |
|
|
318
|
+
| `enableColumnFilters` | Table option | `boolean` | `true` | `false` removes the panel, the button and the menu item. |
|
|
319
|
+
| `enableColumnFilter` | Column option | `boolean` | `true` | `false` takes one column out of filtering. |
|
|
320
|
+
| `meta.type` | Column meta | `TMDataGridColumnType` | `"string"` | Selects the operators and the value control. |
|
|
321
|
+
| `meta.defaultFilterOperator` | Column meta | `TMDataGridFilterOperator` | The type's default | The operator a fresh filter opens on. |
|
|
322
|
+
| `meta.filterControl` | Column meta | `TMDataGridFilterControlComponent` | By type and operator | Replaces the value control. Module scope. |
|
|
323
|
+
| `filterFn` | Column option | name or fn | `"tmDataGrid"` | Custom matching for one column. |
|
|
324
|
+
| `quickSearchMode` | Option | `"fuzzy" \| "contains"` | `"fuzzy"` | How the quick search matches. |
|
|
325
|
+
| `enableMatchHighlighting` | Option | `boolean` | `false` | Mark matched text in default-rendered cells. |
|
|
326
|
+
| `enableGlobalFilter` | Table option | `boolean` | `true` | Also a column option. Removes the input, or one column's participation. |
|
|
327
|
+
| `globalFilterFn` | Table option | filter fn | fuzzy | Overrides the matching, and the ranking with it. |
|
|
328
|
+
| `TMDataGrid.FilterPanel` | Component | – | – | The panel of filter rows. |
|
|
329
|
+
| `TMDataGrid.FilterButton` | Component | – | – | Toolbar button opening the panel, with an active count. |
|
|
330
|
+
| `TMDataGrid.FilterPills` | Component | `api`, `size`, `showClearAll`, `onPillClick`, `className` | – | Active filters as removable pills, renderable anywhere. |
|
|
331
|
+
| `TMDataGrid.Search` | Component | `placeholder`, `debounce` (`250`), `w` (`220`) | – | The debounced quick-search input. |
|
|
332
|
+
| `openColumnFilter` | Export | `(api, columnId) => void` | – | Opens the panel on a column. |
|
|
333
|
+
| `isFilterActive` | Export | `(value) => boolean` | – | Whether a filter value is doing anything. |
|
|
334
|
+
| `getOperatorsForType` | Export | `(type) => operators` | – | The operator list a type offers. |
|
|
335
|
+
| `FILTER_OPERATOR_LABELS` | Export | record | – | The label shown for each operator. |
|
|
336
|
+
| `formatFilterLabel` | Export | `({ label, type, filter }) => string` | – | The one-line description used on the pills. |
|
|
337
|
+
| `emptyValueForOperator` · `operatorNeedsValue` · `operatorTakesArrayValue` · `operatorTakesRangeValue` | Exports | – | – | What shape of value an operator wants. |
|
|
338
|
+
| `TMDataGridFilterValueInput` | Export | component | – | The default value control, for falling back to. |
|
|
339
|
+
| `DgRangeSliderFilter` · `DgDateRangeFilter` · `DgAutocompleteFilter` · `DgTriStateFilter` | Exports | components | – | The four ready-made controls. |
|
|
340
|
+
| `fuzzyGlobalFilterFn` | Export | filter fn | – | The default matcher, for a custom input. |
|
|
341
|
+
| `--dg-match-highlight-bg` | CSS variable | colour | Themed yellow | The mark colour. |
|
|
342
|
+
|
|
343
|
+
See also: the `columns` skill for `meta.type` and `meta.options`, and the
|
|
344
|
+
`server-side` skill for forwarding `columnFilters` to an API.
|
|
@@ -3,17 +3,18 @@ name: getting-started
|
|
|
3
3
|
description: >
|
|
4
4
|
Set up TMDataGrid, a compound React data grid built on TanStack Table v9 and
|
|
5
5
|
Mantine. Covers useTMDataGrid, the TMDataGrid root, context, the component
|
|
6
|
-
catalog (Table, Footer, Toolbar, Spacer, SummaryCount,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
parts to render, or when
|
|
6
|
+
catalog (Table, Footer, Toolbar, Spacer, SummaryCount, Search,
|
|
7
|
+
LoadingIndicator, EditActions, FilterButton, ColumnsButton, FilterPanel,
|
|
8
|
+
FilterPills, ColumnsPanel), the size scale and the bounded-height layout
|
|
9
|
+
requirement. Load when adding a grid, choosing which parts to render, or when
|
|
10
|
+
rows do not appear.
|
|
10
11
|
metadata:
|
|
11
12
|
type: core
|
|
12
13
|
library: '@jielga/tmdatagrid'
|
|
13
|
-
library_version: '1.0.
|
|
14
|
+
library_version: '1.0.2'
|
|
14
15
|
sources:
|
|
15
16
|
- 'Jielga/TMDataGrid:src/docs/getting-started.md'
|
|
16
|
-
- 'Jielga/TMDataGrid:src/docs/
|
|
17
|
+
- 'Jielga/TMDataGrid:src/docs/anatomy.md'
|
|
17
18
|
- 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'
|
|
18
19
|
- 'Jielga/TMDataGrid:src/tmdatagrid/core/sizes.ts'
|
|
19
20
|
---
|
|
@@ -103,14 +104,31 @@ Spread the hook result (`{...grid}`) rather than assigning `table`, `ui` and
|
|
|
103
104
|
| Column menu | On hover: sort, filter, pin, move, hide, manage columns. |
|
|
104
105
|
| Filter panel | Column, operator and value rows. |
|
|
105
106
|
| Column manager | Search, toggle, show/hide all, reset. |
|
|
106
|
-
| Row selection | Checkbox column pinned to the left, or click-to-select rows with `
|
|
107
|
+
| Row selection | Checkbox column pinned to the left, or click-to-select rows with `selectionMode: "row"`. See the `rows` skill. |
|
|
107
108
|
| Pagination | Off by default: all rows render, virtualized. Opt in with `enablePagination: true`. |
|
|
108
109
|
| Sizing | `size="xs"` to `size="xl"` scales rows, type and controls. |
|
|
109
110
|
|
|
110
111
|
Each is bound to a capability check - disabling the standard table or column
|
|
111
|
-
option also removes its interface
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
option also removes its interface, and empty menus and inactive buttons are
|
|
113
|
+
never rendered. Column ordering (`enableColumnOrdering`) and pagination
|
|
114
|
+
(`enablePagination`) are the two switches the grid defines itself, because
|
|
115
|
+
TanStack ships state and APIs for both but no `enable` option.
|
|
116
|
+
|
|
117
|
+
## Which skill covers what
|
|
118
|
+
|
|
119
|
+
| Need to | Load |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| Define columns, size, hide, pin, reorder, sort them | `columns` |
|
|
122
|
+
| Filter, or add a search box | `filtering` |
|
|
123
|
+
| Select rows, react to clicks, colour rows, details, pin rows | `rows` |
|
|
124
|
+
| Move a cell cursor, select ranges, copy, export CSV | `cell-selection` |
|
|
125
|
+
| Make cells editable | `editing` |
|
|
126
|
+
| Group rows, aggregate, add a summary row | `grouping` |
|
|
127
|
+
| Page, tune scrolling, handle empty and loading states | `data` |
|
|
128
|
+
| Back the grid with a server | `server-side` |
|
|
129
|
+
| Configure the hook, or persist state | `options` |
|
|
130
|
+
| Theme, size, compose the toolbar, translate | `appearance` |
|
|
131
|
+
| Write tests against a grid | `testing` |
|
|
114
132
|
|
|
115
133
|
## Layout
|
|
116
134
|
|
|
@@ -131,11 +149,14 @@ flex column.
|
|
|
131
149
|
| Component | Props | Notes |
|
|
132
150
|
| --- | --- | --- |
|
|
133
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`. |
|
|
134
|
-
| `TMDataGrid.Table` | `onRowClick(row)`, `rowContextMenu` render prop, `rowContextMenuProps` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `
|
|
152
|
+
| `TMDataGrid.Table` | `onRowClick(row)`, `rowContextMenu` render prop, `rowContextMenuProps` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `selectionMode: "row"`. |
|
|
135
153
|
| `TMDataGrid.Footer` | `pageSizeOptions` (default `[10, 25, 50, 100]`), `pagination` render prop | Pagination controls. Renders nothing unless pagination is enabled. |
|
|
136
154
|
| `TMDataGrid.Toolbar` | `children` | Flex row above the grid. |
|
|
137
155
|
| `TMDataGrid.Spacer` | - | Pushes later toolbar items right. |
|
|
138
156
|
| `TMDataGrid.SummaryCount` | `children` | Visible rows out of total. |
|
|
157
|
+
| `TMDataGrid.Search` | `placeholder`, `debounce` (default `250`), `w` (default `220`) | Quick search over every column, debounced into `globalFilter`. Renders nothing under `enableGlobalFilter: false`. |
|
|
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. |
|
|
139
160
|
| `TMDataGrid.FilterButton` | - | Toggles filter panel. Renders nothing if no column is filterable. |
|
|
140
161
|
| `TMDataGrid.ColumnsButton` | - | Opens column manager. Renders nothing if no column is hideable. |
|
|
141
162
|
| `TMDataGrid.FilterPanel` | - | Rendered by `.Table`; exported for custom layouts. Header close button, Escape, click-away, "Add filter" and "Clear all". |
|
|
@@ -229,7 +250,7 @@ mirrors them. Set `meta.rowHeight` for a height outside the scale.
|
|
|
229
250
|
| `getColumnLabel(column)` | `meta.label`, a string header, or the column id. |
|
|
230
251
|
| `getColumnType(column)` | `meta.type`, defaulting to `"string"`. |
|
|
231
252
|
| `formatFilterLabel({ label, type, filter })` | The one-line filter description used on the pills. |
|
|
232
|
-
| `moveColumn({ table, columnId, targetId, side })` | Moves a column next to another. See the `
|
|
253
|
+
| `moveColumn({ table, columnId, targetId, side })` | Moves a column next to another. See the `columns` skill. |
|
|
233
254
|
| `moveColumnByStep({ table, columnId, direction })` | Moves a column one position within its region. |
|
|
234
255
|
| `SELECT_COLUMN_ID` | Id of the generated checkbox column. |
|
|
235
256
|
|