Skip to content

Commit a9066a4

Browse files
committed
feat: query param matchers; fix: same route, diff params #54
1 parent 328ddff commit a9066a4

24 files changed

+765
-222
lines changed

.prettierrc

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2+
"tabWidth": 2,
23
"useTabs": false,
34
"singleQuote": false,
45
"trailingComma": "none",
56
"printWidth": 120,
67
"endOfLine": "lf",
7-
"plugins": ["prettier-plugin-svelte"],
88
"pluginSearchDirs": false,
99
"overrides": [
1010
{
@@ -36,7 +36,15 @@
3636
"options": {
3737
"parser": "typescript"
3838
}
39+
},
40+
{
41+
"files": "*",
42+
"options": {
43+
"tabWidth": 2
44+
}
3945
}
4046
],
41-
"bracketSameLine": true
47+
"bracketSameLine": true,
48+
"singleAttributePerLine": true,
49+
"htmlWhitespaceSensitivity": "ignore"
4250
}

.vscode/settings.json

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
11
{
2-
"editor.defaultFormatter": "esbenp.prettier-vscode"
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"workbench.colorCustomizations": {
4+
"activityBar.activeBackground": "#9ae649",
5+
"activityBar.background": "#9ae649",
6+
"activityBar.foreground": "#15202b",
7+
"activityBar.inactiveForeground": "#15202b99",
8+
"activityBarBadge.background": "#3b92e4",
9+
"activityBarBadge.foreground": "#15202b",
10+
"commandCenter.border": "#15202b99",
11+
"editorGroup.border": "#9ae649",
12+
"panel.border": "#9ae649",
13+
"sash.hoverBorder": "#9ae649",
14+
"sideBar.border": "#9ae649",
15+
"statusBar.background": "#81de1e",
16+
"statusBar.border": "#81de1e",
17+
"statusBar.debuggingBackground": "#7b1ede",
18+
"statusBar.debuggingBorder": "#7b1ede",
19+
"statusBar.debuggingForeground": "#e7e7e7",
20+
"statusBar.foreground": "#15202b",
21+
"statusBarItem.hoverBackground": "#67b118",
22+
"statusBarItem.remoteBackground": "#81de1e",
23+
"statusBarItem.remoteForeground": "#15202b",
24+
"tab.activeBorder": "#9ae649",
25+
"titleBar.activeBackground": "#81de1e",
26+
"titleBar.activeForeground": "#15202b",
27+
"titleBar.border": "#81de1e",
28+
"titleBar.inactiveBackground": "#81de1e99",
29+
"titleBar.inactiveForeground": "#15202b99"
30+
},
31+
"peacock.color": "#81de1e"
332
}

demo/.prettierrc

-51
This file was deleted.

demo/src/routes/not-found.svelte

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
let props = $props();
2+
let { route } = $props();
33
</script>
44

55
<div class="flex flex-col items-center justify-center gap-4">
@@ -8,5 +8,6 @@
88
<p class="text-sm text-gray-500">The page you are looking for does not exist.</p>
99
<pre class="rounded-md bg-gray-900 p-2 text-sm text-gray-400">$props():
1010

11-
{JSON.stringify(props, null, 2)}</pre>
11+
{JSON.stringify(route, null, 2)}
12+
</pre>
1213
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts">
2+
let { route } = $props();
3+
</script>
4+
5+
<div class="flex flex-col items-center justify-center gap-4">
6+
<pre class="rounded-md bg-gray-800 p-2 text-sm text-emerald-500">included from "custom-not-found.svelte":</pre>
7+
<h1 class="text-2xl font-bold">404 not found :(</h1>
8+
<p class="text-sm text-gray-500">The page you are looking for does not exist.</p>
9+
<pre class="rounded-md bg-gray-900 p-2 text-sm text-gray-400">$props():
10+
11+
{JSON.stringify(route, null, 2)}
12+
</pre>
13+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script lang="ts">
2+
import Code from "$lib/components/code.svelte";
3+
import InlineCode from "$lib/components/inline-code.svelte";
4+
5+
let { route } = $props();
6+
</script>
7+
8+
{#snippet content()}
9+
The route uses the pattern <InlineCode text="/\/?<child>.*)/" /> which captures everything after the base path and passes it to the component as the `params` prop.
10+
{/snippet}
11+
12+
<Code
13+
title="params.route value:"
14+
file="src/routes/props/display-params-another.svelte">
15+
<div>{JSON.stringify(route, null, 2)}</div>
16+
</Code>

demo/src/routes/props/props.svelte

+105-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,58 @@
22
import Badge from "$lib/components/badge.svelte";
33
import Container from "$lib/components/container.svelte";
44
import RouteWrapper from "$lib/components/routes/route-wrapper.svelte";
5-
import { type Route } from "@mateothegreat/svelte5-router";
5+
import { getStatusByValue, StatusCode, type Route } from "@mateothegreat/svelte5-router";
66
import Router from "@mateothegreat/svelte5-router/router.svelte";
7+
import CustomNotFound from "./custom-not-found.svelte";
8+
import DisplayParamsAnother from "./display-params-another.svelte";
79
import DisplayParams from "./display-params.svelte";
10+
import QueryMatch_1 from "./query-match-1.svelte";
11+
import QueryMatch_2 from "./query-match-2.svelte";
812
913
const routes: Route[] = [
1014
{
1115
// This route will be used if there is no match above.
1216
component: snippet
1317
},
18+
{
19+
name: "props-query-match-2",
20+
path: "query-matcher",
21+
query: {
22+
value: 2
23+
},
24+
component: QueryMatch_2
25+
},
26+
{
27+
/**
28+
* /props/query-matcher?pagination=2,23
29+
* /props/query-matcher?pagination=2,23&company=123
30+
* /props/query-matcher?pagination=2,23&company=1234567
31+
* /props/query-matcher?pagination=2,23&company=1
32+
* /props/query-matcher?pagination=2,23&company=12
33+
*/
34+
name: "props-query-matching",
35+
path: "/query-matcher",
36+
query: {
37+
// The "pagination" query param:
38+
// * must be present
39+
// * must be a number
40+
// + and then be followed by an optional "cursor"
41+
// * if present, it must have a comma delimiter
42+
// * and then be a string of alphanumeric characters:
43+
pagination: /^(?<page>\d+)(,(?<cursor>[a-z0-9]+))?$/,
44+
// The "company" query param is optional, and if present:
45+
// * can be empty
46+
// * must be a single number
47+
company: /^(\d+)?$/
48+
},
49+
component: QueryMatch_1,
50+
props: {
51+
metadata: {
52+
src: "props.svelte",
53+
routeName: "props-query-matching"
54+
}
55+
}
56+
},
1457
{
1558
// This route will match any path and pass the pattern groups
1659
// as an object to the component that is passed in $props().
@@ -27,6 +70,15 @@
2770
userAgent: navigator.userAgent
2871
}
2972
}
73+
},
74+
{
75+
path: /another\/(?<child>.*)/,
76+
component: DisplayParamsAnother,
77+
props: {
78+
another: {
79+
one: "works!"
80+
}
81+
}
3082
}
3183
];
3284
@@ -67,16 +119,67 @@
67119
href: "/props/foo",
68120
label: "/props/foo"
69121
},
122+
{
123+
href: "/props/another/bar",
124+
label: "/props/another/bar"
125+
},
70126
{
71127
href: "/props/bar?someQueryParam=123",
72128
label: "/props/bar?someQueryParam=123"
129+
},
130+
{
131+
href: "/props/query-matcher?pagination=2,23&company=123",
132+
label: "/props/query-matcher?pagination=2,23&company=123"
133+
},
134+
{
135+
href: "/props/query-matcher?value=2",
136+
label: "/props/query-matcher?value=2"
73137
}
74138
]}>
75139
<Router
76140
id="props-router"
77141
basePath="/props"
78142
{routes}
79143
hooks={{
80-
// pre: globalAuthGuardHook
144+
//
145+
// You could use a global auth guard here to run before every route:
146+
// pre: (route: Routed) => {
147+
// if (!isAuthenticated()) {
148+
// console.warn("user is not authenticated, redirecting to login", route);
149+
// return {
150+
// component: NotGonnaMakeIt,
151+
// };
152+
// }
153+
// }
154+
//
155+
// You could also use a global error handler here to run after every route:
156+
// post: [
157+
// (route: Routed) => {
158+
// console.info("do some more work here", route);
159+
// return true;
160+
// },
161+
// someLogMethod,
162+
// finalMethod,
163+
// ]
164+
//
165+
}}
166+
statuses={{
167+
[StatusCode.NotFound]: (path: string) => {
168+
console.warn(`the path "${path}" could not be found :(`, {
169+
// You could use the status name to make something pretty:
170+
status: getStatusByValue(StatusCode.NotFound),
171+
// You could also use the status code to something more dynamic:
172+
code: StatusCode.NotFound
173+
});
174+
// Now, we're going to return a new route that will be rendered by the router:
175+
return {
176+
component: CustomNotFound,
177+
// You can pass props to the component that is rendered if you need to
178+
// bubble up some extra information:
179+
props: {
180+
src: "props.svelte"
181+
}
182+
};
183+
}
81184
}} />
82185
</RouteWrapper>
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import Code from "$lib/components/code.svelte";
3+
import InlineCode from "$lib/components/inline-code.svelte";
4+
5+
let { route } = $props();
6+
</script>
7+
8+
{#snippet content()}
9+
The route uses the pattern <InlineCode text="/\/?<child>.*)/" /> which captures everything after the base path and passes it to the component as the `params` prop.
10+
{/snippet}
11+
12+
<h1>Query Match 1</h1>
13+
14+
<Code
15+
title="params.route value:"
16+
file="src/routes/props/query-match-1.svelte">
17+
<div>{JSON.stringify(route, null, 2)}</div>
18+
</Code>
19+
20+
<Code
21+
title="params.query value:"
22+
file="src/routes/props/query-match-1.svelte">
23+
<div>{JSON.stringify(route.query, null, 2)}</div>
24+
</Code>
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script lang="ts">
2+
import Code from "$lib/components/code.svelte";
3+
import InlineCode from "$lib/components/inline-code.svelte";
4+
5+
let { route } = $props();
6+
</script>
7+
8+
{#snippet content()}
9+
The route uses the pattern <InlineCode text="/\/?<child>.*)/" /> which captures everything after the base path and passes it to the component as the `params` prop.
10+
{/snippet}
11+
12+
<h1>Query Match 2</h1>
13+
<Code
14+
title="params.route value:"
15+
file="src/routes/props/query-match-2.svelte">
16+
<div>{JSON.stringify(route, null, 2)}</div>
17+
</Code>
18+
19+
<Code
20+
title="params.query value:"
21+
file="src/routes/props/query-match-2.svelte">
22+
<div>{JSON.stringify(route.query, null, 2)}</div>
23+
</Code>

0 commit comments

Comments
 (0)