@jielga/tmdatagrid 1.0.0 → 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/README.md +8 -8
- package/dist/index.d.ts +225 -225
- package/dist/index.js +58 -50
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -2
- package/skills/appearance/SKILL.md +322 -0
- package/skills/cell-selection/SKILL.md +240 -0
- package/skills/columns/SKILL.md +261 -86
- 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 +48 -27
- package/skills/grouping/SKILL.md +264 -0
- package/skills/options/SKILL.md +31 -20
- package/skills/rows/SKILL.md +369 -0
- package/skills/rows/references/rows-api.md +117 -0
- package/skills/server-side/SKILL.md +7 -7
- package/skills/testing/SKILL.md +12 -12
- package/src/tmdatagrid/TMDataGridContext.ts +2 -2
- package/src/tmdatagrid/components/TMDataGrid.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGrid.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +6 -6
- package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +2 -2
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +12 -6
- package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +1 -1
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +8 -8
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +12 -12
- package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +4 -4
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +8 -8
- package/src/tmdatagrid/components/TMDataGridTable.module.css +18 -18
- package/src/tmdatagrid/components/TMDataGridTable.tsx +116 -116
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +5 -5
- package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +1 -1
- package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +2 -2
- package/src/tmdatagrid/components/editors/editorShared.ts +3 -3
- package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +1 -1
- package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +2 -2
- package/src/tmdatagrid/components/sticky.module.css +8 -8
- package/src/tmdatagrid/core/autosize.ts +4 -4
- package/src/tmdatagrid/core/capabilities.ts +17 -17
- package/src/tmdatagrid/core/cellExport.ts +13 -13
- package/src/tmdatagrid/core/cellNavigation.ts +6 -6
- package/src/tmdatagrid/core/cellRange.ts +4 -4
- package/src/tmdatagrid/core/columnOptions.ts +2 -2
- package/src/tmdatagrid/core/columnOrdering.ts +2 -2
- package/src/tmdatagrid/core/columnUtils.ts +3 -3
- package/src/tmdatagrid/core/editEngine.ts +49 -49
- package/src/tmdatagrid/core/expanding.ts +5 -5
- package/src/tmdatagrid/core/filterControls.ts +5 -5
- package/src/tmdatagrid/core/filterOperators.ts +14 -14
- package/src/tmdatagrid/core/labels.ts +8 -8
- package/src/tmdatagrid/core/matchHighlight.ts +4 -4
- package/src/tmdatagrid/core/persistence.ts +8 -8
- package/src/tmdatagrid/core/quickSearch.ts +8 -8
- package/src/tmdatagrid/core/rowPinning.ts +3 -3
- package/src/tmdatagrid/core/rowSelection.ts +12 -12
- package/src/tmdatagrid/core/sizes.ts +1 -1
- package/src/tmdatagrid/core/summary.ts +3 -3
- package/src/tmdatagrid/useTMDataGrid.tsx +79 -79
- 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.
|