@kitr/table 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +14 -2
- package/dist/index.d.ts +97 -0
- package/dist/index.js +135 -0
- package/dist/kitr.d.ts +17 -0
- package/dist/kitr.js +19 -0
- package/docs/api.md +68 -0
- package/docs/index.md +864 -0
- package/docs/overview.md +90 -0
- package/package.json +65 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Platon Podkopov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kitr/table
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Tables.** Show rows of data with sorting, paging and filters that stay in the URL.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm kitr add table
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`kitr add` installs the package and wires it into your app: the provider in `kitrrc.ts`, its config file and its `AGENTS.md` section. Import it from `@kitr/table`.
|
|
10
|
+
|
|
11
|
+
Built on @tanstack/react-table.
|
|
12
|
+
|
|
13
|
+
Read the guide at https://kitr.dev/packages/table.
|
|
14
|
+
|
|
15
|
+
Part of [kitr](https://kitr.dev), the TypeScript framework where everything is an add-on. MIT licensed.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ColumnFiltersState, PaginationState, SortingState, Updater } from "@tanstack/react-table";
|
|
2
|
+
export * from "@tanstack/react-table";
|
|
3
|
+
/**
|
|
4
|
+
* The feature set kitr's DataTable and `useUrlTableState` assume: sorting,
|
|
5
|
+
* pagination, column filters and column visibility. TanStack Table v9 is feature-sliced, so a
|
|
6
|
+
* table only ships the code for the features you register. Extend with
|
|
7
|
+
* `tableFeatures({ ...dataTableFeatures, rowSelectionFeature })`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const dataTableFeatures: {
|
|
10
|
+
rowSortingFeature: import("@tanstack/react-table").TableFeature;
|
|
11
|
+
rowPaginationFeature: import("@tanstack/react-table").TableFeature;
|
|
12
|
+
columnFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
13
|
+
columnVisibilityFeature: import("@tanstack/react-table").TableFeature;
|
|
14
|
+
};
|
|
15
|
+
/** The type of `dataTableFeatures`, for `ColumnDef<DataTableFeatures, Order>` and `useTable` generics. */
|
|
16
|
+
export type DataTableFeatures = typeof dataTableFeatures;
|
|
17
|
+
/**
|
|
18
|
+
* `dataTableFeatures` plus the browser-side row models: for a list that arrives in one response,
|
|
19
|
+
* the table sorts, filters and pages it itself. Turn the manual flags off with it
|
|
20
|
+
* (`useUrlTableState({ ..., manual: false })` does), or nothing changes when a header is clicked.
|
|
21
|
+
*/
|
|
22
|
+
export declare const clientTableFeatures: {
|
|
23
|
+
sortedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
24
|
+
paginatedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
25
|
+
filteredRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
26
|
+
sortFns: {
|
|
27
|
+
alphanumeric: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
28
|
+
text: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
29
|
+
datetime: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
30
|
+
basic: import("@tanstack/react-table").CreatedSortFn<any, any>;
|
|
31
|
+
};
|
|
32
|
+
filterFns: {
|
|
33
|
+
includesString: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
34
|
+
equalsString: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
35
|
+
equals: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
36
|
+
weakEquals: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
37
|
+
inNumberRange: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
38
|
+
arrIncludes: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
39
|
+
arrIncludesAll: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
40
|
+
arrIncludesSome: import("@tanstack/react-table").CreatedFilterFn<any, any>;
|
|
41
|
+
};
|
|
42
|
+
rowSortingFeature: import("@tanstack/react-table").TableFeature;
|
|
43
|
+
rowPaginationFeature: import("@tanstack/react-table").TableFeature;
|
|
44
|
+
columnFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
45
|
+
columnVisibilityFeature: import("@tanstack/react-table").TableFeature;
|
|
46
|
+
};
|
|
47
|
+
/** The type of `clientTableFeatures`. */
|
|
48
|
+
export type ClientTableFeatures = typeof clientTableFeatures;
|
|
49
|
+
/** The search-param shape kitr tables use. Add it to your route's `validateSearch`. */
|
|
50
|
+
export interface TableSearch {
|
|
51
|
+
/** One-based page number. Absent means page 1. */
|
|
52
|
+
page?: number;
|
|
53
|
+
/** Rows per page. Absent means the default page size. */
|
|
54
|
+
size?: number;
|
|
55
|
+
/** Sort columns, comma-separated, `-` for descending: `-total,customer`. */
|
|
56
|
+
sort?: string;
|
|
57
|
+
/** Column filters as column id to string value: `{ status: 'open' }`. */
|
|
58
|
+
filter?: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/** Options for `useUrlTableState`: the route's current search, a way to change it and the page size. */
|
|
61
|
+
export interface UrlTableStateOptions {
|
|
62
|
+
/** The current table search params, usually `Route.useSearch()`. */
|
|
63
|
+
search: TableSearch;
|
|
64
|
+
/** Merge a patch into the URL, e.g. `(patch) => navigate({ search: (s) => ({ ...s, ...patch }) })`. */
|
|
65
|
+
setSearch: (patch: Partial<TableSearch>) => void;
|
|
66
|
+
/** Rows per page when the URL has no `size`, and the size left out of the URL. Default 20. */
|
|
67
|
+
defaultPageSize?: number;
|
|
68
|
+
/**
|
|
69
|
+
* `true` (default): the server sorts, filters and pages, the table shows what came back.
|
|
70
|
+
* `false`: the table does it in the browser; use it with `clientTableFeatures`.
|
|
71
|
+
*/
|
|
72
|
+
manual?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Bind sorting, pagination and column filters to URL search params so a
|
|
76
|
+
* filtered view is shareable by link. Router-agnostic; pass the route's search
|
|
77
|
+
* and a setter. Spread the result into `useTable` (with `dataTableFeatures` or a superset).
|
|
78
|
+
*/
|
|
79
|
+
export declare function useUrlTableState({ search, setSearch, defaultPageSize, manual }: UrlTableStateOptions): {
|
|
80
|
+
state: {
|
|
81
|
+
sorting: SortingState;
|
|
82
|
+
pagination: PaginationState;
|
|
83
|
+
columnFilters: ColumnFiltersState;
|
|
84
|
+
};
|
|
85
|
+
onSortingChange: (updater: Updater<SortingState>) => void;
|
|
86
|
+
onPaginationChange: (updater: Updater<PaginationState>) => void;
|
|
87
|
+
onColumnFiltersChange: (updater: Updater<ColumnFiltersState>) => void;
|
|
88
|
+
manualSorting: boolean;
|
|
89
|
+
manualPagination: boolean;
|
|
90
|
+
manualFiltering: boolean;
|
|
91
|
+
};
|
|
92
|
+
/** `-name,createdAt` ⇄ `[{ id: 'name', desc: true }, { id: 'createdAt', desc: false }]` */
|
|
93
|
+
export declare function parseSort(sort?: string): SortingState;
|
|
94
|
+
/** Turns TanStack's sorting state into the URL's `sort` string: `[{ id: 'total', desc: true }]` becomes `-total`. */
|
|
95
|
+
export declare function formatSort(sorting: SortingState): string;
|
|
96
|
+
/** Validate raw search params into `TableSearch`. Plug into `validateSearch` or wrap with zod. */
|
|
97
|
+
export declare function parseTableSearch(raw: Record<string, unknown>): TableSearch;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { columnFilteringFeature, columnVisibilityFeature, createFilteredRowModel, createPaginatedRowModel, createSortedRowModel, filterFn_arrIncludes, filterFn_arrIncludesAll, filterFn_arrIncludesSome, filterFn_equals, filterFn_equalsString, filterFn_inNumberRange, filterFn_includesString, filterFn_weakEquals, rowPaginationFeature, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures } from "@tanstack/react-table";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
export * from "@tanstack/react-table";
|
|
4
|
+
// src/features.ts
|
|
5
|
+
/**
|
|
6
|
+
* The feature set kitr's DataTable and `useUrlTableState` assume: sorting,
|
|
7
|
+
* pagination, column filters and column visibility. TanStack Table v9 is feature-sliced, so a
|
|
8
|
+
* table only ships the code for the features you register. Extend with
|
|
9
|
+
* `tableFeatures({ ...dataTableFeatures, rowSelectionFeature })`.
|
|
10
|
+
*/
|
|
11
|
+
const dataTableFeatures = tableFeatures({
|
|
12
|
+
rowSortingFeature,
|
|
13
|
+
rowPaginationFeature,
|
|
14
|
+
columnFilteringFeature,
|
|
15
|
+
columnVisibilityFeature
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* `dataTableFeatures` plus the browser-side row models: for a list that arrives in one response,
|
|
19
|
+
* the table sorts, filters and pages it itself. Turn the manual flags off with it
|
|
20
|
+
* (`useUrlTableState({ ..., manual: false })` does), or nothing changes when a header is clicked.
|
|
21
|
+
*/
|
|
22
|
+
const clientTableFeatures = tableFeatures({
|
|
23
|
+
...dataTableFeatures,
|
|
24
|
+
sortedRowModel: createSortedRowModel(),
|
|
25
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
26
|
+
filteredRowModel: createFilteredRowModel(),
|
|
27
|
+
sortFns: {
|
|
28
|
+
alphanumeric: sortFn_alphanumeric,
|
|
29
|
+
text: sortFn_text,
|
|
30
|
+
datetime: sortFn_datetime,
|
|
31
|
+
basic: sortFn_basic
|
|
32
|
+
},
|
|
33
|
+
filterFns: {
|
|
34
|
+
includesString: filterFn_includesString,
|
|
35
|
+
equalsString: filterFn_equalsString,
|
|
36
|
+
equals: filterFn_equals,
|
|
37
|
+
weakEquals: filterFn_weakEquals,
|
|
38
|
+
inNumberRange: filterFn_inNumberRange,
|
|
39
|
+
arrIncludes: filterFn_arrIncludes,
|
|
40
|
+
arrIncludesAll: filterFn_arrIncludesAll,
|
|
41
|
+
arrIncludesSome: filterFn_arrIncludesSome
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// src/url-state.ts
|
|
45
|
+
/**
|
|
46
|
+
* Bind sorting, pagination and column filters to URL search params so a
|
|
47
|
+
* filtered view is shareable by link. Router-agnostic; pass the route's search
|
|
48
|
+
* and a setter. Spread the result into `useTable` (with `dataTableFeatures` or a superset).
|
|
49
|
+
*/
|
|
50
|
+
function useUrlTableState({ search, setSearch, defaultPageSize = 20, manual = true }) {
|
|
51
|
+
const sorting = useMemo(() => parseSort(search.sort), [search.sort]);
|
|
52
|
+
const pagination = useMemo(() => ({
|
|
53
|
+
pageIndex: Math.max(0, (search.page ?? 1) - 1),
|
|
54
|
+
pageSize: search.size ?? defaultPageSize
|
|
55
|
+
}), [
|
|
56
|
+
search.page,
|
|
57
|
+
search.size,
|
|
58
|
+
defaultPageSize
|
|
59
|
+
]);
|
|
60
|
+
const columnFilters = useMemo(() => Object.entries(search.filter ?? {}).map(([id, value]) => ({
|
|
61
|
+
id,
|
|
62
|
+
value
|
|
63
|
+
})), [search.filter]);
|
|
64
|
+
const onSortingChange = useCallback((updater) => {
|
|
65
|
+
const next = typeof updater === "function" ? updater(sorting) : updater;
|
|
66
|
+
setSearch({
|
|
67
|
+
sort: next.length ? formatSort(next) : void 0,
|
|
68
|
+
page: void 0
|
|
69
|
+
});
|
|
70
|
+
}, [sorting, setSearch]);
|
|
71
|
+
const onPaginationChange = useCallback((updater) => {
|
|
72
|
+
const next = typeof updater === "function" ? updater(pagination) : updater;
|
|
73
|
+
setSearch({
|
|
74
|
+
page: next.pageIndex === 0 ? void 0 : next.pageIndex + 1,
|
|
75
|
+
size: next.pageSize === defaultPageSize ? void 0 : next.pageSize
|
|
76
|
+
});
|
|
77
|
+
}, [
|
|
78
|
+
pagination,
|
|
79
|
+
setSearch,
|
|
80
|
+
defaultPageSize
|
|
81
|
+
]);
|
|
82
|
+
const onColumnFiltersChange = useCallback((updater) => {
|
|
83
|
+
const next = typeof updater === "function" ? updater(columnFilters) : updater;
|
|
84
|
+
const filter = {};
|
|
85
|
+
for (const f of next) if (f.value !== void 0 && f.value !== "") filter[f.id] = String(f.value);
|
|
86
|
+
setSearch({
|
|
87
|
+
filter: Object.keys(filter).length ? filter : void 0,
|
|
88
|
+
page: void 0
|
|
89
|
+
});
|
|
90
|
+
}, [columnFilters, setSearch]);
|
|
91
|
+
return {
|
|
92
|
+
state: {
|
|
93
|
+
sorting,
|
|
94
|
+
pagination,
|
|
95
|
+
columnFilters
|
|
96
|
+
},
|
|
97
|
+
onSortingChange,
|
|
98
|
+
onPaginationChange,
|
|
99
|
+
onColumnFiltersChange,
|
|
100
|
+
manualSorting: manual,
|
|
101
|
+
manualPagination: manual,
|
|
102
|
+
manualFiltering: manual
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** `-name,createdAt` ⇄ `[{ id: 'name', desc: true }, { id: 'createdAt', desc: false }]` */
|
|
106
|
+
function parseSort(sort) {
|
|
107
|
+
if (!sort) return [];
|
|
108
|
+
return sort.split(",").filter(Boolean).map((part) => part.startsWith("-") ? {
|
|
109
|
+
id: part.slice(1),
|
|
110
|
+
desc: true
|
|
111
|
+
} : {
|
|
112
|
+
id: part,
|
|
113
|
+
desc: false
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/** Turns TanStack's sorting state into the URL's `sort` string: `[{ id: 'total', desc: true }]` becomes `-total`. */
|
|
117
|
+
function formatSort(sorting) {
|
|
118
|
+
return sorting.map((s) => s.desc ? `-${s.id}` : s.id).join(",");
|
|
119
|
+
}
|
|
120
|
+
/** Validate raw search params into `TableSearch`. Plug into `validateSearch` or wrap with zod. */
|
|
121
|
+
function parseTableSearch(raw) {
|
|
122
|
+
const out = {};
|
|
123
|
+
const page = Number(raw.page);
|
|
124
|
+
if (Number.isInteger(page) && page > 0) out.page = page;
|
|
125
|
+
const size = Number(raw.size);
|
|
126
|
+
if (Number.isInteger(size) && size > 0 && size <= 500) out.size = size;
|
|
127
|
+
if (typeof raw.sort === "string" && /^-?[\w.]+(,-?[\w.]+)*$/.test(raw.sort)) out.sort = raw.sort;
|
|
128
|
+
if (raw.filter && typeof raw.filter === "object") {
|
|
129
|
+
const filter = {};
|
|
130
|
+
for (const [k, v] of Object.entries(raw.filter)) if (typeof v === "string") filter[k] = v;
|
|
131
|
+
if (Object.keys(filter).length) out.filter = filter;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
export { clientTableFeatures, dataTableFeatures, formatSort, parseSort, parseTableSearch, useUrlTableState };
|
package/dist/kitr.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** `kitr add table`: TanStack Table with sorting, pagination and filters bound to URL search params. */
|
|
2
|
+
declare const _default: {
|
|
3
|
+
readonly name: "table";
|
|
4
|
+
readonly client: true;
|
|
5
|
+
readonly add: (ctx: import("@kitr/core/package").AddContext) => void;
|
|
6
|
+
readonly remove: (ctx: import("@kitr/core/package").AddContext) => void;
|
|
7
|
+
readonly wraps: {
|
|
8
|
+
readonly '@tanstack/react-table': "@kitr/table";
|
|
9
|
+
};
|
|
10
|
+
readonly eject: {
|
|
11
|
+
readonly '@kitr/table': "@tanstack/react-table";
|
|
12
|
+
};
|
|
13
|
+
readonly vite: {
|
|
14
|
+
readonly optimizeDeps: ["@tanstack/react-table"];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export { _default as default };
|
package/dist/kitr.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { definePackage } from "@kitr/core/package";
|
|
2
|
+
// src/kitr.ts
|
|
3
|
+
/** `kitr add table`: TanStack Table with sorting, pagination and filters bound to URL search params. */
|
|
4
|
+
var kitr_default = definePackage({
|
|
5
|
+
name: "table",
|
|
6
|
+
client: true,
|
|
7
|
+
add(ctx) {
|
|
8
|
+
ctx.deps.add("@tanstack/react-table");
|
|
9
|
+
ctx.agents.add("table", `- Tables: \`validateSearch: parseTableSearch\` on the route, \`useUrlTableState({ search, setSearch })\` spread into \`useReactTable\` or the registry \`<DataTable>\`. Sorting/paging live in the URL, so views are shareable.`);
|
|
10
|
+
},
|
|
11
|
+
remove(ctx) {
|
|
12
|
+
ctx.deps.remove("@tanstack/react-table");
|
|
13
|
+
ctx.agents.remove("table");
|
|
14
|
+
},
|
|
15
|
+
wraps: { "@tanstack/react-table": "@kitr/table" },
|
|
16
|
+
eject: { "@kitr/table": "@tanstack/react-table" },
|
|
17
|
+
vite: { optimizeDeps: ["@tanstack/react-table"] }
|
|
18
|
+
});
|
|
19
|
+
export { kitr_default as default };
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API reference
|
|
3
|
+
description: Every export of @kitr/table.
|
|
4
|
+
order: 2
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# API reference
|
|
8
|
+
|
|
9
|
+
`@kitr/table` re-exports everything from `@tanstack/react-table` v9 and adds the following.
|
|
10
|
+
|
|
11
|
+
## useUrlTableState
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
function useUrlTableState(options: {
|
|
15
|
+
search: TableSearch
|
|
16
|
+
setSearch: (patch: Partial<TableSearch>) => void
|
|
17
|
+
defaultPageSize?: number // default 20
|
|
18
|
+
}): {
|
|
19
|
+
state: { sorting; pagination; columnFilters }
|
|
20
|
+
onSortingChange
|
|
21
|
+
onPaginationChange
|
|
22
|
+
onColumnFiltersChange
|
|
23
|
+
manualSorting: true
|
|
24
|
+
manualPagination: true
|
|
25
|
+
manualFiltering: true
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Spread the result into `useTable` or `<DataTable>`. Router-agnostic: any router that can read and patch search params works.
|
|
30
|
+
|
|
31
|
+
## TableSearch
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
interface TableSearch {
|
|
35
|
+
page?: number // one-based
|
|
36
|
+
size?: number
|
|
37
|
+
sort?: string // "name,-createdAt"
|
|
38
|
+
filter?: Record<string, string>
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## parseTableSearch
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
function parseTableSearch(raw: Record<string, unknown>): TableSearch
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For a route's `validateSearch`. Keeps positive integer pages, sizes up to 500, well-formed sort strings and string filters; drops everything else.
|
|
49
|
+
|
|
50
|
+
## parseSort and formatSort
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
parseSort('-name,createdAt') // [{ id: 'name', desc: true }, { id: 'createdAt', desc: false }]
|
|
54
|
+
formatSort([{ id: 'name', desc: true }]) // '-name'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## dataTableFeatures
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const dataTableFeatures = tableFeatures({
|
|
61
|
+
rowSortingFeature,
|
|
62
|
+
rowPaginationFeature,
|
|
63
|
+
columnFilteringFeature,
|
|
64
|
+
columnVisibilityFeature,
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The features `<DataTable>` and `useUrlTableState` assume. TanStack Table v9 only bundles the features you register. Add more with `tableFeatures({ ...dataTableFeatures, rowSelectionFeature })`.
|