@jk2908/solas 0.5.3 → 0.6.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +81 -85
  3. package/dist/config.d.ts +25 -0
  4. package/dist/config.js +152 -0
  5. package/dist/events.d.ts +4 -0
  6. package/dist/events.js +5 -0
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.js +59 -37
  9. package/dist/internal/boundary-error.d.ts +4 -0
  10. package/dist/internal/boundary-error.js +8 -0
  11. package/dist/internal/browser-router/history.d.ts +46 -0
  12. package/dist/internal/browser-router/history.js +131 -0
  13. package/dist/internal/browser-router/link.d.ts +1 -1
  14. package/dist/internal/browser-router/link.js +1 -1
  15. package/dist/internal/browser-router/router.d.ts +6 -14
  16. package/dist/internal/browser-router/router.js +39 -13
  17. package/dist/internal/browser-router/shared.d.ts +183 -164
  18. package/dist/internal/browser-router/shared.js +49 -56
  19. package/dist/internal/browser-router/use-router.d.ts +1 -10
  20. package/dist/internal/browser-router/use-search-params.js +3 -3
  21. package/dist/internal/build.d.ts +108 -110
  22. package/dist/internal/build.js +569 -581
  23. package/dist/internal/codegen/config.js +3 -3
  24. package/dist/internal/codegen/environments.js +5 -6
  25. package/dist/internal/codegen/format.d.ts +6 -0
  26. package/dist/internal/codegen/format.js +24 -0
  27. package/dist/internal/codegen/manifest.js +2 -2
  28. package/dist/internal/codegen/maps.d.ts +1 -1
  29. package/dist/internal/codegen/maps.js +2 -2
  30. package/dist/internal/codegen/types.js +4 -4
  31. package/dist/internal/codegen/utils.js +2 -2
  32. package/dist/internal/env/request-context.js +1 -1
  33. package/dist/internal/env/rsc.d.ts +6 -4
  34. package/dist/internal/env/rsc.js +27 -25
  35. package/dist/internal/env/ssr.js +4 -4
  36. package/dist/internal/http-router/router.js +8 -8
  37. package/dist/internal/metadata.d.ts +1 -1
  38. package/dist/internal/metadata.js +1 -1
  39. package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
  40. package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
  41. package/dist/internal/navigation/redirect.js +6 -6
  42. package/dist/internal/postbuild.js +18 -17
  43. package/dist/internal/prerender.d.ts +130 -138
  44. package/dist/internal/prerender.js +326 -354
  45. package/dist/internal/public-files.js +2 -4
  46. package/dist/internal/render/head.js +2 -2
  47. package/dist/internal/resolver.js +1 -1
  48. package/dist/internal/server/actions.js +2 -2
  49. package/dist/internal/ui/error-boundary.d.ts +1 -1
  50. package/dist/manifest.d.ts +7 -0
  51. package/dist/manifest.js +85 -0
  52. package/dist/prerender.d.ts +1 -1
  53. package/dist/prerender.js +1 -1
  54. package/dist/solas.d.ts +2 -46
  55. package/dist/solas.js +6 -266
  56. package/dist/types.d.ts +14 -21
  57. package/dist/types.js +1 -1
  58. package/dist/utils/base-path.d.ts +12 -14
  59. package/dist/utils/base-path.js +75 -82
  60. package/dist/utils/compress.d.ts +9 -11
  61. package/dist/utils/compress.js +59 -64
  62. package/dist/utils/context.d.ts +4 -6
  63. package/dist/utils/context.js +17 -22
  64. package/dist/utils/logger.js +2 -2
  65. package/dist/utils/time.d.ts +2 -4
  66. package/dist/utils/time.js +23 -29
  67. package/package.json +18 -8
  68. package/dist/runtimes/bun.d.ts +0 -13
  69. package/dist/runtimes/bun.js +0 -39
@@ -1,170 +1,189 @@
1
- import { Solas } from '../../solas.js';
2
- export declare namespace BrowserRouter {
3
- export type Params = Record<string, string>;
4
- export type Query = Record<string, string | number | boolean>;
5
- export type Path = keyof Solas.Routes & string;
6
- type Replace = {
7
- replace?: boolean;
1
+ import * as Solas from '../../solas.js';
2
+ export type Params = Record<string, string>;
3
+ export type Query = Record<string, string | number | boolean>;
4
+ export type Path = keyof Solas.Routes & string;
5
+ type Replace = {
6
+ replace?: boolean;
7
+ };
8
+ export type GoOptions = {
9
+ replace?: boolean;
10
+ query?: Query;
11
+ params?: Params;
12
+ };
13
+ export type Context = {
14
+ go: Go;
15
+ prefetch: (path: string) => void;
16
+ refresh: Refresh;
17
+ isNavigating: boolean;
18
+ url: {
19
+ pathname: string;
20
+ search?: string;
21
+ hash?: string;
8
22
  };
9
- export type GoOptions = {
10
- replace?: boolean;
11
- query?: Query;
12
- params?: Params;
23
+ history: {
24
+ entries: HistoryEntry[];
25
+ index: number;
13
26
  };
14
- /**
15
- * These targets are used as-is. They are not matched against the route table,
16
- * so this covers normal external URLs and hash-only links
17
- */
18
- export type ExternalTarget = `${string}:${string}` | `//${string}` | `#${string}`;
19
- export function isHashOnlyTarget(target: string): boolean;
20
- export function isExternalTarget(target: string, origin: string): boolean;
21
- /**
22
- * Turn a route pattern into the real path shape a caller can use. In practice,
23
- * every ':param' or '*' part becomes a plain string slot
24
- *
25
- * @example
26
- * ```ts
27
- * // '/p/:id' becomes '/p/${string}'
28
- * // '/test/*' becomes '/test/${string}'
29
- * // '/posts' stays '/posts'
30
- * ```
31
- *
32
- * @example
33
- * ```ts
34
- * type A = ResolvedPath<'/posts/:id'>
35
- * // '/posts/${string}'
36
- * ```
37
- *
38
- * @example
39
- * ```ts
40
- * type B = ResolvedPath<'/docs/*'>
41
- * // '/docs/${string}'
42
- * ```
43
- */
44
- export type ResolvedPath<TPath extends string> = TPath extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${string}/${ResolvedPath<Rest>}` : TPath extends `${infer Start}:${string}` ? `${Start}${string}` : TPath extends `${infer Start}*${infer Rest}` ? `${Start}${string}${ResolvedPath<Rest>}` : TPath;
45
- /**
46
- * Once we have a real path, also allow the usual query-string and hash forms
47
- *
48
- * @example
49
- * ```ts
50
- * type A = TargetSuffix<'/posts/123'>
51
- * // '/posts/123' | '/posts/123?${string}' | '/posts/123#${string}' | '/posts/123?${string}#${string}'
52
- * ```
53
- */
54
- export type TargetSuffix<TPath extends string> = TPath | `${TPath}?${string}` | `${TPath}#${string}` | `${TPath}?${string}#${string}`;
55
- /**
56
- * This is the final string form a caller can navigate to. It can be an external
57
- * URL, or a concrete URL that matches one of the known routes
58
- *
59
- * @example
60
- * ```ts
61
- * type A = Target
62
- * // 'https://example.com'
63
- * // '#intro'
64
- * // '/posts/123'
65
- * // '/posts/123?draft=true'
66
- * ```
67
- */
68
- export type Target = ExternalTarget | TargetSuffix<ResolvedPath<Path>>;
69
- /**
70
- * Extra options for callers who already have a finished target string
71
- *
72
- * @example
73
- * ```ts
74
- * const a: TargetConfig = { query: { page: 2 } }
75
- * // params is rejected here because the path is already complete
76
- * ```
77
- */
78
- type TargetConfig = {
79
- params?: never;
80
- query?: Query;
81
- };
82
- /**
83
- * Extra options for callers who pass a route pattern and params separately.
84
- * If the route definition says that route needs params, this type makes
85
- * those params required. If the route has no params, it rejects them
86
- *
87
- * @example
88
- * ```ts
89
- * // if Solas.Routes['/posts/:id'] is { params: { id: string } }
90
- * type A = PatternConfig<'/posts/:id'>
91
- * // { query?: Query } & { params: { id: string } }
92
- * ```
93
- *
94
- * @example
95
- * ```ts
96
- * // if Solas.Routes['/about'] has no params field
97
- * type B = PatternConfig<'/about'>
98
- * // { query?: Query } & { params?: never }
99
- * ```
100
- */
101
- type PatternConfig<TPath extends Path> = {
102
- query?: Query;
103
- } & (Solas.Routes[TPath] extends {
104
- params: infer TParams extends Params;
105
- } ? {
106
- params: TParams;
107
- } : {
108
- params?: never;
109
- });
110
- /**
111
- * Typed <Link /> props, using `href` instead of the internal `to` name
112
- *
113
- * `query` is always allowed
114
- * `params` are only allowed when `href` is a known route pattern
115
- *
116
- * @example
117
- * ```ts
118
- * const a: LinkProps = { href: '/posts/:id', params: { id: '123' } }
119
- * const b: LinkProps = { href: '/posts/123?draft=true' }
120
- * ```
121
- */
122
- export type LinkProps = ({
123
- href: Target;
124
- } & TargetConfig) | (keyof Solas.Routes extends never ? never : {
125
- [TPath in Path]: {
126
- href: TPath;
127
- } & PatternConfig<TPath>;
128
- }[Path]);
129
- /**
130
- * Typed input for router.go(), using the same route rules as <Link />
131
- *
132
- * @example
133
- * ```ts
134
- * go('/p/post-2')
135
- * go('/?foo=bar', { replace: true })
136
- * ```
137
- *
138
- * @example
139
- * ```ts
140
- * go('/p/:id', { params: { id: 'post-2' }, replace: true })
141
- * ```
142
- *
143
- * The last overload is the fallback for plain `string` values. The
144
- * `string extends TTo` check stops that fallback from taking over
145
- * when TypeScript already knows the caller passed a more specific
146
- * string literal
147
- *
148
- * @example
149
- * ```ts
150
- * declare const dynamicPath: string
151
- * go(dynamicPath, { replace: true })
152
- * ```
153
- */
154
- export type Go = {
155
- <TTo extends Path>(to: TTo, opts?: PatternConfig<TTo> & Replace): Promise<string>;
156
- <TTo extends Target>(to: TTo, opts?: TargetConfig & Replace): Promise<string>;
157
- <TTo extends string>(to: string extends TTo ? TTo : never, opts?: GoOptions): Promise<string>;
158
- };
159
- export type Refresh = () => Promise<string>;
160
- /**
161
- * Convert a route pattern and params into a real path string. This is used internally
162
- * to implement <Link /> and router.go
163
- */
164
- export function toTarget(path: string, params?: Record<string, string>, query?: Query): string;
165
- export {};
166
- }
27
+ };
28
+ export type HistoryEntry = {
29
+ pathname: string;
30
+ search: string;
31
+ hash: string;
32
+ };
33
+ /**
34
+ * These targets are used as-is. They are not matched against the route table,
35
+ * so this covers normal external URLs and hash-only links
36
+ */
37
+ export type ExternalTarget = `${string}:${string}` | `//${string}` | `#${string}`;
38
+ export declare function isHashOnlyTarget(target: string): boolean;
39
+ export declare function isExternalTarget(target: string, origin: string): boolean;
40
+ /**
41
+ * Turn a route pattern into the real path shape a caller can use. In practice,
42
+ * every ':param' or '*' part becomes a plain string slot
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // '/p/:id' becomes '/p/${string}'
47
+ * // '/test/*' becomes '/test/${string}'
48
+ * // '/posts' stays '/posts'
49
+ * ```
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * type A = ResolvedPath<'/posts/:id'>
54
+ * // '/posts/${string}'
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * type B = ResolvedPath<'/docs/*'>
60
+ * // '/docs/${string}'
61
+ * ```
62
+ */
63
+ export type ResolvedPath<TPath extends string> = TPath extends `${infer Start}:${string}/${infer Rest}` ? `${Start}${string}/${ResolvedPath<Rest>}` : TPath extends `${infer Start}:${string}` ? `${Start}${string}` : TPath extends `${infer Start}*${infer Rest}` ? `${Start}${string}${ResolvedPath<Rest>}` : TPath;
64
+ /**
65
+ * Once we have a real path, also allow the usual query-string and hash forms
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * type A = TargetSuffix<'/posts/123'>
70
+ * // '/posts/123' | '/posts/123?${string}' | '/posts/123#${string}' | '/posts/123?${string}#${string}'
71
+ * ```
72
+ */
73
+ export type TargetSuffix<TPath extends string> = TPath | `${TPath}?${string}` | `${TPath}#${string}` | `${TPath}?${string}#${string}`;
74
+ /**
75
+ * This is the final string form a caller can navigate to. It can be an external
76
+ * URL, or a concrete URL that matches one of the known routes
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * type A = Target
81
+ * // 'https://example.com'
82
+ * // '#intro'
83
+ * // '/posts/123'
84
+ * // '/posts/123?draft=true'
85
+ * ```
86
+ */
87
+ export type Target = ExternalTarget | TargetSuffix<ResolvedPath<Path>>;
88
+ /**
89
+ * Extra options for callers who already have a finished target string
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const a: TargetConfig = { query: { page: 2 } }
94
+ * // params is rejected here because the path is already complete
95
+ * ```
96
+ */
97
+ type TargetConfig = {
98
+ params?: never;
99
+ query?: Query;
100
+ };
101
+ /**
102
+ * Extra options for callers who pass a route pattern and params separately.
103
+ * If the route definition says that route needs params, this type makes
104
+ * those params required. If the route has no params, it rejects them
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // if Solas.Routes['/posts/:id'] is { params: { id: string } }
109
+ * type A = PatternConfig<'/posts/:id'>
110
+ * // { query?: Query } & { params: { id: string } }
111
+ * ```
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * // if Solas.Routes['/about'] has no params field
116
+ * type B = PatternConfig<'/about'>
117
+ * // { query?: Query } & { params?: never }
118
+ * ```
119
+ */
120
+ type PatternConfig<TPath extends Path> = {
121
+ query?: Query;
122
+ } & (Solas.Routes[TPath] extends {
123
+ params: infer TParams extends Params;
124
+ } ? {
125
+ params: TParams;
126
+ } : {
127
+ params?: never;
128
+ });
129
+ /**
130
+ * Typed <Link /> props, using `href` instead of the internal `to` name
131
+ *
132
+ * `query` is always allowed
133
+ * `params` are only allowed when `href` is a known route pattern
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const a: LinkProps = { href: '/posts/:id', params: { id: '123' } }
138
+ * const b: LinkProps = { href: '/posts/123?draft=true' }
139
+ * ```
140
+ */
141
+ export type LinkProps = ({
142
+ href: Target;
143
+ } & TargetConfig) | (keyof Solas.Routes extends never ? never : {
144
+ [TPath in Path]: {
145
+ href: TPath;
146
+ } & PatternConfig<TPath>;
147
+ }[Path]);
148
+ /**
149
+ * Typed input for router.go(), using the same route rules as <Link />
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * go('/p/post-2')
154
+ * go('/?foo=bar', { replace: true })
155
+ * ```
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * go('/p/:id', { params: { id: 'post-2' }, replace: true })
160
+ * ```
161
+ *
162
+ * The last overload is the fallback for plain `string` values. The
163
+ * `string extends TTo` check stops that fallback from taking over
164
+ * when TypeScript already knows the caller passed a more specific
165
+ * string literal
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * declare const dynamicPath: string
170
+ * go(dynamicPath, { replace: true })
171
+ * ```
172
+ */
173
+ export type Go = {
174
+ (to: number): Promise<string>;
175
+ <TTo extends Path>(to: TTo, opts?: PatternConfig<TTo> & Replace): Promise<string>;
176
+ <TTo extends Target>(to: TTo, opts?: TargetConfig & Replace): Promise<string>;
177
+ <TTo extends string>(to: string extends TTo ? TTo : never, opts?: GoOptions): Promise<string>;
178
+ };
179
+ export type Refresh = () => Promise<string>;
180
+ /**
181
+ * Convert a route pattern and params into a real path string. This is used internally
182
+ * to implement <Link /> and router.go
183
+ */
184
+ export declare function toTarget(path: string, params?: Record<string, string>, query?: Query): string;
167
185
  /**
168
186
  * Apply the base path to a target string when needed
169
187
  */
170
188
  export declare function withBase(target: string): string;
189
+ export {};
@@ -1,72 +1,65 @@
1
- import { BasePath } from '../../utils/base-path.js';
2
- const BASE_PATH = BasePath.normalise(import.meta.env.BASE_URL);
3
- export { BrowserRouter };
4
- var BrowserRouter;
5
- (function (BrowserRouter) {
6
- function isHashOnlyTarget(target) {
7
- return target.startsWith('#');
1
+ import { applyBasePath, normaliseBasePath } from '../../utils/base-path.js';
2
+ const BASE_PATH = normaliseBasePath(import.meta.env.BASE_URL);
3
+ export function isHashOnlyTarget(target) {
4
+ return target.startsWith('#');
5
+ }
6
+ export function isExternalTarget(target, origin) {
7
+ if (isHashOnlyTarget(target))
8
+ return false;
9
+ try {
10
+ return new URL(target, origin).origin !== origin;
8
11
  }
9
- BrowserRouter.isHashOnlyTarget = isHashOnlyTarget;
10
- function isExternalTarget(target, origin) {
11
- if (isHashOnlyTarget(target))
12
- return false;
13
- try {
14
- return new URL(target, origin).origin !== origin;
15
- }
16
- catch {
17
- return false;
18
- }
12
+ catch {
13
+ return false;
19
14
  }
20
- BrowserRouter.isExternalTarget = isExternalTarget;
21
- /**
22
- * Convert a route pattern and params into a real path string. This is used internally
23
- * to implement <Link /> and router.go
24
- */
25
- function toTarget(path, params, query) {
26
- const used = new Set();
27
- // first, replace all the :param parts with the corresponding params
28
- let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
29
- const value = params?.[key];
30
- if (value == null) {
31
- throw new Error(`[Link]: missing route param: ${key}`);
32
- }
33
- used.add(key);
34
- return encodeURIComponent(value);
35
- });
36
- if (to.includes('*')) {
37
- const remaining = Object.entries(params ?? {}).filter(([key]) => !used.has(key));
38
- if (remaining.length !== 1) {
39
- throw new Error('[Link]: wildcard routes require exactly one unmatched param');
40
- }
41
- to = to.replace('*', remaining[0][1].split('/').map(encodeURIComponent).join('/'));
15
+ }
16
+ /**
17
+ * Convert a route pattern and params into a real path string. This is used internally
18
+ * to implement <Link /> and router.go
19
+ */
20
+ export function toTarget(path, params, query) {
21
+ const used = new Set();
22
+ // first, replace all the :param parts with the corresponding params
23
+ let to = path.replaceAll(/:([A-Za-z0-9_]+)/g, (_, key) => {
24
+ const value = params?.[key];
25
+ if (value == null) {
26
+ throw new Error(`[Link]: missing route param: ${key}`);
42
27
  }
43
- if (!query)
44
- return withBase(to);
45
- const hashIndex = to.indexOf('#');
46
- const hash = hashIndex >= 0 ? to.slice(hashIndex) : '';
47
- const pathWithSearch = hashIndex >= 0 ? to.slice(0, hashIndex) : to;
48
- const searchIndex = pathWithSearch.indexOf('?');
49
- const pathname = searchIndex >= 0 ? pathWithSearch.slice(0, searchIndex) : pathWithSearch;
50
- const currentSearch = searchIndex >= 0 ? pathWithSearch.slice(searchIndex + 1) : '';
51
- const search = new URLSearchParams(currentSearch);
52
- for (const [key, value] of Object.entries(query)) {
53
- search.set(key, String(value));
28
+ used.add(key);
29
+ return encodeURIComponent(value);
30
+ });
31
+ if (to.includes('*')) {
32
+ const remaining = Object.entries(params ?? {}).filter(([key]) => !used.has(key));
33
+ if (remaining.length !== 1) {
34
+ throw new Error('[Link]: wildcard routes require exactly one unmatched param');
54
35
  }
55
- const value = search.toString();
56
- return withBase(`${pathname}${value.length > 0 ? `?${value}` : ''}${hash}`);
36
+ to = to.replace('*', remaining[0][1].split('/').map(encodeURIComponent).join('/'));
57
37
  }
58
- BrowserRouter.toTarget = toTarget;
59
- })(BrowserRouter || (BrowserRouter = {}));
38
+ if (!query)
39
+ return withBase(to);
40
+ const hashIndex = to.indexOf('#');
41
+ const hash = hashIndex >= 0 ? to.slice(hashIndex) : '';
42
+ const pathWithSearch = hashIndex >= 0 ? to.slice(0, hashIndex) : to;
43
+ const searchIndex = pathWithSearch.indexOf('?');
44
+ const pathname = searchIndex >= 0 ? pathWithSearch.slice(0, searchIndex) : pathWithSearch;
45
+ const currentSearch = searchIndex >= 0 ? pathWithSearch.slice(searchIndex + 1) : '';
46
+ const search = new URLSearchParams(currentSearch);
47
+ for (const [key, value] of Object.entries(query)) {
48
+ search.set(key, String(value));
49
+ }
50
+ const value = search.toString();
51
+ return withBase(`${pathname}${value.length > 0 ? `?${value}` : ''}${hash}`);
52
+ }
60
53
  /**
61
54
  * Apply the base path to a target string when needed
62
55
  */
63
56
  export function withBase(target) {
64
- if (BrowserRouter.isHashOnlyTarget(target))
57
+ if (isHashOnlyTarget(target))
65
58
  return target;
66
59
  if (target.startsWith('//') || /^[A-Za-z][A-Za-z\d+.-]*:/.test(target))
67
60
  return target;
68
61
  const suffixIndex = target.search(/[?#]/);
69
62
  const pathname = suffixIndex === -1 ? target : target.slice(0, suffixIndex);
70
63
  const suffix = suffixIndex === -1 ? '' : target.slice(suffixIndex);
71
- return `${BasePath.apply(pathname || '/', BASE_PATH)}${suffix}`;
64
+ return `${applyBasePath(pathname || '/', BASE_PATH)}${suffix}`;
72
65
  }
@@ -1,10 +1 @@
1
- export declare function useRouter(): {
2
- go: import("./shared.js").BrowserRouter.Go;
3
- prefetch: (path: string) => void;
4
- refresh: import("./shared.js").BrowserRouter.Refresh;
5
- isNavigating: boolean;
6
- url: {
7
- pathname?: string | undefined;
8
- search?: string | undefined;
9
- };
10
- };
1
+ export declare function useRouter(): import("./shared.js").Context;
@@ -1,14 +1,14 @@
1
1
  import { useMemo, useSyncExternalStore } from 'react';
2
- import { Solas } from '../../solas.js';
2
+ import * as Events from '../../events.js';
3
3
  import { useRouter } from '../browser-router/use-router.js';
4
4
  export function useSearchParams() {
5
5
  const { url } = useRouter();
6
6
  const search = useSyncExternalStore(fn => {
7
7
  window.addEventListener('popstate', fn);
8
- window.addEventListener(Solas.Events.names.NAVIGATION, fn);
8
+ window.addEventListener(Events.names.NAVIGATION, fn);
9
9
  return () => {
10
10
  window.removeEventListener('popstate', fn);
11
- window.removeEventListener(Solas.Events.names.NAVIGATION, fn);
11
+ window.removeEventListener(Events.names.NAVIGATION, fn);
12
12
  };
13
13
  }, () => window.location.search, () => url?.search);
14
14
  return useMemo(() => new URLSearchParams(search), [search]);