@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.
@@ -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, FilterButton,
7
- ColumnsButton, FilterPanel, FilterPills, ColumnsPanel), the size scale and the
8
- bounded-height layout requirement. Load when adding a grid, choosing which
9
- parts to render, or when rows do not appear.
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.1'
14
+ library_version: '1.0.2'
14
15
  sources:
15
16
  - 'Jielga/TMDataGrid:src/docs/getting-started.md'
16
- - 'Jielga/TMDataGrid:src/docs/components.md'
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 `rowSelectionMode: "row"`. |
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. Column ordering (`enableColumnOrdering`) and
112
- pagination (`enablePagination`) are the two switches the grid defines itself.
113
- See the `features` skill.
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 `rowSelectionMode: "row"`. |
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 `features` skill. |
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