tanstack-url-state 0.1.1 → 0.2.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/README.md +20 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/schema.d.ts +29 -0
- package/dist/schema.js +33 -0
- package/dist/search-params.d.ts +0 -2
- package/dist/search-params.js +1 -14
- package/dist/use-search-state.js +1 -1
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -54,8 +54,28 @@ Options, per hook or per call:
|
|
|
54
54
|
| `scroll` | `false` | Scroll to the top after the update. |
|
|
55
55
|
| `debounce` | `0` | Milliseconds to wait before writing the URL; the state updates immediately. |
|
|
56
56
|
|
|
57
|
+
## Lists: pagination and sort
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { pagination, searchParams, sortParam } from "tanstack-url-state";
|
|
61
|
+
|
|
62
|
+
export const Route = createFileRoute("/videos")({
|
|
63
|
+
...searchParams({
|
|
64
|
+
...pagination(), // page (default 1), perPage (default 20, at most 100)
|
|
65
|
+
sort: sortParam(["title", "publishDate"], "-publishDate"), // "title" | "-title" | …
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- `pagination({ perPage, maxPerPage })` gives `page` and `perPage` with defaults (kept out of the URL) and a fallback for junk such as `?page=abc`.
|
|
71
|
+
- `sortParam(sorts, fallback?)` accepts `field` and `-field` (descending) for every field in `sorts`, which may list bare or signed names; anything else reads as `fallback`, or as no sort.
|
|
72
|
+
|
|
57
73
|
`searchDefaults(schema)` returns the defaulted keys of a zod object schema, if you need them elsewhere.
|
|
58
74
|
|
|
75
|
+
## Bundle size
|
|
76
|
+
|
|
77
|
+
ESM only, one module per concern, `"sideEffects": false`: you ship what you import. The schema helpers (`pagination`, `sortParam`, `searchDefaults`) need only `zod`; `searchParams` adds the router's `stripSearchParams`; the hooks need React and the router and never pull the route helpers. A test bundles each group to keep it that way.
|
|
78
|
+
|
|
59
79
|
## License
|
|
60
80
|
|
|
61
81
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export { searchDefaults,
|
|
1
|
+
export { pagination, searchDefaults, sortParam } from "./schema.js";
|
|
2
|
+
export type { PaginationOptions, SortValue } from "./schema.js";
|
|
3
|
+
export { searchParams } from "./search-params.js";
|
|
2
4
|
export { useSearchState, useSearchStates } from "./use-search-state.js";
|
|
3
5
|
export type { SearchRoute, SearchStateOptions } from "./use-search-state.js";
|
package/dist/index.js
CHANGED
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Values a route search schema falls back to when a key is missing from the URL. */
|
|
3
|
+
export declare function searchDefaults(schema: unknown): Record<string, unknown>;
|
|
4
|
+
export interface PaginationOptions {
|
|
5
|
+
/** Default page size, kept out of the URL; default 20. */
|
|
6
|
+
perPage?: number;
|
|
7
|
+
/** Largest page size a URL may ask for; default 100. */
|
|
8
|
+
maxPerPage?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* `page` (1-based) and `perPage` for a list route: spread into `searchParams({ ... })`.
|
|
12
|
+
* Both have defaults (so they stay out of the URL) and fall back to them on junk input.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pagination({ perPage, maxPerPage }?: PaginationOptions): {
|
|
15
|
+
page: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
16
|
+
perPage: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
17
|
+
};
|
|
18
|
+
/** `field` and `-field` for every field, whichever form `sorts` lists them in. */
|
|
19
|
+
export type SortValue<S extends string> = S extends `-${infer F}` ? S | F : S | `-${S}`;
|
|
20
|
+
/**
|
|
21
|
+
* `sort=field` / `sort=-field` (descending). Unknown fields fall back to `fallback`, or to no
|
|
22
|
+
* sort when there is none. `sorts` may list bare fields, signed ones, or both.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sortParam<const S extends string>(sorts: readonly S[]): z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
25
|
+
[K in SortValue<S>]: K;
|
|
26
|
+
}>>>;
|
|
27
|
+
export declare function sortParam<const S extends string>(sorts: readonly S[], fallback: SortValue<S>): z.ZodCatch<z.ZodDefault<z.ZodEnum<{
|
|
28
|
+
[K in SortValue<S>]: K;
|
|
29
|
+
}>>>;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// zod-only building blocks; no router or React import, so they bundle on their own.
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const defaultsCache = new WeakMap();
|
|
4
|
+
/** Values a route search schema falls back to when a key is missing from the URL. */
|
|
5
|
+
export function searchDefaults(schema) {
|
|
6
|
+
if (!(schema instanceof z.ZodObject)) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
let defaults = defaultsCache.get(schema);
|
|
10
|
+
if (!defaults) {
|
|
11
|
+
// zod 4 `.partial()` keeps `.default()`s, so this yields exactly the defaulted keys.
|
|
12
|
+
defaults = Object.fromEntries(Object.entries(schema.partial().parse({})).filter(([, value]) => value !== undefined));
|
|
13
|
+
defaultsCache.set(schema, defaults);
|
|
14
|
+
}
|
|
15
|
+
return defaults;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `page` (1-based) and `perPage` for a list route: spread into `searchParams({ ... })`.
|
|
19
|
+
* Both have defaults (so they stay out of the URL) and fall back to them on junk input.
|
|
20
|
+
*/
|
|
21
|
+
export function pagination({ perPage = 20, maxPerPage = 100 } = {}) {
|
|
22
|
+
return {
|
|
23
|
+
page: z.number().int().positive().default(1).catch(1),
|
|
24
|
+
perPage: z.number().int().min(1).max(maxPerPage).default(perPage).catch(perPage),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function sortParam(sorts, fallback) {
|
|
28
|
+
const fields = [...new Set(sorts.map((sort) => sort.replace(/^-/, "")))];
|
|
29
|
+
const sort = z.enum(fields.flatMap((field) => [field, `-${field}`]));
|
|
30
|
+
return fallback === undefined
|
|
31
|
+
? sort.optional().catch(undefined)
|
|
32
|
+
: sort.default(fallback).catch(fallback);
|
|
33
|
+
}
|
package/dist/search-params.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
/** Values a route search schema falls back to when a key is missing from the URL. */
|
|
3
|
-
export declare function searchDefaults(schema: unknown): Record<string, unknown>;
|
|
4
2
|
/**
|
|
5
3
|
* Route search options from a zod shape: `validateSearch` (parsing + types) and a middleware
|
|
6
4
|
* that drops values equal to their default from the URL. Spread into `createFileRoute(...)({ ... })`.
|
package/dist/search-params.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import { stripSearchParams } from "@tanstack/react-router";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
/** Values a route search schema falls back to when a key is missing from the URL. */
|
|
5
|
-
export function searchDefaults(schema) {
|
|
6
|
-
if (!(schema instanceof z.ZodObject)) {
|
|
7
|
-
return {};
|
|
8
|
-
}
|
|
9
|
-
let defaults = defaultsCache.get(schema);
|
|
10
|
-
if (!defaults) {
|
|
11
|
-
// zod 4 `.partial()` keeps `.default()`s, so this yields exactly the defaulted keys.
|
|
12
|
-
defaults = Object.fromEntries(Object.entries(schema.partial().parse({})).filter(([, value]) => value !== undefined));
|
|
13
|
-
defaultsCache.set(schema, defaults);
|
|
14
|
-
}
|
|
15
|
-
return defaults;
|
|
16
|
-
}
|
|
3
|
+
import { searchDefaults } from "./schema.js";
|
|
17
4
|
/**
|
|
18
5
|
* Route search options from a zod shape: `validateSearch` (parsing + types) and a middleware
|
|
19
6
|
* that drops values equal to their default from the URL. Spread into `createFileRoute(...)({ ... })`.
|
package/dist/use-search-state.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useRouter, useSearch } from "@tanstack/react-router";
|
|
2
2
|
import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
-
import { searchDefaults } from "./
|
|
3
|
+
import { searchDefaults } from "./schema.js";
|
|
4
4
|
// One queue per app: search params are global to the URL. Only written by client events, never during SSR.
|
|
5
5
|
const EMPTY = {};
|
|
6
6
|
let pending = EMPTY;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tanstack-url-state",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "nuqs-style URL search state for TanStack Router, typed by each route's validateSearch",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,14 +8,22 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/B0redDev/tanstack-url-state.git"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
11
|
+
"keywords": [
|
|
12
|
+
"tanstack-router",
|
|
13
|
+
"search-params",
|
|
14
|
+
"url-state",
|
|
15
|
+
"react",
|
|
16
|
+
"zod"
|
|
17
|
+
],
|
|
12
18
|
"exports": {
|
|
13
19
|
".": {
|
|
14
20
|
"types": "./dist/index.d.ts",
|
|
15
21
|
"import": "./dist/index.js"
|
|
16
22
|
}
|
|
17
23
|
},
|
|
18
|
-
"files": [
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
19
27
|
"sideEffects": false,
|
|
20
28
|
"scripts": {
|
|
21
29
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|