@solidjs/router 0.6.0 → 0.7.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 +90 -54
- package/dist/components.d.ts +5 -4
- package/dist/components.jsx +18 -3
- package/dist/index.js +42 -13
- package/dist/routing.d.ts +3 -3
- package/dist/routing.js +14 -7
- package/dist/types.d.ts +9 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +32 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A router lets you change your view based on the URL in the browser. This allows your "single-page" application to simulate a traditional multipage site. To use Solid Router, you specify components called Routes that depend on the value of the URL (the "path"), and the router handles the mechanism of swapping them in and out.
|
|
8
8
|
|
|
9
|
-
Solid Router is a universal router for SolidJS - it works whether you're rendering on the client or on the server. It was inspired by and combines paradigms of React Router and the Ember Router. Routes can be defined directly in your app's template using JSX, but you can also pass your route configuration directly as an object. It also supports nested routing, so navigation can change a part of a component, rather than completely replacing it.
|
|
9
|
+
Solid Router is a universal router for SolidJS - it works whether you're rendering on the client or on the server. It was inspired by and combines paradigms of React Router and the Ember Router. Routes can be defined directly in your app's template using JSX, but you can also pass your route configuration directly as an object. It also supports nested routing, so navigation can change a part of a component, rather than completely replacing it.
|
|
10
10
|
|
|
11
11
|
It supports all of Solid's SSR methods and has Solid's transitions baked in, so use it freely with suspense, resources, and lazy components. Solid Router also allows you to define a data function that loads parallel to the routes ([render-as-you-fetch](https://epicreact.dev/render-as-you-fetch/)).
|
|
12
12
|
|
|
@@ -16,8 +16,8 @@ It supports all of Solid's SSR methods and has Solid's transitions baked in, so
|
|
|
16
16
|
- [Create Links to Your Routes](#create-links-to-your-routes)
|
|
17
17
|
- [Dynamic Routes](#dynamic-routes)
|
|
18
18
|
- [Data Functions](#data-functions)
|
|
19
|
-
- [Nested Routes](#nested-routes)
|
|
20
|
-
- [Hash Mode Router](#hash-mode-router)
|
|
19
|
+
- [Nested Routes](#nested-routes)
|
|
20
|
+
- [Hash Mode Router](#hash-mode-router)
|
|
21
21
|
- [Config Based Routing](#config-based-routing)
|
|
22
22
|
- [Router Primitives](#router-primitives)
|
|
23
23
|
- [useParams](#useparams)
|
|
@@ -63,17 +63,16 @@ Solid Router allows you to configure your routes using JSX:
|
|
|
63
63
|
|
|
64
64
|
1. Use the `Routes` component to specify where the routes should appear in your app.
|
|
65
65
|
|
|
66
|
-
|
|
67
66
|
```jsx
|
|
68
67
|
import { Routes, Route } from "@solidjs/router"
|
|
69
68
|
|
|
70
69
|
export default function App() {
|
|
71
70
|
return <>
|
|
72
|
-
|
|
71
|
+
<h1>My Site with Lots of Pages</h1>
|
|
73
72
|
<Routes>
|
|
74
73
|
|
|
75
74
|
</Routes>
|
|
76
|
-
|
|
75
|
+
</>
|
|
77
76
|
}
|
|
78
77
|
```
|
|
79
78
|
|
|
@@ -87,13 +86,13 @@ import Users from "./pages/Users"
|
|
|
87
86
|
|
|
88
87
|
export default function App() {
|
|
89
88
|
return <>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
<h1>My Site with Lots of Pages</h1>
|
|
90
|
+
<Routes>
|
|
91
|
+
<Route path="/users" component={Users} />
|
|
92
|
+
<Route path="/" component={Home} />
|
|
93
|
+
<Route path="/about" element={<div>This site was made with Solid</div>} />
|
|
94
|
+
</Routes>
|
|
95
|
+
</>
|
|
97
96
|
}
|
|
98
97
|
```
|
|
99
98
|
|
|
@@ -109,13 +108,13 @@ const Home = lazy(() => import("./pages/Home"));
|
|
|
109
108
|
|
|
110
109
|
export default function App() {
|
|
111
110
|
return <>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
<h1>My Site with Lots of Pages</h1>
|
|
112
|
+
<Routes>
|
|
113
|
+
<Route path="/users" component={Users} />
|
|
114
|
+
<Route path="/" component={Home} />
|
|
115
|
+
<Route path="/about" element={<div>This site was made with Solid</div>} />
|
|
116
|
+
</Routes>
|
|
117
|
+
</>
|
|
119
118
|
}
|
|
120
119
|
```
|
|
121
120
|
|
|
@@ -131,17 +130,17 @@ const Home = lazy(() => import("./pages/Home"));
|
|
|
131
130
|
|
|
132
131
|
export default function App() {
|
|
133
132
|
return <>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
<h1>My Site with Lots of Pages</h1>
|
|
134
|
+
<nav>
|
|
135
|
+
<A href="/about">About</A>
|
|
136
|
+
<A href="/">Home</A>
|
|
137
|
+
</nav>
|
|
138
|
+
<Routes>
|
|
139
|
+
<Route path="/users" component={Users} />
|
|
140
|
+
<Route path="/" component={Home} />
|
|
141
|
+
<Route path="/about" element={<div>This site was made with Solid</div>} />
|
|
142
|
+
</Routes>
|
|
143
|
+
</>
|
|
145
144
|
}
|
|
146
145
|
```
|
|
147
146
|
|
|
@@ -163,7 +162,7 @@ Solid Router provides a `Navigate` component that works similarly to `A`, but it
|
|
|
163
162
|
|
|
164
163
|
```jsx
|
|
165
164
|
function getPath ({navigate, location}) {
|
|
166
|
-
//navigate is the result of calling useNavigate(); location is the result of calling useLocation().
|
|
165
|
+
//navigate is the result of calling useNavigate(); location is the result of calling useLocation().
|
|
167
166
|
//You can use those to dynamically determine a path to navigate to
|
|
168
167
|
return "/some-path";
|
|
169
168
|
}
|
|
@@ -174,7 +173,7 @@ function getPath ({navigate, location}) {
|
|
|
174
173
|
|
|
175
174
|
## Dynamic Routes
|
|
176
175
|
|
|
177
|
-
If you don't know the path ahead of time, you might want to treat part of the path as a flexible parameter that is passed on to the component.
|
|
176
|
+
If you don't know the path ahead of time, you might want to treat part of the path as a flexible parameter that is passed on to the component.
|
|
178
177
|
|
|
179
178
|
```jsx
|
|
180
179
|
import { lazy } from "solid-js";
|
|
@@ -185,14 +184,14 @@ const Home = lazy(() => import("./pages/Home"));
|
|
|
185
184
|
|
|
186
185
|
export default function App() {
|
|
187
186
|
return <>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
187
|
+
<h1>My Site with Lots of Pages</h1>
|
|
188
|
+
<Routes>
|
|
189
|
+
<Route path="/users" component={Users} />
|
|
190
|
+
<Route path="/users/:id" component={User} />
|
|
191
|
+
<Route path="/" component={Home} />
|
|
192
|
+
<Route path="/about" element={<div>This site was made with Solid</div>} />
|
|
193
|
+
</Routes>
|
|
194
|
+
</>
|
|
196
195
|
}
|
|
197
196
|
```
|
|
198
197
|
|
|
@@ -200,6 +199,48 @@ The colon indicates that `id` can be any string, and as long as the URL fits tha
|
|
|
200
199
|
|
|
201
200
|
You can then access that `id` from within a route component with `useParams`:
|
|
202
201
|
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
Each path parameter can be validated using a `MatchFilter`.
|
|
205
|
+
This allows for more complex routing descriptions than just checking the presence of a parameter.
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import {lazy} from "solid-js";
|
|
209
|
+
import {Routes, Route} from "@solidjs/router"
|
|
210
|
+
import type {SegmentValidators} from "./types";
|
|
211
|
+
|
|
212
|
+
const Users = lazy(() => import("./pages/Users"));
|
|
213
|
+
const User = lazy(() => import("./pages/User"));
|
|
214
|
+
const Home = lazy(() => import("./pages/Home"));
|
|
215
|
+
|
|
216
|
+
const filters: MatchFilters = {
|
|
217
|
+
parent: ['mom', 'dad'], // allow enum values
|
|
218
|
+
id: /^\d+$/, // only allow numbers
|
|
219
|
+
withHtmlExtension: (v: string) => v.length > 5 && v.endsWith('.html') // we want an `*.html` extension
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export default function App() {
|
|
223
|
+
return <>
|
|
224
|
+
<h1>My Site with Lots of Pages</h1>
|
|
225
|
+
<Routes>
|
|
226
|
+
<Route path="/users/:parent/:id/:withHtmlExtension" component={User} matchFilters={filters}/>
|
|
227
|
+
</Routes>
|
|
228
|
+
</>
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Here, we have added the `matchFilters` prop. This allows us to validate the `parent`, `id` and `withHtmlExtension` parameters against the filters defined in `filters`.
|
|
233
|
+
If the validation fails, the route will not match.
|
|
234
|
+
|
|
235
|
+
So in this example:
|
|
236
|
+
|
|
237
|
+
- `/users/mom/123/contact.html` would match,
|
|
238
|
+
- `/users/dad/123/about.html` would match,
|
|
239
|
+
- `/users/aunt/123/contact.html` would not match as `:parent` is not 'mom' or 'dad',
|
|
240
|
+
- `/users/mom/me/contact.html` would not match as `:id` is not a number,
|
|
241
|
+
- `/users/dad/123/contact` would not match as `:withHtmlExtension` is missing `.html`.
|
|
242
|
+
|
|
243
|
+
---
|
|
203
244
|
|
|
204
245
|
```jsx
|
|
205
246
|
//async fetching function
|
|
@@ -250,12 +291,10 @@ Routes also support defining multiple paths using an array. This allows a route
|
|
|
250
291
|
<Route path={["login", "register"]} component={Login}/>
|
|
251
292
|
```
|
|
252
293
|
|
|
253
|
-
|
|
254
294
|
## Data Functions
|
|
255
295
|
In the [above example](#dynamic-routes), the User component is lazy-loaded and then the data is fetched. With route data functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible.
|
|
256
296
|
|
|
257
|
-
To do this, create a function that fetches and returns the data using `createResource`. Then pass that function to the `data` prop of the `Route` component.
|
|
258
|
-
|
|
297
|
+
To do this, create a function that fetches and returns the data using `createResource`. Then pass that function to the `data` prop of the `Route` component.
|
|
259
298
|
|
|
260
299
|
```js
|
|
261
300
|
import { lazy } from "solid-js";
|
|
@@ -299,7 +338,7 @@ A common pattern is to export the data function that corresponds to a route in a
|
|
|
299
338
|
```js
|
|
300
339
|
import { lazy } from "solid-js";
|
|
301
340
|
import { Route } from "@solidjs/router";
|
|
302
|
-
import { fetchUser } ...
|
|
341
|
+
import { fetchUser } ...
|
|
303
342
|
import UserData from "./pages/users/[id].data.js";
|
|
304
343
|
const User = lazy(() => import("/pages/users/[id].js"));
|
|
305
344
|
|
|
@@ -341,15 +380,14 @@ Only leaf Route nodes (innermost `Route` components) are given a route. If you w
|
|
|
341
380
|
|
|
342
381
|
You can also take advantage of nesting by adding a parent element with an `<Outlet/>`.
|
|
343
382
|
```jsx
|
|
344
|
-
|
|
345
383
|
import { Outlet } from "@solidjs/router";
|
|
346
384
|
|
|
347
385
|
function PageWrapper () {
|
|
348
386
|
return <div>
|
|
349
|
-
|
|
387
|
+
<h1> We love our users! </h1>
|
|
350
388
|
<Outlet/>
|
|
351
|
-
|
|
352
|
-
|
|
389
|
+
<A href="/">Back Home</A>
|
|
390
|
+
</div>
|
|
353
391
|
}
|
|
354
392
|
|
|
355
393
|
<Route path="/users" component={PageWrapper}>
|
|
@@ -369,7 +407,7 @@ You can nest indefinitely - just remember that only leaf nodes will become their
|
|
|
369
407
|
</Route>
|
|
370
408
|
```
|
|
371
409
|
|
|
372
|
-
If you declare a `data` function on a parent and a child, the result of the parent's data function will be passed to the child's data function as the `data` property of the argument, as described in the last section. This works even if it isn't a direct child, because by default every route forwards its parent's data.
|
|
410
|
+
If you declare a `data` function on a parent and a child, the result of the parent's data function will be passed to the child's data function as the `data` property of the argument, as described in the last section. This works even if it isn't a direct child, because by default every route forwards its parent's data.
|
|
373
411
|
|
|
374
412
|
## Hash Mode Router
|
|
375
413
|
|
|
@@ -378,7 +416,7 @@ By default, Solid Router uses `location.pathname` as route path. You can simply
|
|
|
378
416
|
```jsx
|
|
379
417
|
import { Router, hashIntegration } from '@solidjs/router'
|
|
380
418
|
|
|
381
|
-
<Router source={hashIntegration()}><App
|
|
419
|
+
<Router source={hashIntegration()}><App /></Router>
|
|
382
420
|
```
|
|
383
421
|
|
|
384
422
|
## Config Based Routing
|
|
@@ -560,11 +598,9 @@ useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
|
560
598
|
setTimeout(() => {
|
|
561
599
|
if (window.confirm("Discard unsaved changes - are you sure?")) {
|
|
562
600
|
// user wants to proceed anyway so retry with force=true
|
|
563
|
-
e.retry(true);
|
|
601
|
+
e.retry(true);
|
|
564
602
|
}
|
|
565
603
|
}, 100);
|
|
566
604
|
}
|
|
567
605
|
});
|
|
568
606
|
```
|
|
569
|
-
|
|
570
|
-
|
package/dist/components.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component, JSX } from "solid-js";
|
|
2
|
-
import type { Location, LocationChangeSignal, Navigator, RouteDataFunc, RouteDefinition, RouterIntegration } from "./types";
|
|
2
|
+
import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteDataFunc, RouteDefinition, RouterIntegration } from "./types";
|
|
3
3
|
declare module "solid-js" {
|
|
4
4
|
namespace JSX {
|
|
5
5
|
interface AnchorHTMLAttributes<T> {
|
|
@@ -29,10 +29,11 @@ export interface RoutesProps {
|
|
|
29
29
|
}
|
|
30
30
|
export declare const Routes: (props: RoutesProps) => JSX.Element;
|
|
31
31
|
export declare const useRoutes: (routes: RouteDefinition | RouteDefinition[], base?: string) => () => JSX.Element;
|
|
32
|
-
export type RouteProps = {
|
|
33
|
-
path:
|
|
32
|
+
export type RouteProps<S extends string> = {
|
|
33
|
+
path: S | S[];
|
|
34
34
|
children?: JSX.Element;
|
|
35
35
|
data?: RouteDataFunc;
|
|
36
|
+
matchFilters?: MatchFilters<S>;
|
|
36
37
|
} & ({
|
|
37
38
|
element?: never;
|
|
38
39
|
component: Component;
|
|
@@ -41,7 +42,7 @@ export type RouteProps = {
|
|
|
41
42
|
element?: JSX.Element;
|
|
42
43
|
preload?: () => void;
|
|
43
44
|
});
|
|
44
|
-
export declare const Route: (props: RouteProps) => JSX.Element;
|
|
45
|
+
export declare const Route: <S extends string>(props: RouteProps<S>) => JSX.Element;
|
|
45
46
|
export declare const Outlet: () => JSX.Element;
|
|
46
47
|
export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
|
|
47
48
|
href: string;
|
package/dist/components.jsx
CHANGED
|
@@ -3,7 +3,7 @@ import { children, createMemo, createRoot, mergeProps, on, Show, splitProps } fr
|
|
|
3
3
|
import { isServer } from "solid-js/web";
|
|
4
4
|
import { pathIntegration, staticIntegration } from "./integration";
|
|
5
5
|
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath, useRoute, useRouter } from "./routing";
|
|
6
|
-
import { joinPaths, normalizePath } from "./utils";
|
|
6
|
+
import { joinPaths, normalizePath, createMemoObject } from "./utils";
|
|
7
7
|
export const Router = (props) => {
|
|
8
8
|
const { source, url, base, data, out } = props;
|
|
9
9
|
const integration = source || (isServer ? staticIntegration({ value: url || "" }) : pathIntegration());
|
|
@@ -16,6 +16,14 @@ export const Routes = (props) => {
|
|
|
16
16
|
const routeDefs = children(() => props.children);
|
|
17
17
|
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
|
|
18
18
|
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
|
|
19
|
+
const params = createMemoObject(() => {
|
|
20
|
+
const m = matches();
|
|
21
|
+
const params = {};
|
|
22
|
+
for (let i = 0; i < m.length; i++) {
|
|
23
|
+
Object.assign(params, m[i].params);
|
|
24
|
+
}
|
|
25
|
+
return params;
|
|
26
|
+
});
|
|
19
27
|
if (router.out) {
|
|
20
28
|
router.out.matches.push(matches().map(({ route, path, params }) => ({
|
|
21
29
|
originalPath: route.originalPath,
|
|
@@ -42,7 +50,7 @@ export const Routes = (props) => {
|
|
|
42
50
|
}
|
|
43
51
|
createRoot(dispose => {
|
|
44
52
|
disposers[i] = dispose;
|
|
45
|
-
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i]);
|
|
53
|
+
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i], params);
|
|
46
54
|
});
|
|
47
55
|
}
|
|
48
56
|
}
|
|
@@ -76,7 +84,14 @@ export const Outlet = () => {
|
|
|
76
84
|
};
|
|
77
85
|
export function A(props) {
|
|
78
86
|
props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);
|
|
79
|
-
const [, rest] = splitProps(props, [
|
|
87
|
+
const [, rest] = splitProps(props, [
|
|
88
|
+
"href",
|
|
89
|
+
"state",
|
|
90
|
+
"class",
|
|
91
|
+
"activeClass",
|
|
92
|
+
"inactiveClass",
|
|
93
|
+
"end"
|
|
94
|
+
]);
|
|
80
95
|
const to = useResolvedPath(() => props.href);
|
|
81
96
|
const href = useHref(to);
|
|
82
97
|
const location = useLocation();
|
package/dist/index.js
CHANGED
|
@@ -185,7 +185,7 @@ function extractSearchParams(url) {
|
|
|
185
185
|
});
|
|
186
186
|
return params;
|
|
187
187
|
}
|
|
188
|
-
function createMatcher(path, partial) {
|
|
188
|
+
function createMatcher(path, partial, matchFilters) {
|
|
189
189
|
const [pattern, splat] = path.split("/*", 2);
|
|
190
190
|
const segments = pattern.split("/").filter(Boolean);
|
|
191
191
|
const len = segments.length;
|
|
@@ -199,24 +199,46 @@ function createMatcher(path, partial) {
|
|
|
199
199
|
path: len ? "" : "/",
|
|
200
200
|
params: {}
|
|
201
201
|
};
|
|
202
|
+
const matchFilter = s => matchFilters === undefined ? undefined : matchFilters[s];
|
|
202
203
|
for (let i = 0; i < len; i++) {
|
|
203
204
|
const segment = segments[i];
|
|
204
205
|
const locSegment = locSegments[i];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}) !== 0) {
|
|
206
|
+
const key = segment[0] === ":" ? segment.slice(1) : segment;
|
|
207
|
+
if (segment[0] === ":" && matchSegment(locSegment, matchFilter(key))) {
|
|
208
|
+
match.params[key] = locSegment;
|
|
209
|
+
} else if (!matchSegment(locSegment, segment)) {
|
|
210
210
|
return null;
|
|
211
211
|
}
|
|
212
212
|
match.path += `/${locSegment}`;
|
|
213
213
|
}
|
|
214
214
|
if (splat) {
|
|
215
|
-
|
|
215
|
+
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
216
|
+
if (matchSegment(remainder, matchFilter(splat))) {
|
|
217
|
+
match.params[splat] = remainder;
|
|
218
|
+
} else {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
216
221
|
}
|
|
217
222
|
return match;
|
|
218
223
|
};
|
|
219
224
|
}
|
|
225
|
+
function matchSegment(input, filter) {
|
|
226
|
+
const isEqual = s => s.localeCompare(input, undefined, {
|
|
227
|
+
sensitivity: "base"
|
|
228
|
+
}) === 0;
|
|
229
|
+
if (filter === undefined) {
|
|
230
|
+
return true;
|
|
231
|
+
} else if (typeof filter === "string") {
|
|
232
|
+
return isEqual(filter);
|
|
233
|
+
} else if (typeof filter === "function") {
|
|
234
|
+
return filter(input);
|
|
235
|
+
} else if (Array.isArray(filter)) {
|
|
236
|
+
return filter.some(isEqual);
|
|
237
|
+
} else if (filter instanceof RegExp) {
|
|
238
|
+
return filter.test(input);
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
220
242
|
function scoreRoute(route) {
|
|
221
243
|
const [pattern, splat] = route.pattern.split("/*", 2);
|
|
222
244
|
const segments = pattern.split("/").filter(Boolean);
|
|
@@ -294,9 +316,9 @@ const useHref = to => {
|
|
|
294
316
|
const useNavigate = () => useRouter().navigatorFactory();
|
|
295
317
|
const useLocation = () => useRouter().location;
|
|
296
318
|
const useIsRouting = () => useRouter().isRouting;
|
|
297
|
-
const useMatch = path => {
|
|
319
|
+
const useMatch = (path, matchFilters) => {
|
|
298
320
|
const location = useLocation();
|
|
299
|
-
const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path)));
|
|
321
|
+
const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path, undefined, matchFilters)));
|
|
300
322
|
return createMemo(() => {
|
|
301
323
|
for (const matcher of matchers()) {
|
|
302
324
|
const match = matcher(location.pathname);
|
|
@@ -353,7 +375,7 @@ function createRoutes(routeDef, base = "", fallback) {
|
|
|
353
375
|
...shared,
|
|
354
376
|
originalPath,
|
|
355
377
|
pattern,
|
|
356
|
-
matcher: createMatcher(pattern, !isLeaf)
|
|
378
|
+
matcher: createMatcher(pattern, !isLeaf, routeDef.matchFilters)
|
|
357
379
|
});
|
|
358
380
|
}
|
|
359
381
|
return acc;
|
|
@@ -642,7 +664,7 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
642
664
|
beforeLeave
|
|
643
665
|
};
|
|
644
666
|
}
|
|
645
|
-
function createRouteContext(router, parent, child, match) {
|
|
667
|
+
function createRouteContext(router, parent, child, match, params) {
|
|
646
668
|
const {
|
|
647
669
|
base,
|
|
648
670
|
location,
|
|
@@ -655,7 +677,6 @@ function createRouteContext(router, parent, child, match) {
|
|
|
655
677
|
data
|
|
656
678
|
} = match().route;
|
|
657
679
|
const path = createMemo(() => match().path);
|
|
658
|
-
const params = createMemoObject(() => match().params);
|
|
659
680
|
preload && preload();
|
|
660
681
|
const route = {
|
|
661
682
|
parent,
|
|
@@ -713,6 +734,14 @@ const Routes = props => {
|
|
|
713
734
|
const routeDefs = children(() => props.children);
|
|
714
735
|
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
|
|
715
736
|
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
|
|
737
|
+
const params = createMemoObject(() => {
|
|
738
|
+
const m = matches();
|
|
739
|
+
const params = {};
|
|
740
|
+
for (let i = 0; i < m.length; i++) {
|
|
741
|
+
Object.assign(params, m[i].params);
|
|
742
|
+
}
|
|
743
|
+
return params;
|
|
744
|
+
});
|
|
716
745
|
if (router.out) {
|
|
717
746
|
router.out.matches.push(matches().map(({
|
|
718
747
|
route,
|
|
@@ -742,7 +771,7 @@ const Routes = props => {
|
|
|
742
771
|
}
|
|
743
772
|
createRoot(dispose => {
|
|
744
773
|
disposers[i] = dispose;
|
|
745
|
-
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i]);
|
|
774
|
+
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i], params);
|
|
746
775
|
});
|
|
747
776
|
}
|
|
748
777
|
}
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component, Accessor } from "solid-js";
|
|
2
|
-
import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDataFunc, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
|
|
2
|
+
import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDataFunc, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
|
|
3
3
|
export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;
|
|
4
4
|
export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;
|
|
5
5
|
export declare const useRouter: () => RouterContext;
|
|
@@ -9,7 +9,7 @@ export declare const useHref: (to: () => string | undefined) => Accessor<string
|
|
|
9
9
|
export declare const useNavigate: () => Navigator;
|
|
10
10
|
export declare const useLocation: <S = unknown>() => Location<S>;
|
|
11
11
|
export declare const useIsRouting: () => () => boolean;
|
|
12
|
-
export declare const useMatch: (path: () =>
|
|
12
|
+
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S> | undefined) => Accessor<import("./types").PathMatch | undefined>;
|
|
13
13
|
export declare const useParams: <T extends Params>() => T;
|
|
14
14
|
type MaybeReturnType<T> = T extends (...args: any) => infer R ? R : T;
|
|
15
15
|
export declare const useRouteData: <T>() => MaybeReturnType<T>;
|
|
@@ -21,5 +21,5 @@ export declare function createBranches(routeDef: RouteDefinition | RouteDefiniti
|
|
|
21
21
|
export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
|
|
22
22
|
export declare function createLocation(path: Accessor<string>, state: Accessor<any>): Location;
|
|
23
23
|
export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, base?: string, data?: RouteDataFunc, out?: object): RouterContext;
|
|
24
|
-
export declare function createRouteContext(router: RouterContext, parent: RouteContext, child: () => RouteContext, match: () => RouteMatch): RouteContext;
|
|
24
|
+
export declare function createRouteContext(router: RouterContext, parent: RouteContext, child: () => RouteContext, match: () => RouteMatch, params: Params): RouteContext;
|
|
25
25
|
export {};
|
package/dist/routing.js
CHANGED
|
@@ -23,9 +23,9 @@ export const useHref = (to) => {
|
|
|
23
23
|
export const useNavigate = () => useRouter().navigatorFactory();
|
|
24
24
|
export const useLocation = () => useRouter().location;
|
|
25
25
|
export const useIsRouting = () => useRouter().isRouting;
|
|
26
|
-
export const useMatch = (path) => {
|
|
26
|
+
export const useMatch = (path, matchFilters) => {
|
|
27
27
|
const location = useLocation();
|
|
28
|
-
const matchers = createMemo(() => expandOptionals(path()).map(
|
|
28
|
+
const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path, undefined, matchFilters)));
|
|
29
29
|
return createMemo(() => {
|
|
30
30
|
for (const matcher of matchers()) {
|
|
31
31
|
const match = matcher(location.pathname);
|
|
@@ -41,12 +41,20 @@ export const useSearchParams = () => {
|
|
|
41
41
|
const navigate = useNavigate();
|
|
42
42
|
const setSearchParams = (params, options) => {
|
|
43
43
|
const searchString = untrack(() => mergeSearchString(location.search, params));
|
|
44
|
-
navigate(location.pathname + searchString + location.hash, {
|
|
44
|
+
navigate(location.pathname + searchString + location.hash, {
|
|
45
|
+
scroll: false,
|
|
46
|
+
resolve: false,
|
|
47
|
+
...options
|
|
48
|
+
});
|
|
45
49
|
};
|
|
46
50
|
return [location.query, setSearchParams];
|
|
47
51
|
};
|
|
48
52
|
export const useBeforeLeave = (listener) => {
|
|
49
|
-
const s = useRouter().beforeLeave.subscribe({
|
|
53
|
+
const s = useRouter().beforeLeave.subscribe({
|
|
54
|
+
listener,
|
|
55
|
+
location: useLocation(),
|
|
56
|
+
navigate: useNavigate()
|
|
57
|
+
});
|
|
50
58
|
onCleanup(s);
|
|
51
59
|
};
|
|
52
60
|
export function createRoutes(routeDef, base = "", fallback) {
|
|
@@ -75,7 +83,7 @@ export function createRoutes(routeDef, base = "", fallback) {
|
|
|
75
83
|
...shared,
|
|
76
84
|
originalPath,
|
|
77
85
|
pattern,
|
|
78
|
-
matcher: createMatcher(pattern, !isLeaf)
|
|
86
|
+
matcher: createMatcher(pattern, !isLeaf, routeDef.matchFilters)
|
|
79
87
|
});
|
|
80
88
|
}
|
|
81
89
|
return acc;
|
|
@@ -363,11 +371,10 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
363
371
|
beforeLeave
|
|
364
372
|
};
|
|
365
373
|
}
|
|
366
|
-
export function createRouteContext(router, parent, child, match) {
|
|
374
|
+
export function createRouteContext(router, parent, child, match, params) {
|
|
367
375
|
const { base, location, navigatorFactory } = router;
|
|
368
376
|
const { pattern, element: outlet, preload, data } = match().route;
|
|
369
377
|
const path = createMemo(() => match().path);
|
|
370
|
-
const params = createMemoObject(() => match().params);
|
|
371
378
|
preload && preload();
|
|
372
379
|
const route = {
|
|
373
380
|
parent,
|
package/dist/types.d.ts
CHANGED
|
@@ -40,8 +40,9 @@ export interface RouteDataFuncArgs<T = unknown> {
|
|
|
40
40
|
navigate: Navigator;
|
|
41
41
|
}
|
|
42
42
|
export type RouteDataFunc<T = unknown, R = unknown> = (args: RouteDataFuncArgs<T>) => R;
|
|
43
|
-
export type RouteDefinition = {
|
|
44
|
-
path:
|
|
43
|
+
export type RouteDefinition<S extends string | string[] = any> = {
|
|
44
|
+
path: S;
|
|
45
|
+
matchFilters?: MatchFilters<S>;
|
|
45
46
|
data?: RouteDataFunc;
|
|
46
47
|
children?: RouteDefinition | RouteDefinition[];
|
|
47
48
|
} & ({
|
|
@@ -52,6 +53,11 @@ export type RouteDefinition = {
|
|
|
52
53
|
element?: JSX.Element;
|
|
53
54
|
preload?: () => void;
|
|
54
55
|
});
|
|
56
|
+
export type MatchFilter = string[] | RegExp | ((s: string) => boolean);
|
|
57
|
+
export type PathParams<P extends string | readonly string[]> = P extends `${infer Head}/${infer Tail}` ? [...PathParams<Head>, ...PathParams<Tail>] : P extends `:${infer S}?` ? [S] : P extends `:${infer S}` ? [S] : P extends `*${infer S}` ? [S] : [];
|
|
58
|
+
export type MatchFilters<P extends string | readonly string[] = any> = P extends string ? {
|
|
59
|
+
[K in PathParams<P>[number]]?: MatchFilter;
|
|
60
|
+
} : Record<string, MatchFilter>;
|
|
55
61
|
export interface PathMatch {
|
|
56
62
|
params: Params;
|
|
57
63
|
path: string;
|
|
@@ -73,6 +79,7 @@ export interface Route {
|
|
|
73
79
|
preload?: () => void;
|
|
74
80
|
data?: RouteDataFunc;
|
|
75
81
|
matcher: (location: string) => PathMatch | null;
|
|
82
|
+
matchFilters?: MatchFilters;
|
|
76
83
|
}
|
|
77
84
|
export interface Branch {
|
|
78
85
|
routes: Route[];
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { Params, PathMatch, Route, SetParams } from "./types";
|
|
1
|
+
import type { MatchFilters, Params, PathMatch, Route, SetParams } from "./types";
|
|
2
2
|
export declare function normalizePath(path: string, omitSlash?: boolean): string;
|
|
3
3
|
export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
|
|
4
4
|
export declare function invariant<T>(value: T | null | undefined, message: string): T;
|
|
5
5
|
export declare function joinPaths(from: string, to: string): string;
|
|
6
6
|
export declare function extractSearchParams(url: URL): Params;
|
|
7
|
-
export declare function createMatcher(path:
|
|
7
|
+
export declare function createMatcher<S extends string>(path: S, partial?: boolean, matchFilters?: MatchFilters<S>): (location: string) => PathMatch | null;
|
|
8
8
|
export declare function scoreRoute(route: Route): number;
|
|
9
9
|
export declare function createMemoObject<T extends Record<string | symbol, unknown>>(fn: () => T): T;
|
|
10
10
|
export declare function mergeSearchString(search: string, params: SetParams): string;
|
package/dist/utils.js
CHANGED
|
@@ -39,7 +39,7 @@ export function extractSearchParams(url) {
|
|
|
39
39
|
});
|
|
40
40
|
return params;
|
|
41
41
|
}
|
|
42
|
-
export function createMatcher(path, partial) {
|
|
42
|
+
export function createMatcher(path, partial, matchFilters) {
|
|
43
43
|
const [pattern, splat] = path.split("/*", 2);
|
|
44
44
|
const segments = pattern.split("/").filter(Boolean);
|
|
45
45
|
const len = segments.length;
|
|
@@ -53,23 +53,50 @@ export function createMatcher(path, partial) {
|
|
|
53
53
|
path: len ? "" : "/",
|
|
54
54
|
params: {}
|
|
55
55
|
};
|
|
56
|
+
const matchFilter = (s) => matchFilters === undefined ? undefined : matchFilters[s];
|
|
56
57
|
for (let i = 0; i < len; i++) {
|
|
57
58
|
const segment = segments[i];
|
|
58
59
|
const locSegment = locSegments[i];
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
const key = segment[0] === ":" ? segment.slice(1) : segment;
|
|
61
|
+
if (segment[0] === ":" && matchSegment(locSegment, matchFilter(key))) {
|
|
62
|
+
match.params[key] = locSegment;
|
|
61
63
|
}
|
|
62
|
-
else if (
|
|
64
|
+
else if (!matchSegment(locSegment, segment)) {
|
|
63
65
|
return null;
|
|
64
66
|
}
|
|
65
67
|
match.path += `/${locSegment}`;
|
|
66
68
|
}
|
|
67
69
|
if (splat) {
|
|
68
|
-
|
|
70
|
+
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
71
|
+
if (matchSegment(remainder, matchFilter(splat))) {
|
|
72
|
+
match.params[splat] = remainder;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
69
77
|
}
|
|
70
78
|
return match;
|
|
71
79
|
};
|
|
72
80
|
}
|
|
81
|
+
function matchSegment(input, filter) {
|
|
82
|
+
const isEqual = (s) => s.localeCompare(input, undefined, { sensitivity: "base" }) === 0;
|
|
83
|
+
if (filter === undefined) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
else if (typeof filter === "string") {
|
|
87
|
+
return isEqual(filter);
|
|
88
|
+
}
|
|
89
|
+
else if (typeof filter === "function") {
|
|
90
|
+
return filter(input);
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(filter)) {
|
|
93
|
+
return filter.some(isEqual);
|
|
94
|
+
}
|
|
95
|
+
else if (filter instanceof RegExp) {
|
|
96
|
+
return filter.test(input);
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
73
100
|
export function scoreRoute(route) {
|
|
74
101
|
const [pattern, splat] = route.pattern.split("/*", 2);
|
|
75
102
|
const segments = pattern.split("/").filter(Boolean);
|