@djangocfg/ui-core 2.1.293 → 2.1.294
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 +50 -3
- package/package.json +16 -4
- package/src/hooks/index.ts +67 -0
- package/src/hooks/router/README.md +119 -0
- package/src/hooks/router/adapter.tsx +139 -0
- package/src/hooks/router/adapters/index.ts +5 -0
- package/src/hooks/router/adapters/nextjs.tsx +100 -0
- package/src/hooks/router/index.ts +90 -0
- package/src/hooks/router/parsers.ts +154 -0
- package/src/hooks/router/useBackOrFallback.ts +145 -0
- package/src/hooks/router/useIsActive.ts +60 -0
- package/src/hooks/router/useLocation.ts +163 -0
- package/src/hooks/router/useNavigate.ts +96 -0
- package/src/hooks/router/useQueryParams.ts +262 -0
- package/src/hooks/router/useQueryState.ts +106 -0
- package/src/hooks/router/useRouter.ts +81 -0
- package/src/hooks/router/useSmartLink.ts +157 -0
- package/src/hooks/router/useUrlBuilder.ts +118 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useUrlBuilder — pure URL/querystring assembly.
|
|
5
|
+
*
|
|
6
|
+
* WHY:
|
|
7
|
+
* Building URLs by hand (template literals + URLSearchParams) is fiddly:
|
|
8
|
+
* you have to remember to skip empty values, encode keys, handle arrays.
|
|
9
|
+
* This hook centralizes those rules so callers stay declarative.
|
|
10
|
+
* Zero side effects — only React-bound thing is `useCallback` for ref
|
|
11
|
+
* stability inside JSX.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const { build, withCurrentParams } = useUrlBuilder();
|
|
15
|
+
* build('/products', { page: 2, tag: ['a', 'b'], q: '' });
|
|
16
|
+
* // '/products?page=2&tag=a&tag=b'
|
|
17
|
+
* withCurrentParams('/products', { page: 1 });
|
|
18
|
+
* // keeps everything in current ?…, overrides `page`
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useCallback, useMemo } from 'react';
|
|
22
|
+
import { useLocation } from './useLocation';
|
|
23
|
+
|
|
24
|
+
/** Value types accepted by `build`. Empty/null/undefined are stripped. */
|
|
25
|
+
export type QueryValue = string | number | boolean | null | undefined;
|
|
26
|
+
/** Param map. Arrays become repeated keys (`?tag=a&tag=b`), not csv. */
|
|
27
|
+
export type QueryParamsInput = Record<string, QueryValue | QueryValue[]>;
|
|
28
|
+
|
|
29
|
+
function appendValue(
|
|
30
|
+
params: URLSearchParams,
|
|
31
|
+
key: string,
|
|
32
|
+
value: QueryValue
|
|
33
|
+
): void {
|
|
34
|
+
if (value === null || value === undefined) return;
|
|
35
|
+
if (typeof value === 'string' && value === '') return;
|
|
36
|
+
// Booleans serialize as 'true'/'false' — common-sense default.
|
|
37
|
+
params.append(key, String(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a query-string fragment (no leading `?`) from a param map.
|
|
42
|
+
* Skips empty / null / undefined values; arrays become repeated keys.
|
|
43
|
+
* Exported standalone so utilities can share the same rules without a hook.
|
|
44
|
+
*/
|
|
45
|
+
export function buildQueryString(params?: QueryParamsInput): string {
|
|
46
|
+
if (!params) return '';
|
|
47
|
+
const search = new URLSearchParams();
|
|
48
|
+
for (const key of Object.keys(params)) {
|
|
49
|
+
const value = params[key];
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
for (const item of value) appendValue(search, key, item);
|
|
52
|
+
} else {
|
|
53
|
+
appendValue(search, key, value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return search.toString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build a full path: `path + '?' + qs` (or just `path` if qs is empty).
|
|
61
|
+
* Pure — useful outside React (link generators, server code).
|
|
62
|
+
*/
|
|
63
|
+
export function buildUrl(path: string, params?: QueryParamsInput): string {
|
|
64
|
+
const qs = buildQueryString(params);
|
|
65
|
+
return qs ? `${path}?${qs}` : path;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface UseUrlBuilderReturn {
|
|
69
|
+
/** Assemble `path` + serialized params. */
|
|
70
|
+
build: (path: string, params?: QueryParamsInput) => string;
|
|
71
|
+
/**
|
|
72
|
+
* Assemble `path` keeping the current page's querystring,
|
|
73
|
+
* with `overrides` merged on top. Pass `null`/`undefined`/`''` in
|
|
74
|
+
* `overrides` to drop a key.
|
|
75
|
+
*/
|
|
76
|
+
withCurrentParams: (
|
|
77
|
+
path: string,
|
|
78
|
+
overrides?: QueryParamsInput
|
|
79
|
+
) => string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Stable URL builder helpers. `withCurrentParams` re-renders when the
|
|
84
|
+
* current querystring changes (it reads `useLocation` internally).
|
|
85
|
+
*/
|
|
86
|
+
export function useUrlBuilder(): UseUrlBuilderReturn {
|
|
87
|
+
const { search } = useLocation();
|
|
88
|
+
|
|
89
|
+
const build = useCallback((path: string, params?: QueryParamsInput): string => {
|
|
90
|
+
return buildUrl(path, params);
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const withCurrentParams = useCallback(
|
|
94
|
+
(path: string, overrides?: QueryParamsInput): string => {
|
|
95
|
+
const next = new URLSearchParams(search);
|
|
96
|
+
if (overrides) {
|
|
97
|
+
for (const key of Object.keys(overrides)) {
|
|
98
|
+
const value = overrides[key];
|
|
99
|
+
// Overrides semantics: drop on empty, replace otherwise.
|
|
100
|
+
next.delete(key);
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
for (const item of value) appendValue(next, key, item);
|
|
103
|
+
} else {
|
|
104
|
+
appendValue(next, key, value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const qs = next.toString();
|
|
109
|
+
return qs ? `${path}?${qs}` : path;
|
|
110
|
+
},
|
|
111
|
+
[search]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return useMemo<UseUrlBuilderReturn>(
|
|
115
|
+
() => ({ build, withCurrentParams }),
|
|
116
|
+
[build, withCurrentParams]
|
|
117
|
+
);
|
|
118
|
+
}
|