olova 2.0.61 → 2.0.63

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 (80) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +42 -61
  3. package/dist/compiler.d.ts +44 -0
  4. package/dist/compiler.js +2139 -0
  5. package/dist/compiler.js.map +1 -0
  6. package/dist/core.d.ts +4 -0
  7. package/dist/core.js +859 -0
  8. package/dist/core.js.map +1 -0
  9. package/dist/global.d.ts +15 -0
  10. package/dist/global.js +226 -0
  11. package/dist/global.js.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +2302 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/runtime.d.ts +89 -0
  16. package/dist/runtime.js +633 -0
  17. package/dist/runtime.js.map +1 -0
  18. package/dist/signals-core-BdfWh1Yt.d.ts +43 -0
  19. package/dist/vite.d.ts +5 -0
  20. package/dist/vite.js +2302 -0
  21. package/dist/vite.js.map +1 -0
  22. package/package.json +83 -65
  23. package/dist/chunk-D7SIC5TC.js +0 -367
  24. package/dist/chunk-D7SIC5TC.js.map +0 -1
  25. package/dist/entry-server.cjs +0 -120
  26. package/dist/entry-server.cjs.map +0 -1
  27. package/dist/entry-server.js +0 -115
  28. package/dist/entry-server.js.map +0 -1
  29. package/dist/entry-worker.cjs +0 -133
  30. package/dist/entry-worker.cjs.map +0 -1
  31. package/dist/entry-worker.js +0 -127
  32. package/dist/entry-worker.js.map +0 -1
  33. package/dist/main.cjs +0 -18
  34. package/dist/main.cjs.map +0 -1
  35. package/dist/main.js +0 -16
  36. package/dist/main.js.map +0 -1
  37. package/dist/olova.cjs +0 -1680
  38. package/dist/olova.cjs.map +0 -1
  39. package/dist/olova.d.cts +0 -72
  40. package/dist/olova.d.ts +0 -72
  41. package/dist/olova.js +0 -1321
  42. package/dist/olova.js.map +0 -1
  43. package/dist/performance.cjs +0 -386
  44. package/dist/performance.cjs.map +0 -1
  45. package/dist/performance.js +0 -3
  46. package/dist/performance.js.map +0 -1
  47. package/dist/router.cjs +0 -646
  48. package/dist/router.cjs.map +0 -1
  49. package/dist/router.d.cts +0 -113
  50. package/dist/router.d.ts +0 -113
  51. package/dist/router.js +0 -632
  52. package/dist/router.js.map +0 -1
  53. package/main.tsx +0 -76
  54. package/olova.ts +0 -619
  55. package/src/entry-server.tsx +0 -165
  56. package/src/entry-worker.tsx +0 -201
  57. package/src/generator/index.ts +0 -409
  58. package/src/hydration/flight.ts +0 -320
  59. package/src/hydration/index.ts +0 -12
  60. package/src/hydration/types.ts +0 -225
  61. package/src/logger.ts +0 -182
  62. package/src/main.tsx +0 -24
  63. package/src/performance.ts +0 -488
  64. package/src/plugin/index.ts +0 -204
  65. package/src/router/ErrorBoundary.tsx +0 -145
  66. package/src/router/Link.tsx +0 -117
  67. package/src/router/OlovaRouter.tsx +0 -354
  68. package/src/router/Outlet.tsx +0 -8
  69. package/src/router/context.ts +0 -117
  70. package/src/router/index.ts +0 -29
  71. package/src/router/matching.ts +0 -63
  72. package/src/router/router.tsx +0 -23
  73. package/src/router/search-params.ts +0 -29
  74. package/src/scanner/index.ts +0 -114
  75. package/src/types/index.ts +0 -190
  76. package/src/utils/export.ts +0 -85
  77. package/src/utils/index.ts +0 -4
  78. package/src/utils/naming.ts +0 -54
  79. package/src/utils/path.ts +0 -45
  80. package/tsup.config.ts +0 -35
@@ -1,354 +0,0 @@
1
- import { Suspense, useCallback, useEffect, useMemo, useRef, useState, useTransition, type ComponentType, type ReactNode } from 'react';
2
- import type { LayoutRoute, Metadata, NavigateOptions, NotFoundPageConfig, Route, SearchParams, SetSearchParamsOptions } from '../types';
3
- import { OutletContext, RouterContext } from './context';
4
- import { ErrorBoundary, RouteErrorBoundary } from './ErrorBoundary';
5
- import { findNotFoundPage, matchLayoutScope, matchRoute } from './matching';
6
- import { buildSearchString, parseSearchParams } from './search-params';
7
-
8
- function normalizePath(path: string): string {
9
- if (path === '/') return path;
10
- return path.replace(/\/+$/, '') || '/';
11
- }
12
-
13
- interface OlovaRouterProps {
14
- routes: Route[];
15
- layouts?: LayoutRoute[];
16
- notFoundPages?: NotFoundPageConfig[];
17
- notFound?: ReactNode;
18
- loadingFallback?: ReactNode;
19
- initialPath?: string;
20
- onNavigate?: (from: string, to: string) => void;
21
- onBeforeNavigate?: (from: string, to: string) => boolean | void;
22
- }
23
-
24
- export function OlovaRouter({
25
- routes,
26
- layouts = [],
27
- notFoundPages = [],
28
- notFound = <div>404 - Not Found</div>,
29
- loadingFallback = <div>Loading...</div>,
30
- initialPath,
31
- onNavigate,
32
- onBeforeNavigate,
33
- }: OlovaRouterProps) {
34
- const isSSR = typeof window === 'undefined';
35
- const [currentPath, setCurrentPath] = useState(() => normalizePath(initialPath || (isSSR ? '/' : window.location.pathname)));
36
- const [searchParams, setSearchParamsState] = useState<SearchParams>(() =>
37
- isSSR ? {} : parseSearchParams(window.location.search)
38
- );
39
- const [isPending, startTransition] = useTransition();
40
- const prevPathRef = useRef(currentPath);
41
-
42
- const [defaultTitle] = useState(() => isSSR ? '' : document.title);
43
-
44
- useEffect(() => {
45
- if (isSSR) return;
46
- const onPopState = () => {
47
- const newPath = normalizePath(window.location.pathname);
48
- startTransition(() => {
49
- setCurrentPath(newPath);
50
- setSearchParamsState(parseSearchParams(window.location.search));
51
- });
52
- };
53
- window.addEventListener('popstate', onPopState);
54
- return () => window.removeEventListener('popstate', onPopState);
55
- }, [isSSR]);
56
-
57
- const performNavigation = useCallback((path: string, options: NavigateOptions = {}, historyMethod: 'push' | 'replace' = 'push') => {
58
- if (isSSR) return;
59
- const { scroll = true } = options;
60
-
61
- const normalizedPath = normalizePath(path.split('?')[0].split('#')[0]);
62
- const hash = path.includes('#') ? '#' + path.split('#')[1] : '';
63
- const search = path.includes('?') ? '?' + path.split('?')[1].split('#')[0] : '';
64
- const fullUrl = normalizedPath + search + hash;
65
-
66
- if (onBeforeNavigate) {
67
- const result = onBeforeNavigate(currentPath, normalizedPath);
68
- if (result === false) return;
69
- }
70
-
71
- const prevPath = currentPath;
72
-
73
- if (historyMethod === 'replace') {
74
- window.history.replaceState({}, '', fullUrl);
75
- } else {
76
- window.history.pushState({}, '', fullUrl);
77
- }
78
-
79
- startTransition(() => {
80
- setCurrentPath(normalizedPath);
81
- setSearchParamsState(parseSearchParams(search));
82
- });
83
-
84
- if (scroll) {
85
- if (hash) {
86
- const el = document.getElementById(hash.slice(1));
87
- if (el) {
88
- el.scrollIntoView({ behavior: 'smooth' });
89
- } else {
90
- window.scrollTo(0, 0);
91
- }
92
- } else {
93
- window.scrollTo(0, 0);
94
- }
95
- }
96
-
97
- if (onNavigate) {
98
- onNavigate(prevPath, normalizedPath);
99
- }
100
- }, [isSSR, currentPath, onBeforeNavigate, onNavigate]);
101
-
102
- const navigate = useCallback((path: string, options?: NavigateOptions) => {
103
- performNavigation(path, options, options?.replace ? 'replace' : 'push');
104
- }, [performNavigation]);
105
-
106
- const push = useCallback((path: string, options?: NavigateOptions) => {
107
- performNavigation(path, options, 'push');
108
- }, [performNavigation]);
109
-
110
- const replace = useCallback((path: string, options?: NavigateOptions) => {
111
- performNavigation(path, options, 'replace');
112
- }, [performNavigation]);
113
-
114
- const back = useCallback(() => {
115
- if (!isSSR) window.history.back();
116
- }, [isSSR]);
117
-
118
- const forward = useCallback(() => {
119
- if (!isSSR) window.history.forward();
120
- }, [isSSR]);
121
-
122
- const refresh = useCallback(() => {
123
- if (!isSSR) {
124
- startTransition(() => {
125
- setCurrentPath(prev => prev);
126
- });
127
- }
128
- }, [isSSR]);
129
-
130
- const prefetch = useCallback((path: string) => {
131
- // Trigger lazy component import by finding the matching route
132
- const normalizedPath = normalizePath(path.split('?')[0]);
133
- const pathParts = normalizedPath.split('/').filter(Boolean);
134
- for (const route of routes) {
135
- const segments = route.path.split('/').filter(Boolean);
136
- const result = matchRoute(segments, pathParts);
137
- if (result.match && route.component) {
138
- // Accessing the component triggers React.lazy to load the chunk
139
- break;
140
- }
141
- }
142
- }, [routes]);
143
-
144
- const setSearchParams = useCallback((
145
- newParams: Record<string, string | string[] | null>,
146
- options: SetSearchParamsOptions = {}
147
- ) => {
148
- if (isSSR) return;
149
- const { replace: shouldReplace = false, merge = false } = options;
150
-
151
- let finalParams: Record<string, string | string[] | null>;
152
-
153
- if (merge) {
154
- finalParams = { ...searchParams, ...newParams };
155
- for (const key of Object.keys(finalParams)) {
156
- if (finalParams[key] === null) {
157
- delete finalParams[key];
158
- }
159
- }
160
- } else {
161
- finalParams = newParams;
162
- }
163
-
164
- const searchString = buildSearchString(finalParams as Record<string, string | string[] | null>);
165
- const newUrl = currentPath + searchString;
166
-
167
- if (shouldReplace) {
168
- window.history.replaceState({}, '', newUrl);
169
- } else {
170
- window.history.pushState({}, '', newUrl);
171
- }
172
-
173
- setSearchParamsState(parseSearchParams(searchString));
174
- }, [isSSR, searchParams, currentPath]);
175
-
176
- const sortedRoutes = useMemo(() => {
177
- return [...routes]
178
- .sort((a, b) => {
179
- const aHasCatchAll = a.path.includes('*');
180
- const bHasCatchAll = b.path.includes('*');
181
- const aHasDynamic = a.path.includes(':');
182
- const bHasDynamic = b.path.includes(':');
183
-
184
- if (aHasCatchAll && !bHasCatchAll) return 1;
185
- if (!aHasCatchAll && bHasCatchAll) return -1;
186
- if (aHasDynamic && !bHasDynamic) return 1;
187
- if (!aHasDynamic && bHasDynamic) return -1;
188
- return b.path.length - a.path.length;
189
- })
190
- .map(route => ({
191
- ...route,
192
- segments: route.path.split('/').filter(Boolean)
193
- }));
194
- }, [routes]);
195
-
196
- const applyMetadata = useCallback((metadata: Metadata | undefined) => {
197
- if (isSSR) return;
198
- if (metadata?.title) {
199
- document.title = metadata.title;
200
- } else {
201
- document.title = defaultTitle;
202
- }
203
-
204
- const setMetaTag = (name: string, content: string | undefined, attr = 'name') => {
205
- let meta = document.querySelector(`meta[${attr}="${name}"]`);
206
- if (content) {
207
- if (!meta) {
208
- meta = document.createElement('meta');
209
- meta.setAttribute(attr, name);
210
- document.head.appendChild(meta);
211
- }
212
- meta.setAttribute('content', content);
213
- } else if (meta) {
214
- document.head.removeChild(meta);
215
- }
216
- };
217
-
218
- setMetaTag('description', metadata?.description);
219
- setMetaTag('keywords', metadata?.keywords
220
- ? (Array.isArray(metadata.keywords) ? metadata.keywords.join(', ') : metadata.keywords)
221
- : undefined
222
- );
223
-
224
- // Open Graph tags
225
- if (metadata?.openGraph) {
226
- setMetaTag('og:title', metadata.openGraph.title || metadata.title, 'property');
227
- setMetaTag('og:description', metadata.openGraph.description || metadata.description, 'property');
228
- setMetaTag('og:image', metadata.openGraph.image, 'property');
229
- setMetaTag('og:url', metadata.openGraph.url, 'property');
230
- setMetaTag('og:type', metadata.openGraph.type || 'website', 'property');
231
- }
232
-
233
- // Canonical URL
234
- let canonical = document.querySelector('link[rel="canonical"]') as HTMLLinkElement | null;
235
- if (metadata?.openGraph?.url) {
236
- if (!canonical) {
237
- canonical = document.createElement('link');
238
- canonical.setAttribute('rel', 'canonical');
239
- document.head.appendChild(canonical);
240
- }
241
- canonical.setAttribute('href', metadata.openGraph.url);
242
- }
243
- }, [defaultTitle, isSSR]);
244
-
245
- const { currentRoute, MatchedComponent, params } = useMemo(() => {
246
- const pathParts = currentPath.split('/').filter(Boolean);
247
-
248
- for (const route of sortedRoutes) {
249
- if (route.path === '/' && currentPath === '/') {
250
- return {
251
- currentRoute: route,
252
- MatchedComponent: route.component || null,
253
- params: {} as Record<string, string>
254
- };
255
- }
256
-
257
- const result = matchRoute(route.segments, pathParts);
258
- if (result.match) {
259
- return {
260
- currentRoute: route,
261
- MatchedComponent: route.component || null,
262
- params: result.params
263
- };
264
- }
265
- }
266
- return {
267
- currentRoute: null as Route | null,
268
- MatchedComponent: null as ComponentType | null,
269
- params: {} as Record<string, string>
270
- };
271
- }, [sortedRoutes, currentPath]);
272
-
273
- // Track path changes for onNavigate callback
274
- useEffect(() => {
275
- prevPathRef.current = currentPath;
276
- }, [currentPath]);
277
-
278
- useEffect(() => {
279
- if (isSSR) return;
280
- applyMetadata(currentRoute?.metadata);
281
- }, [currentRoute, applyMetadata, isSSR]);
282
-
283
- const matchingLayouts = useMemo(() => {
284
- return layouts
285
- .filter(layout => matchLayoutScope(layout.path, currentPath))
286
- .sort((a, b) => a.path.length - b.path.length);
287
- }, [layouts, currentPath]);
288
-
289
- const FinalComponent = useMemo(() => {
290
- if (MatchedComponent) return MatchedComponent;
291
- return findNotFoundPage(currentPath, notFoundPages);
292
- }, [MatchedComponent, currentPath, notFoundPages]);
293
-
294
- const content = useMemo(() => {
295
- const LoadingComponent = currentRoute?.loading;
296
- const ErrorComponent = currentRoute?.error;
297
-
298
- let result: ReactNode = FinalComponent ? <FinalComponent /> : notFound;
299
-
300
- // Only wrap with Suspense if route has explicit loading component
301
- // (eager imports don't need Suspense fallback)
302
- if (LoadingComponent) {
303
- result = (
304
- <Suspense fallback={<LoadingComponent />}>
305
- {result}
306
- </Suspense>
307
- );
308
- }
309
-
310
- // Wrap with per-route error boundary (error.tsx)
311
- if (ErrorComponent) {
312
- result = (
313
- <RouteErrorBoundary fallbackComponent={ErrorComponent} routePath={currentPath}>
314
- {result}
315
- </RouteErrorBoundary>
316
- );
317
- }
318
-
319
- for (let i = matchingLayouts.length - 1; i >= 0; i--) {
320
- const Layout = matchingLayouts[i].layout;
321
- if (!Layout) continue;
322
-
323
- const wrapped = result;
324
- result = (
325
- <OutletContext.Provider value={{ content: wrapped }}>
326
- <Layout />
327
- </OutletContext.Provider>
328
- );
329
- }
330
-
331
- return result;
332
- }, [FinalComponent, matchingLayouts, notFound, loadingFallback, currentRoute, currentPath]);
333
-
334
- return (
335
- <ErrorBoundary>
336
- <RouterContext.Provider value={{
337
- currentPath,
338
- params,
339
- searchParams,
340
- navigate,
341
- push,
342
- replace,
343
- back,
344
- forward,
345
- refresh,
346
- setSearchParams,
347
- isNavigating: isPending,
348
- prefetch,
349
- }}>
350
- {content}
351
- </RouterContext.Provider>
352
- </ErrorBoundary>
353
- );
354
- }
@@ -1,8 +0,0 @@
1
- import { useContext } from 'react';
2
- import { OutletContext } from './context';
3
-
4
- export function Outlet() {
5
- const context = useContext(OutletContext);
6
- if (!context) return null;
7
- return <>{context.content}</>;
8
- }
@@ -1,117 +0,0 @@
1
- import { createContext, useContext, useMemo, useEffect, useRef, useCallback } from 'react';
2
- import type { RouterContextType, OutletContextType, NavigateOptions } from '../types';
3
-
4
- export const RouterContext = createContext<RouterContextType | null>(null);
5
-
6
- export const OutletContext = createContext<OutletContextType | null>(null);
7
-
8
- const ssrRouter: RouterContextType = {
9
- currentPath: '/',
10
- params: {},
11
- searchParams: {},
12
- navigate: () => {},
13
- push: () => {},
14
- replace: () => {},
15
- back: () => {},
16
- forward: () => {},
17
- refresh: () => {},
18
- setSearchParams: () => {},
19
- isNavigating: false,
20
- prefetch: () => {},
21
- };
22
-
23
- export function useRouter() {
24
- const context = useContext(RouterContext);
25
- if (!context) {
26
- if (typeof window === 'undefined') return ssrRouter;
27
- throw new Error('useRouter must be used within OlovaRouter');
28
- }
29
- return context;
30
- }
31
-
32
- export function useParams<T extends Record<string, string> = Record<string, string>>(): T {
33
- const context = useContext(RouterContext);
34
- return (context?.params || {}) as T;
35
- }
36
-
37
- export function useSearchParams(): URLSearchParams {
38
- const context = useContext(RouterContext);
39
- const searchParams = context?.searchParams || {};
40
-
41
- return useMemo(() => {
42
- const params = new URLSearchParams();
43
- Object.entries(searchParams).forEach(([key, value]) => {
44
- if (Array.isArray(value)) {
45
- value.forEach(v => params.append(key, v));
46
- } else {
47
- params.set(key, value);
48
- }
49
- });
50
- return params;
51
- }, [searchParams]);
52
- }
53
-
54
- export function usePathname(): string {
55
- const context = useContext(RouterContext);
56
- if (!context) {
57
- if (typeof window === 'undefined') return '/';
58
- throw new Error('usePathname must be used within OlovaRouter');
59
- }
60
- return context.currentPath;
61
- }
62
-
63
- export function useSelectedLayoutSegment(): string | null {
64
- const context = useContext(RouterContext);
65
- if (!context) return null;
66
- const segments = context.currentPath.split('/').filter(Boolean);
67
- return segments[0] || null;
68
- }
69
-
70
- export function useSelectedLayoutSegments(): string[] {
71
- const context = useContext(RouterContext);
72
- if (!context) return [];
73
- return context.currentPath.split('/').filter(Boolean);
74
- }
75
-
76
- export function useIsNavigating(): boolean {
77
- const context = useContext(RouterContext);
78
- return context?.isNavigating ?? false;
79
- }
80
-
81
- export function useNavigationEvent(callback: (from: string, to: string) => void) {
82
- const context = useContext(RouterContext);
83
- const currentPath = context?.currentPath;
84
- const prevPath = useRef(currentPath || '/');
85
-
86
- useEffect(() => {
87
- if (!currentPath) return;
88
- if (prevPath.current !== currentPath) {
89
- callback(prevPath.current, currentPath);
90
- prevPath.current = currentPath;
91
- }
92
- }, [currentPath, callback]);
93
- }
94
-
95
- export function redirect(path: string, options?: NavigateOptions): never {
96
- if (typeof window !== 'undefined') {
97
- if (options?.replace) {
98
- window.history.replaceState({}, '', path);
99
- } else {
100
- window.history.pushState({}, '', path);
101
- }
102
- window.dispatchEvent(new PopStateEvent('popstate'));
103
- }
104
- throw new Error(`REDIRECT:${path}`);
105
- }
106
-
107
- export function useRedirect() {
108
- const { navigate, replace } = useRouter();
109
-
110
- return useCallback((path: string, options?: NavigateOptions) => {
111
- if (options?.replace) {
112
- replace(path, options);
113
- } else {
114
- navigate(path, options);
115
- }
116
- }, [navigate, replace]);
117
- }
@@ -1,29 +0,0 @@
1
- export {
2
- redirect,
3
- useIsNavigating,
4
- useNavigationEvent,
5
- useParams,
6
- usePathname,
7
- useRedirect,
8
- useRouter,
9
- useSearchParams,
10
- useSelectedLayoutSegment,
11
- useSelectedLayoutSegments,
12
- } from './context';
13
- export { ErrorBoundary, RouteErrorBoundary } from './ErrorBoundary';
14
- export { createLink, type ResolveRoutePath } from './Link';
15
- export { OlovaRouter } from './OlovaRouter';
16
- export { Outlet } from './Outlet';
17
-
18
- export type {
19
- LayoutRoute,
20
- Metadata,
21
- NavigateOptions,
22
- NotFoundPageConfig,
23
- OutletContextType,
24
- Route,
25
- RouterContextType,
26
- SearchParams,
27
- SetSearchParamsOptions,
28
- } from '../types';
29
-
@@ -1,63 +0,0 @@
1
- import type { ComponentType } from 'react';
2
- import type { NotFoundPageConfig } from '../types';
3
-
4
- export function matchRoute(patternParts: string[], pathParts: string[]) {
5
- const params: Record<string, string> = {};
6
-
7
- // Filter out empty parts (handles trailing slashes)
8
- const filteredPathParts = pathParts.filter(Boolean);
9
- const filteredPatternParts = patternParts.filter(Boolean);
10
-
11
- for (let i = 0; i < filteredPatternParts.length; i++) {
12
- const patternPart = filteredPatternParts[i];
13
- const pathPart = filteredPathParts[i];
14
-
15
- if (patternPart === '*') {
16
- params['*'] = filteredPathParts.slice(i).join('/');
17
- return { match: true, params };
18
- }
19
-
20
- if (pathPart === undefined) {
21
- return { match: false, params: {} };
22
- }
23
-
24
- if (patternPart.startsWith(':')) {
25
- params[patternPart.slice(1)] = decodeURIComponent(pathPart);
26
- } else if (patternPart !== pathPart) {
27
- return { match: false, params: {} };
28
- }
29
- }
30
-
31
- if (filteredPathParts.length > filteredPatternParts.length) {
32
- return { match: false, params: {} };
33
- }
34
-
35
- return { match: true, params };
36
- }
37
-
38
- export function matchLayoutScope(layoutPath: string, pathname: string): boolean {
39
- if (layoutPath === '/') return true;
40
- const normalizedPathname = pathname.replace(/\/+$/, '') || '/';
41
- const normalizedLayout = layoutPath.replace(/\/+$/, '') || '/';
42
- return normalizedPathname === normalizedLayout || normalizedPathname.startsWith(normalizedLayout + '/');
43
- }
44
-
45
- export function findNotFoundPage(path: string, notFoundPages: NotFoundPageConfig[]): ComponentType | null {
46
- if (!notFoundPages || notFoundPages.length === 0) return null;
47
-
48
- const normalizedPath = path.replace(/\/+$/, '') || '/';
49
-
50
- const sorted = [...notFoundPages].sort((a, b) =>
51
- b.pathPrefix.length - a.pathPrefix.length
52
- );
53
-
54
- for (const nf of sorted) {
55
- if (nf.pathPrefix === '') {
56
- return nf.component;
57
- }
58
- if (normalizedPath === nf.pathPrefix || normalizedPath.startsWith(nf.pathPrefix + '/')) {
59
- return nf.component;
60
- }
61
- }
62
- return null;
63
- }
@@ -1,23 +0,0 @@
1
- export {
2
- OlovaRouter,
3
- Outlet,
4
- createLink,
5
- redirect,
6
- useIsNavigating,
7
- useNavigationEvent,
8
- useParams,
9
- usePathname,
10
- useRedirect,
11
- useRouter,
12
- useSearchParams,
13
- useSelectedLayoutSegment,
14
- useSelectedLayoutSegments,
15
- type LayoutRoute,
16
- type Metadata,
17
- type NavigateOptions,
18
- type NotFoundPageConfig,
19
- type Route,
20
- type SearchParams,
21
- type SetSearchParamsOptions,
22
- } from './index';
23
-
@@ -1,29 +0,0 @@
1
- import type { SearchParams } from '../types';
2
-
3
- export function parseSearchParams(search: string): SearchParams {
4
- const params: SearchParams = {};
5
- const urlParams = new URLSearchParams(search);
6
-
7
- for (const key of urlParams.keys()) {
8
- const values = urlParams.getAll(key);
9
- params[key] = values.length === 1 ? values[0] : values;
10
- }
11
-
12
- return params;
13
- }
14
-
15
- export function buildSearchString(params: Record<string, string | string[] | null>): string {
16
- const urlParams = new URLSearchParams();
17
-
18
- for (const [key, value] of Object.entries(params)) {
19
- if (value === null || value === undefined) continue;
20
- if (Array.isArray(value)) {
21
- value.forEach(v => urlParams.append(key, v));
22
- } else {
23
- urlParams.set(key, value);
24
- }
25
- }
26
-
27
- const str = urlParams.toString();
28
- return str ? `?${str}` : '';
29
- }