@jielga/tmdatagrid 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +262 -8
- package/dist/index.js +1340 -1218
- package/dist/index.js.map +1 -1
- package/docs/columns.md +7 -0
- package/docs/filtering.md +2 -1
- package/docs/grouping.md +1 -1
- package/docs/localization.md +3 -3
- package/docs/persistence.md +88 -0
- package/docs/typed-deep-links.md +169 -0
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +1 -1
- package/skills/appearance/references/labels.md +3 -3
- package/skills/cell-selection/SKILL.md +1 -1
- package/skills/columns/SKILL.md +4 -1
- package/skills/data/SKILL.md +1 -1
- package/skills/editing/SKILL.md +1 -1
- package/skills/filtering/SKILL.md +3 -2
- package/skills/getting-started/SKILL.md +2 -2
- package/skills/grouping/SKILL.md +2 -2
- package/skills/migrating-to-2/SKILL.md +1 -1
- package/skills/options/SKILL.md +30 -6
- package/skills/options/references/typed-links.md +134 -0
- package/skills/rows/SKILL.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/skills/testing-components/SKILL.md +1 -1
- package/skills/testing-editing/SKILL.md +1 -1
- package/src/components/TMDataGridFilterPills.tsx +15 -1
- package/src/components/TMDataGridGroupColumn.tsx +19 -4
- package/src/components/TMDataGridHeaderFilterRow.tsx +7 -1
- package/src/core/filterOperators.ts +41 -18
- package/src/core/grouping.ts +11 -3
- package/src/core/labels.ts +4 -3
- package/src/core/persistence.ts +9 -1
- package/src/core/urlState.ts +439 -0
- package/src/core/valueText.ts +46 -0
- package/src/index.ts +13 -0
- package/src/useTMDataGrid.tsx +50 -7
package/docs/columns.md
CHANGED
|
@@ -47,6 +47,13 @@ addresses the column by id - `initialState`, `editing.columns`, `moveColumn`,
|
|
|
47
47
|
a test selector - takes the underscore form; the dotted form silently matches
|
|
48
48
|
nothing.
|
|
49
49
|
|
|
50
|
+
Each column the helper returns also carries, in its type only, its id,
|
|
51
|
+
`meta.type`, value type, `meta.filter.operators`, and its `enableColumnFilter`,
|
|
52
|
+
`enableSorting` and `meta.filter.url` switches.
|
|
53
|
+
[Typed links](/docs/persistence#typed-links) read them, so a link into the grid
|
|
54
|
+
can name only the filters and sorts its columns offer.
|
|
55
|
+
Only literal values count: `enableSorting: isAdmin` reads as not sortable.
|
|
56
|
+
|
|
50
57
|
### Columns derived from the other rows
|
|
51
58
|
|
|
52
59
|
`accessorFn` is handed one row, so a value that depends on the rest - a share
|
package/docs/filtering.md
CHANGED
|
@@ -206,6 +206,7 @@ file: columns/FilterPanelPlaced.tsx
|
|
|
206
206
|
## TMDataGrid.FilterPills
|
|
207
207
|
|
|
208
208
|
One pill per active filter - `First name: Sofia ✕` - where the ✕ clears that filter and a click on the label sends the user to that column's control through [openColumnFilter](#opencolumnfilter).
|
|
209
|
+
A value with an option reads as the option's label, and a boolean value as `labels.booleanTrue` or `labels.booleanFalse`.
|
|
209
210
|
Half-typed filters produce no pill.
|
|
210
211
|
The label spells the operator out unless it is the column type's default: `Age is greater than 30`, but `First name: Sofia`.
|
|
211
212
|
|
|
@@ -447,7 +448,7 @@ stays, empty.
|
|
|
447
448
|
| `getColumnOperators` · `getColumnDefaultOperator` | Exports | `(column) => operators` · `(column) => operator` | – | The list one column offers after `meta.filter.operators`, and the operator a fresh filter on it opens on. |
|
|
448
449
|
| `FILTER_OPERATOR_LABELS` | Export | record | – | The label shown for each operator. |
|
|
449
450
|
| `TMDataGridFilterValueInput` | Export | component | – | The default value control, for falling back to. |
|
|
450
|
-
| `formatFilterLabel` | Export | `({ label, type, filter }) => string` | – | The one-line description used on the pills. |
|
|
451
|
+
| `formatFilterLabel` | Export | `({ label, type, filter, operatorLabels?, options?, labels? }) => string` | – | The one-line description used on the pills. `operatorLabels` names the operators, `options` gives a value its option's label, and `labels` gives a boolean its `booleanTrue` / `booleanFalse`; each defaults to English or to the value itself. |
|
|
451
452
|
| `emptyValueForOperator` · `operatorNeedsValue` · `operatorTakesArrayValue` · `operatorTakesRangeValue` · `filterValueShape` | Exports | – | – | What shape of value an operator expects. |
|
|
452
453
|
| `TMDataGridFilterValueShape` | Type | `"scalar" \| "set" \| "range"` | – | What `filterValueShape` returns for an operator. |
|
|
453
454
|
| `TMDataGridFiltersOptions` · `TMDataGridFiltersSettings` · `TMDataGridFilterSurface` · `TMDataGridFilterSidebarSide` | Types | – | – | The `filters` option, and the resolved form of it on `api.filters`. |
|
package/docs/grouping.md
CHANGED
|
@@ -160,7 +160,7 @@ server-side can set `enableGrouping: true` alongside `manualGrouping: true`. See
|
|
|
160
160
|
| `aggregationFn` | Column option | `TMDataGridAggregationName \| fn` | – | How a column fills in its group cells. Unset leaves them blank. |
|
|
161
161
|
| `aggregatedCell` | Column option | `(ctx) => ReactNode` | `cell` over the aggregate when `aggregationFn` is set, else blank | Renders a group row's value differently from a data row's. |
|
|
162
162
|
| `GROUP_COLUMN_ID` | Export | `"__group__"` | – | Id of the generated tree column. |
|
|
163
|
-
| `formatGroupValue` | Export | `(value) => string` | – | How the tree lane
|
|
163
|
+
| `formatGroupValue` | Export | `(value, blankLabel?, { type?, options?, labels? }?) => string` | – | How the tree lane writes a group's value: `blankLabel` for an empty value, an option's label for a value with an option, `labels.booleanTrue` / `booleanFalse` for a boolean, and `toLocaleDateString()` for a `Date`. |
|
|
164
164
|
| `getGroupDataRows` | Export | `(row) => Row[]` | – | Every record under a group row, at any depth. |
|
|
165
165
|
| `isPagingActive` | Export | `(table, features) => boolean` | – | Whether the pager is slicing anything. `false` while grouped. |
|
|
166
166
|
| `--dg-row-group-bg` | CSS variable | colour | Themed | Group row background. |
|
package/docs/localization.md
CHANGED
|
@@ -111,8 +111,8 @@ shows it; a function label shows its template:
|
|
|
111
111
|
| `filterFrom` | `From` | The lower bound of a `between` filter: its input, or its slider thumb. |
|
|
112
112
|
| `filterTo` | `To` | The upper bound of a `between` filter: its input, or its slider thumb. |
|
|
113
113
|
| `filterAll` | `All` | The no-filter segment of the tri-state filter. |
|
|
114
|
-
| `booleanTrue` | `Yes` | The `true` choice of a boolean filter value control and of the tri-state filter. |
|
|
115
|
-
| `booleanFalse` | `No` | The `false` choice of a boolean filter value control and of the tri-state filter. |
|
|
114
|
+
| `booleanTrue` | `Yes` | The `true` choice of a boolean filter value control and of the tri-state filter, and a `true` value on a filter pill and in a group row. |
|
|
115
|
+
| `booleanFalse` | `No` | The `false` choice of a boolean filter value control and of the tri-state filter, and a `false` value on a filter pill and in a group row. |
|
|
116
116
|
| `addFilter` | `Add filter` | Button in `TMDataGrid.FilterPanel`. |
|
|
117
117
|
| `clearAllFilters` | `Clear all` | Button in `TMDataGrid.FilterPanel` and in `TMDataGrid.FilterPills`. |
|
|
118
118
|
| `closeFilters` | `Close filters` | `aria-label` of the close button of the filter popup and sidebar. |
|
|
@@ -134,7 +134,7 @@ shows it; a function label shows its template:
|
|
|
134
134
|
| `moveRight` | `Move right` | Column menu item. |
|
|
135
135
|
| `autosizeColumn` | `Autosize column` | Column menu item. |
|
|
136
136
|
| `hideColumn` | `Hide column` | Column menu item. |
|
|
137
|
-
| `filterOn` | `` `Filter on ${column}` `` | `aria-label` of the header filter button, of
|
|
137
|
+
| `filterOn` | `` `Filter on ${column}` `` | `aria-label` of the header filter button, of the header filter cell of a column that can filter, and of the value control in it. |
|
|
138
138
|
| `sortColumn` | `` `Sort ${column}` `` | `aria-label` of the header sort button. |
|
|
139
139
|
| `columnMenu` | `` `${column} column menu` `` | `aria-label` of the column menu trigger. |
|
|
140
140
|
| `rowsPerPage` | `Rows per page:` | `TMDataGrid.Footer`: the text before the page size select. |
|
package/docs/persistence.md
CHANGED
|
@@ -116,6 +116,55 @@ The column's filter is then not persisted anywhere. A filter on it in
|
|
|
116
116
|
`initialState` still applies when a link is opened. The flag has no effect
|
|
117
117
|
unless `urlKey` carries `columnFilters`.
|
|
118
118
|
|
|
119
|
+
### Typed links
|
|
120
|
+
|
|
121
|
+
`createTMDataGridUrlState` defines a grid's deep links from its columns.
|
|
122
|
+
Pass it the array the grid renders, built with `createTMDataGridColumnHelper`:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
const columns = columnHelper.columns([
|
|
126
|
+
columnHelper.accessor("department", { header: "Department", meta: { type: "select" } }),
|
|
127
|
+
columnHelper.accessor("salary", { header: "Salary", meta: { type: "number" } }),
|
|
128
|
+
columnHelper.accessor("email", { header: "Email", enableColumnFilter: false }),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
export const employeesUrl = createTMDataGridUrlState({
|
|
132
|
+
key: "employees",
|
|
133
|
+
columns,
|
|
134
|
+
slices: ["columnFilters", "sorting"],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// The grid
|
|
138
|
+
const persist = { urlKey: employeesUrl.urlKey } satisfies TMDataGridPersistence;
|
|
139
|
+
|
|
140
|
+
// A link from anywhere in the app
|
|
141
|
+
const href = `/employees?${employeesUrl.searchParams({
|
|
142
|
+
filters: { department: { operator: "isAnyOf", value: ["Design"] } },
|
|
143
|
+
sorting: [{ id: "salary", desc: true }],
|
|
144
|
+
})}`;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A link may name only what the grid's columns offer:
|
|
148
|
+
|
|
149
|
+
- `filters` takes the columns that can be filtered: `enableColumnFilter` is not `false` and `meta.filter.url` is not `false`.
|
|
150
|
+
- The operators are the ones the column's `meta.type` offers, narrowed by `meta.filter.operators`.
|
|
151
|
+
- The value has the column's type: a `number` for a number column, a `Date` or a `YYYY-MM-DD` string for a date column, the column's own values for a select column, and no value for `isEmpty` and `isNotEmpty`.
|
|
152
|
+
- `sorting` takes the columns whose `enableSorting` is not `false`.
|
|
153
|
+
- Only the slices in `slices` are accepted. By default, every data slice is.
|
|
154
|
+
|
|
155
|
+
Anything else is a type error.
|
|
156
|
+
The column helper reads these switches from literal values, so `enableSorting: isAdmin` reads as not sortable.
|
|
157
|
+
A column with an accessor function is named by its `id`; without an `id` it cannot be named.
|
|
158
|
+
Columns built without the helper offer nothing to a link.
|
|
159
|
+
|
|
160
|
+
The definition builds the link in three forms:
|
|
161
|
+
|
|
162
|
+
- `searchParams(input)` - `URLSearchParams` for a plain URL, encoded the way the default `serialize` encodes the param
|
|
163
|
+
- `search(input)` - `{ [key]: payload }`, for a router that keeps objects in its search
|
|
164
|
+
- `payload(input)` - the param's value alone, in the grid's state shape
|
|
165
|
+
|
|
166
|
+
Pass `searchParams` the same `serialize` as `persist` when you set one.
|
|
167
|
+
|
|
119
168
|
### Routers that own the URL
|
|
120
169
|
|
|
121
170
|
`urlStorage` replaces the default read and write of the param, for a router
|
|
@@ -152,6 +201,31 @@ const persist = useMemo(
|
|
|
152
201
|
);
|
|
153
202
|
```
|
|
154
203
|
|
|
204
|
+
A router that keeps objects in its search params and encodes them itself,
|
|
205
|
+
such as TanStack Router, takes the payload as an object.
|
|
206
|
+
Pass its search to `persist` on a typed link definition:
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
const router = useRouter();
|
|
210
|
+
const persist = useMemo(
|
|
211
|
+
() =>
|
|
212
|
+
employeesUrl.persist({
|
|
213
|
+
get: (name) => (router.state.location.search as Record<string, unknown>)[name],
|
|
214
|
+
set: (name, value) =>
|
|
215
|
+
void router.navigate({
|
|
216
|
+
to: ".",
|
|
217
|
+
search: (prev: Record<string, unknown>) => ({ ...prev, [name]: value }),
|
|
218
|
+
replace: true,
|
|
219
|
+
}),
|
|
220
|
+
}),
|
|
221
|
+
[router],
|
|
222
|
+
);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`get` returns the param's value, or `undefined`; `set` receives the payload, or `undefined` to remove the param.
|
|
226
|
+
Leave `serialize` and `deserialize` at their defaults with `persist(storage)`, since the router does the encoding.
|
|
227
|
+
[Typed links with TanStack Router](/docs/typed-deep-links) puts the route, the grid and the links together.
|
|
228
|
+
|
|
155
229
|
## Behaviour
|
|
156
230
|
|
|
157
231
|
**Restoring happens once**, on mount, through `initialState`. Writing is a
|
|
@@ -201,6 +275,20 @@ its `StorageType`.
|
|
|
201
275
|
| `urlKey` | persist field | `string \| [string, DataSlice[]]` | – | Search param for data slices kept in the URL. See [Deep links](#deep-links). |
|
|
202
276
|
| `urlStorage` | persist field | `TMDataGridUrlStorage` | `location.search` and `history.replaceState` | Reads and writes the URL param. |
|
|
203
277
|
| `TMDataGridUrlStorage` | Type | `{ get(name): string \| null; set(name, value: string \| null): void }` | – | The type of `urlStorage`. |
|
|
278
|
+
| `createTMDataGridUrlState` | Export | `({ key, columns, slices? }) => TMDataGridUrlState` | – | Defines typed links into the grid from its columns. See [Typed links](#typed-links). |
|
|
279
|
+
| `key` | Definition field | `string` | – | The URL search param. |
|
|
280
|
+
| `slices` | Definition field | `TMDataGridDataSlice[]` | Every data slice | The data slices the param carries. |
|
|
281
|
+
| `urlKey` | Definition field | `TMDataGridPersistKey` | – | For `persist.urlKey`: the key, or `[key, slices]` when `slices` is set. |
|
|
282
|
+
| `searchParams` | Definition method | `(input, { serialize? }) => URLSearchParams` | `serialize`: `JSON.stringify` | The link as search params. Empty when the input sets nothing. |
|
|
283
|
+
| `search` | Definition method | `(input) => { [key]: TMDataGridUrlPayload \| undefined }` | – | The link as a router search object. |
|
|
284
|
+
| `payload` | Definition method | `(input) => TMDataGridUrlPayload \| undefined` | – | The param's value, or `undefined` when the input sets nothing. |
|
|
285
|
+
| `validateSearch` | Definition method | `(search) => { [key]?: TMDataGridUrlPayload }` | – | A route search validator. Keeps the param when it is an object. |
|
|
286
|
+
| `persist` | Definition method | `(storage?: TMDataGridUrlSearchStorage) => { urlKey, urlStorage? }` | – | Spread into `persist`. If not given a storage, the grid uses the browser's URL. |
|
|
287
|
+
| `TMDataGridUrlInput` | Type | `{ filters?, globalFilter?, sorting?, pagination? }` | – | What a link may set, typed from the columns and `slices`. |
|
|
288
|
+
| `TMDataGridUrlPayload` | Type | `{ columnFilters?, globalFilter?, sorting?, pagination?, expanded? }` | – | The param's value, in the grid's state shape. |
|
|
289
|
+
| `TMDataGridUrlSearchStorage` | Type | `{ get(name): unknown; set(name, value: TMDataGridUrlPayload \| undefined): void }` | – | A router search read and written as values. |
|
|
290
|
+
| `TMDataGridColumnInfo` · `TMDataGridColumnInfoOf` · `TMDataGridTypedColumn` | Types | – | – | What a helper column tells a link about itself: id, type, value, operators, and its filter, sort and URL switches. |
|
|
291
|
+
| `TMDataGridUrlFiltersInput` · `TMDataGridUrlSortableId` · `TMDataGridDateInput` | Types | – | – | The parts of `TMDataGridUrlInput`. |
|
|
204
292
|
| `meta.filter.url` | Column meta | `boolean` | `true` | Determines whether the column's filter is written to the URL under `urlKey`. |
|
|
205
293
|
| `TMDataGridPersistKey` | Type | `string \| [string, slices]` | – | The type of `dataKey`, `settingsKey` and `urlKey`. |
|
|
206
294
|
| `storageMode` | persist field | `"localStorage" \| "sessionStorage"` | `"localStorage"` | Storage area. `"sessionStorage"` is per tab. |
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Typed links with TanStack Router
|
|
2
|
+
|
|
3
|
+
Links into a grid, built from the grid's own columns and checked by both the grid's types and the router's.
|
|
4
|
+
The grid's URL param lives in the router's search, and the router encodes it.
|
|
5
|
+
|
|
6
|
+
```demo
|
|
7
|
+
file: recipes/RouterDeepLinks.tsx
|
|
8
|
+
hint: Go to Links and open one. Change a filter or the search, and watch the address follow.
|
|
9
|
+
extraSources: data/employeeLinks.tsx, data/EmployeeLinksGrid.tsx
|
|
10
|
+
height: 640
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Define the links from the columns
|
|
14
|
+
|
|
15
|
+
Build the columns with `createTMDataGridColumnHelper`, and pass the same array to `createTMDataGridUrlState`:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
export const employeesLink = createTMDataGridUrlState({
|
|
19
|
+
key: "emp",
|
|
20
|
+
columns: employeeLinkColumns,
|
|
21
|
+
slices: ["columnFilters", "globalFilter", "sorting"],
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The columns decide what a link may name.
|
|
26
|
+
In this demo, `location` sets `meta.filter.url: false`, so no link can filter it.
|
|
27
|
+
[Typed links](/docs/persistence#typed-links) lists the rules.
|
|
28
|
+
|
|
29
|
+
## Validate the route's search
|
|
30
|
+
|
|
31
|
+
Pass the definition's `validateSearch` to the route that renders the grid:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
const employeesRoute = createRoute({
|
|
35
|
+
getParentRoute: () => rootRoute,
|
|
36
|
+
path: "/employees",
|
|
37
|
+
validateSearch: employeesLink.validateSearch,
|
|
38
|
+
component: EmployeesPage,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A route with search params of its own spreads it into its validator:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
validateSearch: (search) => ({
|
|
46
|
+
...employeesLink.validateSearch(search),
|
|
47
|
+
tab: search.tab === "chart" ? "chart" : "table",
|
|
48
|
+
}),
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Keep the grid's param in the router
|
|
52
|
+
|
|
53
|
+
Pass the router's search to the definition's `persist`:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const router = useRouter();
|
|
57
|
+
const persist = useMemo(
|
|
58
|
+
() =>
|
|
59
|
+
employeesLink.persist({
|
|
60
|
+
get: (name) => (router.state.location.search as Record<string, unknown>)[name],
|
|
61
|
+
set: (name, value) =>
|
|
62
|
+
void router.navigate({
|
|
63
|
+
to: ".",
|
|
64
|
+
search: (prev: Record<string, unknown>) => ({ ...prev, [name]: value }),
|
|
65
|
+
replace: true,
|
|
66
|
+
}),
|
|
67
|
+
}),
|
|
68
|
+
[router],
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The grid writes through `router.navigate`, so the router sees every change.
|
|
73
|
+
`replace: true` keeps a filter change out of the history, as the grid's own URL writes do.
|
|
74
|
+
With `createHashHistory()`, the param is written after the `#`, with the rest of the router's search.
|
|
75
|
+
|
|
76
|
+
## Link into the grid
|
|
77
|
+
|
|
78
|
+
Pass `search` to the link:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<Link
|
|
82
|
+
to="/employees"
|
|
83
|
+
search={employeesLink.search({
|
|
84
|
+
filters: {
|
|
85
|
+
salary: { operator: "between", value: [40_000, 60_000] },
|
|
86
|
+
status: { operator: "isAnyOf", value: ["Active"] },
|
|
87
|
+
},
|
|
88
|
+
sorting: [{ id: "salary", desc: true }],
|
|
89
|
+
})}
|
|
90
|
+
>
|
|
91
|
+
Active, highest salary first
|
|
92
|
+
</Link>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`search` fails to compile on a column the grid does not have, an operator the column does not offer, or a value of the wrong type.
|
|
96
|
+
The router then checks the result against the route's search.
|
|
97
|
+
|
|
98
|
+
## Readable URLs
|
|
99
|
+
|
|
100
|
+
By default, TanStack Router writes the param as JSON and escapes it with `URLSearchParams`, so every brace and quote is percent-escaped.
|
|
101
|
+
To write it readable, encode the values with [JSURL2](https://github.com/wmertens/jsurl2) and escape them with `encodeURIComponent`, which leaves JSURL2's `(`, `)`, `~` and `!` as they are:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { parse, stringify } from "jsurl2";
|
|
105
|
+
|
|
106
|
+
function toSearchValue(value: unknown): string {
|
|
107
|
+
if (typeof value === "object" && value !== null) return stringify(value);
|
|
108
|
+
if (typeof value !== "string") return String(value);
|
|
109
|
+
try {
|
|
110
|
+
parse(value);
|
|
111
|
+
return stringify(value);
|
|
112
|
+
} catch {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function stringifySearch(search: Record<string, unknown>): string {
|
|
118
|
+
const pairs = Object.entries(search).flatMap(([key, value]) =>
|
|
119
|
+
value === undefined
|
|
120
|
+
? []
|
|
121
|
+
: [`${encodeURIComponent(key)}=${encodeURIComponent(toSearchValue(value))}`],
|
|
122
|
+
);
|
|
123
|
+
return pairs.length === 0 ? "" : `?${pairs.join("&")}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const router = createRouter({
|
|
127
|
+
routeTree,
|
|
128
|
+
parseSearch: parseSearchWith(parse),
|
|
129
|
+
stringifySearch,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
TanStack Router's own `stringifySearchWith(stringify)` encodes the values as JSURL2 too, but its `URLSearchParams` escapes JSURL2's delimiters again.
|
|
134
|
+
|
|
135
|
+
```demo
|
|
136
|
+
file: recipes/ReadableSearch.tsx
|
|
137
|
+
hint: The same links as above. Compare the two addresses.
|
|
138
|
+
extraSources: data/employeeLinks.tsx, data/EmployeeLinksGrid.tsx
|
|
139
|
+
height: 600
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The serializer applies to every route, and the grid is unaffected.
|
|
143
|
+
JSURL2's `parse` also reads JSON, so a link written before the switch still opens.
|
|
144
|
+
|
|
145
|
+
## Links without a router
|
|
146
|
+
|
|
147
|
+
Without a router, leave out the storage: the grid reads and writes the browser's URL.
|
|
148
|
+
Build a link with `searchParams`:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
const persist = employeesLink.persist();
|
|
152
|
+
|
|
153
|
+
const href = `/employees?${employeesLink.searchParams({
|
|
154
|
+
filters: { department: { operator: "isAnyOf", value: ["Design"] } },
|
|
155
|
+
})}`;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```demo
|
|
159
|
+
file: recipes/PlainLinks.tsx
|
|
160
|
+
hint: Each link loads this page with the grid's param set.
|
|
161
|
+
extraSources: data/employeeLinks.tsx
|
|
162
|
+
height: 560
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Links to the page the grid is on
|
|
166
|
+
|
|
167
|
+
The grid reads its param once, when it mounts.
|
|
168
|
+
Note that a link to the route the grid is already on changes the URL but not the grid.
|
|
169
|
+
Link to such a route from another route, or remount the grid on the navigation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jielga/tmdatagrid",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "A React data grid built on TanStack Table v9 and Mantine - always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,8 +44,8 @@ The `labels` option takes a partial override, `TMDataGridLabelsOverride`, merged
|
|
|
44
44
|
| `filterFrom` | `From` | The lower bound of a `between` filter: its input, or its slider thumb. |
|
|
45
45
|
| `filterTo` | `To` | The upper bound of a `between` filter: its input, or its slider thumb. |
|
|
46
46
|
| `filterAll` | `All` | The no-filter segment of the tri-state filter. |
|
|
47
|
-
| `booleanTrue` | `Yes` | The `true` choice of a boolean filter value control and of the tri-state filter. |
|
|
48
|
-
| `booleanFalse` | `No` | The `false` choice of a boolean filter value control and of the tri-state filter. |
|
|
47
|
+
| `booleanTrue` | `Yes` | The `true` choice of a boolean filter value control and of the tri-state filter, and a `true` value on a filter pill and in a group row. |
|
|
48
|
+
| `booleanFalse` | `No` | The `false` choice of a boolean filter value control and of the tri-state filter, and a `false` value on a filter pill and in a group row. |
|
|
49
49
|
| `addFilter` | `Add filter` | Button in `TMDataGrid.FilterPanel`. |
|
|
50
50
|
| `clearAllFilters` | `Clear all` | Button in `TMDataGrid.FilterPanel` and in `TMDataGrid.FilterPills`. |
|
|
51
51
|
| `closeFilters` | `Close filters` | `aria-label` of the close button of the filter popup and sidebar. |
|
|
@@ -67,7 +67,7 @@ The `labels` option takes a partial override, `TMDataGridLabelsOverride`, merged
|
|
|
67
67
|
| `moveRight` | `Move right` | Column menu item. |
|
|
68
68
|
| `autosizeColumn` | `Autosize column` | Column menu item. |
|
|
69
69
|
| `hideColumn` | `Hide column` | Column menu item. |
|
|
70
|
-
| `filterOn` | `` `Filter on ${column}` `` | `aria-label` of the header filter button, of
|
|
70
|
+
| `filterOn` | `` `Filter on ${column}` `` | `aria-label` of the header filter button, of the header filter cell of a column that can filter, and of the value control in it. |
|
|
71
71
|
| `sortColumn` | `` `Sort ${column}` `` | `aria-label` of the header sort button. |
|
|
72
72
|
| `columnMenu` | `` `${column} column menu` `` | `aria-label` of the column menu trigger. |
|
|
73
73
|
| `rowsPerPage` | `Rows per page:` | `TMDataGrid.Footer`: the text before the page size select. |
|
|
@@ -14,7 +14,7 @@ description: >
|
|
|
14
14
|
metadata:
|
|
15
15
|
type: core
|
|
16
16
|
library: '@jielga/tmdatagrid'
|
|
17
|
-
library_version: '2.
|
|
17
|
+
library_version: '2.4.0'
|
|
18
18
|
sources:
|
|
19
19
|
- 'Jielga/react:packages/tmdatagrid/docs/cell-selection.md'
|
|
20
20
|
- 'Jielga/react:packages/tmdatagrid/src/core/cellNavigation.ts'
|
package/skills/columns/SKILL.md
CHANGED
|
@@ -16,7 +16,7 @@ description: >
|
|
|
16
16
|
metadata:
|
|
17
17
|
type: core
|
|
18
18
|
library: '@jielga/tmdatagrid'
|
|
19
|
-
library_version: '2.
|
|
19
|
+
library_version: '2.4.0'
|
|
20
20
|
sources:
|
|
21
21
|
- 'Jielga/react:packages/tmdatagrid/docs/columns.md'
|
|
22
22
|
- 'Jielga/react:packages/tmdatagrid/docs/column-layout.md'
|
|
@@ -61,6 +61,9 @@ replaces the dots: `accessorKey: "address.city"` makes column id
|
|
|
61
61
|
addresses the column by id - `initialState`, `editing.columns`, `moveColumn`,
|
|
62
62
|
a test selector - takes the underscore form.
|
|
63
63
|
|
|
64
|
+
Helper columns, groups included, carry their id, `meta.type`, value type, `meta.filter.operators` and filter, sort and URL switches in their type (`TMDataGridColumnInfo`).
|
|
65
|
+
`createTMDataGridUrlState` (the `options` skill) reads them to type deep links; only literal values count.
|
|
66
|
+
|
|
64
67
|
`columnHelper.group` nests columns under a shared header; `columns` takes
|
|
65
68
|
another `columnHelper.columns` call, not a bare array:
|
|
66
69
|
|
package/skills/data/SKILL.md
CHANGED
package/skills/editing/SKILL.md
CHANGED
|
@@ -18,7 +18,7 @@ description: >
|
|
|
18
18
|
metadata:
|
|
19
19
|
type: core
|
|
20
20
|
library: '@jielga/tmdatagrid'
|
|
21
|
-
library_version: '2.
|
|
21
|
+
library_version: '2.4.0'
|
|
22
22
|
sources:
|
|
23
23
|
- 'Jielga/react:packages/tmdatagrid/docs/filtering.md'
|
|
24
24
|
- 'Jielga/react:packages/tmdatagrid/docs/quick-search.md'
|
|
@@ -197,6 +197,7 @@ No props.
|
|
|
197
197
|
|
|
198
198
|
One pill per **active** filter, `First name: Sofia ✕`, where the ✕ clears it and a click on the label calls `openColumnFilter`.
|
|
199
199
|
The label spells the operator out unless it is the type's default: `Age is greater than 30`, but `First name: Sofia`.
|
|
200
|
+
A value with an option reads as the option's label, and a boolean value as `labels.booleanTrue` or `labels.booleanFalse`.
|
|
200
201
|
It takes the grid as an `api` prop instead of reading context, so it renders anywhere on the page.
|
|
201
202
|
|
|
202
203
|
```tsx
|
|
@@ -481,7 +482,7 @@ Source: `packages/tmdatagrid/docs/quick-search.md` (Fuzzy by default).
|
|
|
481
482
|
| `getOperatorsForType` | Export | `(type) => operators` | – | The operator list a type offers. |
|
|
482
483
|
| `getColumnOperators` · `getColumnDefaultOperator` | Exports | `(column) => operators` · `(column) => operator` | – | One column's list after `meta.filter.operators`, and the operator a fresh filter on it opens on. |
|
|
483
484
|
| `FILTER_OPERATOR_LABELS` | Export | record | – | The label shown for each operator. |
|
|
484
|
-
| `formatFilterLabel` | Export | `({ label, type, filter }) => string` | – | The one-line description used on the pills. |
|
|
485
|
+
| `formatFilterLabel` | Export | `({ label, type, filter, operatorLabels?, options?, labels? }) => string` | – | The one-line description used on the pills. `operatorLabels` names the operators, `options` gives a value its option's label, and `labels` gives a boolean its `booleanTrue` / `booleanFalse`; each defaults to English or to the value itself. |
|
|
485
486
|
| `emptyValueForOperator` · `operatorNeedsValue` · `operatorTakesArrayValue` · `operatorTakesRangeValue` | Exports | – | – | What shape of value an operator expects. |
|
|
486
487
|
| `TMDataGridFilterValueInput` | Export | component | – | The default value control, for falling back to. |
|
|
487
488
|
| `DgRangeSliderFilter` · `DgDateRangeFilter` · `DgAutocompleteFilter` · `DgTriStateFilter` | Exports | components | – | The four ready-made controls. |
|
|
@@ -13,7 +13,7 @@ description: >
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: core
|
|
15
15
|
library: '@jielga/tmdatagrid'
|
|
16
|
-
library_version: '2.
|
|
16
|
+
library_version: '2.4.0'
|
|
17
17
|
sources:
|
|
18
18
|
- 'Jielga/react:packages/tmdatagrid/docs/getting-started.md'
|
|
19
19
|
- 'Jielga/react:packages/tmdatagrid/docs/anatomy.md'
|
|
@@ -355,7 +355,7 @@ Source: `packages/tmdatagrid/docs/card-view.md`,
|
|
|
355
355
|
| `openColumnFilter(api, columnId)` | Adds an empty filter for a column and opens the panel. |
|
|
356
356
|
| `getColumnLabel(column)` | `meta.label`, a string header, or the column id. |
|
|
357
357
|
| `getColumnType(column)` | `meta.type`, defaulting to `"string"`. |
|
|
358
|
-
| `formatFilterLabel({ label, type, filter })` | The one-line filter description used on the pills. |
|
|
358
|
+
| `formatFilterLabel({ label, type, filter, operatorLabels?, options?, labels? })` | The one-line filter description used on the pills. |
|
|
359
359
|
| `moveColumn({ table, columnId, targetId, side })` | Moves a column next to another. See the `columns` skill. |
|
|
360
360
|
| `moveColumnByStep({ table, columnId, direction })` | Moves a column one position within its region. |
|
|
361
361
|
| `SELECT_COLUMN_ID` | Id of the generated checkbox column. |
|
package/skills/grouping/SKILL.md
CHANGED
|
@@ -14,7 +14,7 @@ description: >
|
|
|
14
14
|
metadata:
|
|
15
15
|
type: core
|
|
16
16
|
library: '@jielga/tmdatagrid'
|
|
17
|
-
library_version: '2.
|
|
17
|
+
library_version: '2.4.0'
|
|
18
18
|
sources:
|
|
19
19
|
- 'Jielga/react:packages/tmdatagrid/docs/grouping.md'
|
|
20
20
|
- 'Jielga/react:packages/tmdatagrid/docs/summary-row.md'
|
|
@@ -275,7 +275,7 @@ Source: `packages/tmdatagrid/docs/grouping.md` (Server-side grids).
|
|
|
275
275
|
| `aggregateColumn` | Export | `({ table, columnId, fn }) => unknown` | `fn: "sum"` | Aggregates over every filtered row, all pages. |
|
|
276
276
|
| `TMDataGridAggregationName` | Export | type | – | The registered function names. |
|
|
277
277
|
| `GROUP_COLUMN_ID` | Export | `"__group__"` | – | Id of the generated tree column. |
|
|
278
|
-
| `formatGroupValue` | Export | `(value) => string` | – | How the tree lane
|
|
278
|
+
| `formatGroupValue` | Export | `(value, blankLabel?, { type?, options?, labels? }?) => string` | – | How the tree lane writes a group's value: `blankLabel` for an empty value, an option's label for a value with an option, `labels.booleanTrue` / `booleanFalse` for a boolean, and `toLocaleDateString()` for a `Date`. |
|
|
279
279
|
| `getGroupDataRows` | Export | `(row) => Row[]` | – | Every record under a group row, at any depth. |
|
|
280
280
|
| `isPagingActive` | Export | `(table, features) => boolean` | – | Whether the pager is slicing anything. `false` while grouped. |
|
|
281
281
|
| `--dg-row-group-bg` | CSS variable | colour | Themed | Group row background. |
|
|
@@ -15,7 +15,7 @@ description: >
|
|
|
15
15
|
metadata:
|
|
16
16
|
type: lifecycle
|
|
17
17
|
library: '@jielga/tmdatagrid'
|
|
18
|
-
library_version: '2.
|
|
18
|
+
library_version: '2.4.0'
|
|
19
19
|
sources:
|
|
20
20
|
- 'Jielga/react:packages/tmdatagrid/docs/migrating-to-2.md'
|
|
21
21
|
- 'Jielga/react:packages/tmdatagrid/src/index.ts'
|
package/skills/options/SKILL.md
CHANGED
|
@@ -10,20 +10,22 @@ description: >
|
|
|
10
10
|
enablePagination, resetPageOnQueryChange, enableRowNumbers, selectionMode,
|
|
11
11
|
showSelectedBackground, cellSelection, renderDetails, overscan,
|
|
12
12
|
quickSearchMode), persist with dataKey/settingsKey slices, storageMode and
|
|
13
|
-
urlKey deep links, the returned api (table, ui,
|
|
14
|
-
resetSettings, scrollToRow), ui actions, and reading
|
|
15
|
-
useSelector over table.store, since table.getState() does not
|
|
16
|
-
Load when configuring the hook, looking up an option's default,
|
|
17
|
-
layout or filters, or reacting to grid state from a parent.
|
|
13
|
+
urlKey and createTMDataGridUrlState deep links, the returned api (table, ui,
|
|
14
|
+
edit, features, labels, resetSettings, scrollToRow), ui actions, and reading
|
|
15
|
+
grid state with useSelector over table.store, since table.getState() does not
|
|
16
|
+
exist in v9. Load when configuring the hook, looking up an option's default,
|
|
17
|
+
persisting layout or filters, or reacting to grid state from a parent.
|
|
18
18
|
metadata:
|
|
19
19
|
type: core
|
|
20
20
|
library: '@jielga/tmdatagrid'
|
|
21
|
-
library_version: '2.
|
|
21
|
+
library_version: '2.4.0'
|
|
22
22
|
sources:
|
|
23
23
|
- 'Jielga/react:packages/tmdatagrid/docs/use-tm-data-grid.md'
|
|
24
24
|
- 'Jielga/react:packages/tmdatagrid/docs/persistence.md'
|
|
25
|
+
- 'Jielga/react:packages/tmdatagrid/docs/typed-deep-links.md'
|
|
25
26
|
- 'Jielga/react:packages/tmdatagrid/src/useTMDataGrid.tsx'
|
|
26
27
|
- 'Jielga/react:packages/tmdatagrid/src/core/persistence.ts'
|
|
28
|
+
- 'Jielga/react:packages/tmdatagrid/src/core/urlState.ts'
|
|
27
29
|
---
|
|
28
30
|
|
|
29
31
|
# TMDataGrid - useTMDataGrid
|
|
@@ -298,6 +300,28 @@ const persist = useMemo(
|
|
|
298
300
|
|
|
299
301
|
Source: `packages/tmdatagrid/docs/persistence.md` (Deep links).
|
|
300
302
|
|
|
303
|
+
### Typed links
|
|
304
|
+
|
|
305
|
+
`createTMDataGridUrlState({ key, columns, slices? })` defines a grid's deep
|
|
306
|
+
links from the columns the grid renders, built with
|
|
307
|
+
`createTMDataGridColumnHelper`. A link can name only the filters, operators,
|
|
308
|
+
values and sorts those columns offer; anything else is a type error. The
|
|
309
|
+
definition gives `urlKey` for `persist`, the link as `searchParams`, `search`
|
|
310
|
+
or `payload`, a route `validateSearch`, and `persist(storage?)`, which keeps
|
|
311
|
+
the param in a router's search such as TanStack Router's.
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
export const employeesUrl = createTMDataGridUrlState({ key: "employees", columns });
|
|
315
|
+
|
|
316
|
+
const persist = { urlKey: employeesUrl.urlKey } satisfies TMDataGridPersistence;
|
|
317
|
+
const href = `/employees?${employeesUrl.searchParams({
|
|
318
|
+
filters: { department: { operator: "isAnyOf", value: ["Design"] } },
|
|
319
|
+
})}`;
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Every rule, every member and the TanStack Router setup are in
|
|
323
|
+
[references/typed-links.md](references/typed-links.md).
|
|
324
|
+
|
|
301
325
|
## Return value
|
|
302
326
|
|
|
303
327
|
| Field | Type | Description |
|