@solidjs/router 0.5.0 → 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.
- package/README.md +30 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.jsx +5 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +108 -166
- package/dist/index.jsx +2 -1
- package/dist/lifecycle.d.ts +2 -0
- package/dist/lifecycle.js +32 -0
- package/dist/routing.d.ts +4 -3
- package/dist/routing.js +27 -14
- package/dist/types.d.ts +25 -6
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +5 -8
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ It supports all of Solid's SSR methods and has Solid's transitions baked in, so
|
|
|
28
28
|
- [useRouteData](#useroutedata)
|
|
29
29
|
- [useMatch](#usematch)
|
|
30
30
|
- [useRoutes](#useroutes)
|
|
31
|
+
- [useBeforeLeave](#usebeforeleave)
|
|
31
32
|
|
|
32
33
|
## Getting Started
|
|
33
34
|
|
|
@@ -538,3 +539,32 @@ return <div classList={{ active: Boolean(match()) }} />;
|
|
|
538
539
|
### useRoutes
|
|
539
540
|
|
|
540
541
|
Used to define routes via a config object instead of JSX. See [Config Based Routing](#config-based-routing).
|
|
542
|
+
|
|
543
|
+
### useBeforeLeave
|
|
544
|
+
|
|
545
|
+
`useBeforeLeave` takes a function that will be called prior to leaving a route. The function will be called with:
|
|
546
|
+
|
|
547
|
+
- from (_Location_): current location (before change).
|
|
548
|
+
- to (_string | number_}: path passed to `navigate`.
|
|
549
|
+
- options (_NavigateOptions_}: options passed to `navigate`.
|
|
550
|
+
- preventDefault (_void function_): call to block the route change.
|
|
551
|
+
- defaultPrevented (_readonly boolean_): true if any previously called leave handlers called preventDefault().
|
|
552
|
+
- retry (_void function_, _force?: boolean_ ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (ie force navigate without confirming).
|
|
553
|
+
|
|
554
|
+
Example usage:
|
|
555
|
+
```js
|
|
556
|
+
useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
|
557
|
+
if (form.isDirty && !e.defaultPrevented) {
|
|
558
|
+
// preventDefault to block immediately and prompt user async
|
|
559
|
+
e.preventDefault();
|
|
560
|
+
setTimeout(() => {
|
|
561
|
+
if (window.confirm("Discard unsaved changes - are you sure?")) {
|
|
562
|
+
// user wants to proceed anyway so retry with force=true
|
|
563
|
+
e.retry(true);
|
|
564
|
+
}
|
|
565
|
+
}, 100);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
|
package/dist/components.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ declare module "solid-js" {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export type RouterProps = {
|
|
14
14
|
base?: string;
|
|
15
15
|
data?: RouteDataFunc;
|
|
16
16
|
children: JSX.Element;
|
|
@@ -29,7 +29,7 @@ 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
|
|
32
|
+
export type RouteProps = {
|
|
33
33
|
path: string | string[];
|
|
34
34
|
children?: JSX.Element;
|
|
35
35
|
data?: RouteDataFunc;
|
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 } from "./utils";
|
|
6
|
+
import { joinPaths, normalizePath } 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());
|
|
@@ -76,7 +76,7 @@ export const Outlet = () => {
|
|
|
76
76
|
};
|
|
77
77
|
export function A(props) {
|
|
78
78
|
props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);
|
|
79
|
-
const [, rest] = splitProps(props, ["href", "state", "activeClass", "inactiveClass", "end"]);
|
|
79
|
+
const [, rest] = splitProps(props, ["href", "state", "class", "activeClass", "inactiveClass", "end"]);
|
|
80
80
|
const to = useResolvedPath(() => props.href);
|
|
81
81
|
const href = useHref(to);
|
|
82
82
|
const location = useLocation();
|
|
@@ -84,11 +84,12 @@ export function A(props) {
|
|
|
84
84
|
const to_ = to();
|
|
85
85
|
if (to_ === undefined)
|
|
86
86
|
return false;
|
|
87
|
-
const path = to_.split(/[?#]/, 1)[0].toLowerCase();
|
|
88
|
-
const loc = location.pathname.toLowerCase();
|
|
87
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase();
|
|
88
|
+
const loc = normalizePath(location.pathname).toLowerCase();
|
|
89
89
|
return props.end ? path === loc : loc.startsWith(path);
|
|
90
90
|
});
|
|
91
91
|
return (<a link {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
|
|
92
|
+
...(props.class && { [props.class]: true }),
|
|
92
93
|
[props.inactiveClass]: !isActive(),
|
|
93
94
|
[props.activeClass]: isActive(),
|
|
94
95
|
...rest.classList
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./components";
|
|
2
2
|
export * from "./integration";
|
|
3
|
-
export
|
|
3
|
+
export * from "./lifecycle";
|
|
4
|
+
export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
|
|
4
5
|
export { mergeSearchString as _mergeSearchString } from "./utils";
|
|
5
|
-
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteDataFunc, RouteDataFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams } from "./types";
|
|
6
|
+
export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteDataFunc, RouteDataFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { isServer, delegateEvents, createComponent as createComponent$1, spread,
|
|
1
|
+
import { isServer, delegateEvents, createComponent as createComponent$1, spread, mergeProps as mergeProps$1, template } from 'solid-js/web';
|
|
2
2
|
import { createSignal, onCleanup, getOwner, runWithOwner, createMemo, createContext, useContext, untrack, createRenderEffect, createComponent, on, startTransition, resetErrorBoundaries, children, createRoot, Show, mergeProps, splitProps } from 'solid-js';
|
|
3
3
|
|
|
4
4
|
function bindEvent(target, type, handler) {
|
|
5
5
|
target.addEventListener(type, handler);
|
|
6
6
|
return () => target.removeEventListener(type, handler);
|
|
7
7
|
}
|
|
8
|
-
|
|
9
8
|
function intercept([value, setValue], get, set) {
|
|
10
9
|
return [get ? () => get(value()) : value, set ? v => setValue(set(v)) : setValue];
|
|
11
10
|
}
|
|
12
|
-
|
|
13
11
|
function querySelector(selector) {
|
|
14
12
|
// Guard against selector being an invalid CSS selector
|
|
15
13
|
try {
|
|
@@ -18,24 +16,19 @@ function querySelector(selector) {
|
|
|
18
16
|
return null;
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
|
-
|
|
22
19
|
function scrollToHash(hash, fallbackTop) {
|
|
23
20
|
const el = querySelector(`#${hash}`);
|
|
24
|
-
|
|
25
21
|
if (el) {
|
|
26
22
|
el.scrollIntoView();
|
|
27
23
|
} else if (fallbackTop) {
|
|
28
24
|
window.scrollTo(0, 0);
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
|
-
|
|
32
27
|
function createIntegration(get, set, init, utils) {
|
|
33
28
|
let ignore = false;
|
|
34
|
-
|
|
35
29
|
const wrap = value => typeof value === "string" ? {
|
|
36
30
|
value
|
|
37
31
|
} : value;
|
|
38
|
-
|
|
39
32
|
const signal = intercept(createSignal(wrap(get()), {
|
|
40
33
|
equals: (a, b) => a.value === b.value
|
|
41
34
|
}), undefined, next => {
|
|
@@ -64,7 +57,6 @@ function normalizeIntegration(integration) {
|
|
|
64
57
|
signal: integration
|
|
65
58
|
};
|
|
66
59
|
}
|
|
67
|
-
|
|
68
60
|
return integration;
|
|
69
61
|
}
|
|
70
62
|
function staticIntegration(obj) {
|
|
@@ -87,7 +79,6 @@ function pathIntegration() {
|
|
|
87
79
|
} else {
|
|
88
80
|
window.history.pushState(state, "", value);
|
|
89
81
|
}
|
|
90
|
-
|
|
91
82
|
scrollToHash(window.location.hash.slice(1), scroll);
|
|
92
83
|
}, notify => bindEvent(window, "popstate", () => notify()), {
|
|
93
84
|
go: delta => window.history.go(delta)
|
|
@@ -105,7 +96,6 @@ function hashIntegration() {
|
|
|
105
96
|
} else {
|
|
106
97
|
window.location.hash = value;
|
|
107
98
|
}
|
|
108
|
-
|
|
109
99
|
const hashIndex = value.indexOf("#");
|
|
110
100
|
const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
|
|
111
101
|
scrollToHash(hash, scroll);
|
|
@@ -113,37 +103,63 @@ function hashIntegration() {
|
|
|
113
103
|
go: delta => window.history.go(delta),
|
|
114
104
|
renderPath: path => `#${path}`,
|
|
115
105
|
parsePath: str => {
|
|
116
|
-
const to = str.replace(/^.*?#/, "");
|
|
106
|
+
const to = str.replace(/^.*?#/, "");
|
|
107
|
+
// Hash-only hrefs like `#foo` from plain anchors will come in as `/#foo` whereas a link to
|
|
117
108
|
// `/foo` will be `/#/foo`. Check if the to starts with a `/` and if not append it as a hash
|
|
118
109
|
// to the current path so we can handle these in-page anchors correctly.
|
|
119
|
-
|
|
120
110
|
if (!to.startsWith("/")) {
|
|
121
111
|
const [, path = "/"] = window.location.hash.split("#", 2);
|
|
122
112
|
return `${path}#${to}`;
|
|
123
113
|
}
|
|
124
|
-
|
|
125
114
|
return to;
|
|
126
115
|
}
|
|
127
116
|
});
|
|
128
117
|
}
|
|
129
118
|
|
|
119
|
+
function createBeforeLeave() {
|
|
120
|
+
let listeners = new Set();
|
|
121
|
+
function subscribe(listener) {
|
|
122
|
+
listeners.add(listener);
|
|
123
|
+
return () => listeners.delete(listener);
|
|
124
|
+
}
|
|
125
|
+
let ignore = false;
|
|
126
|
+
function confirm(to, options) {
|
|
127
|
+
if (ignore) return !(ignore = false);
|
|
128
|
+
const e = {
|
|
129
|
+
to,
|
|
130
|
+
options,
|
|
131
|
+
defaultPrevented: false,
|
|
132
|
+
preventDefault: () => e.defaultPrevented = true
|
|
133
|
+
};
|
|
134
|
+
for (const l of listeners) l.listener({
|
|
135
|
+
...e,
|
|
136
|
+
from: l.location,
|
|
137
|
+
retry: force => {
|
|
138
|
+
force && (ignore = true);
|
|
139
|
+
l.navigate(to, options);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return !e.defaultPrevented;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
subscribe,
|
|
146
|
+
confirm
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
130
150
|
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
|
131
151
|
const trimPathRegex = /^\/+|\/+$/g;
|
|
132
|
-
|
|
133
|
-
function normalize(path, omitSlash = false) {
|
|
152
|
+
function normalizePath(path, omitSlash = false) {
|
|
134
153
|
const s = path.replace(trimPathRegex, "");
|
|
135
154
|
return s ? omitSlash || /^[?#]/.test(s) ? s : "/" + s : "";
|
|
136
155
|
}
|
|
137
|
-
|
|
138
156
|
function resolvePath(base, path, from) {
|
|
139
157
|
if (hasSchemeRegex.test(path)) {
|
|
140
158
|
return undefined;
|
|
141
159
|
}
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const fromPath = from && normalize(from);
|
|
160
|
+
const basePath = normalizePath(base);
|
|
161
|
+
const fromPath = from && normalizePath(from);
|
|
145
162
|
let result = "";
|
|
146
|
-
|
|
147
163
|
if (!fromPath || path.startsWith("/")) {
|
|
148
164
|
result = basePath;
|
|
149
165
|
} else if (fromPath.toLowerCase().indexOf(basePath.toLowerCase()) !== 0) {
|
|
@@ -151,18 +167,16 @@ function resolvePath(base, path, from) {
|
|
|
151
167
|
} else {
|
|
152
168
|
result = fromPath;
|
|
153
169
|
}
|
|
154
|
-
|
|
155
|
-
return (result || "/") + normalize(path, !result);
|
|
170
|
+
return (result || "/") + normalizePath(path, !result);
|
|
156
171
|
}
|
|
157
172
|
function invariant(value, message) {
|
|
158
173
|
if (value == null) {
|
|
159
174
|
throw new Error(message);
|
|
160
175
|
}
|
|
161
|
-
|
|
162
176
|
return value;
|
|
163
177
|
}
|
|
164
178
|
function joinPaths(from, to) {
|
|
165
|
-
return
|
|
179
|
+
return normalizePath(from).replace(/\/*(\*.*)?$/g, "") + normalizePath(to);
|
|
166
180
|
}
|
|
167
181
|
function extractSearchParams(url) {
|
|
168
182
|
const params = {};
|
|
@@ -171,9 +185,6 @@ function extractSearchParams(url) {
|
|
|
171
185
|
});
|
|
172
186
|
return params;
|
|
173
187
|
}
|
|
174
|
-
function urlDecode(str, isQuery) {
|
|
175
|
-
return decodeURIComponent(isQuery ? str.replace(/\+/g, " ") : str);
|
|
176
|
-
}
|
|
177
188
|
function createMatcher(path, partial) {
|
|
178
189
|
const [pattern, splat] = path.split("/*", 2);
|
|
179
190
|
const segments = pattern.split("/").filter(Boolean);
|
|
@@ -181,20 +192,16 @@ function createMatcher(path, partial) {
|
|
|
181
192
|
return location => {
|
|
182
193
|
const locSegments = location.split("/").filter(Boolean);
|
|
183
194
|
const lenDiff = locSegments.length - len;
|
|
184
|
-
|
|
185
195
|
if (lenDiff < 0 || lenDiff > 0 && splat === undefined && !partial) {
|
|
186
196
|
return null;
|
|
187
197
|
}
|
|
188
|
-
|
|
189
198
|
const match = {
|
|
190
199
|
path: len ? "" : "/",
|
|
191
200
|
params: {}
|
|
192
201
|
};
|
|
193
|
-
|
|
194
202
|
for (let i = 0; i < len; i++) {
|
|
195
203
|
const segment = segments[i];
|
|
196
204
|
const locSegment = locSegments[i];
|
|
197
|
-
|
|
198
205
|
if (segment[0] === ":") {
|
|
199
206
|
match.params[segment.slice(1)] = locSegment;
|
|
200
207
|
} else if (segment.localeCompare(locSegment, undefined, {
|
|
@@ -202,14 +209,11 @@ function createMatcher(path, partial) {
|
|
|
202
209
|
}) !== 0) {
|
|
203
210
|
return null;
|
|
204
211
|
}
|
|
205
|
-
|
|
206
212
|
match.path += `/${locSegment}`;
|
|
207
213
|
}
|
|
208
|
-
|
|
209
214
|
if (splat) {
|
|
210
215
|
match.params[splat] = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
211
216
|
}
|
|
212
|
-
|
|
213
217
|
return match;
|
|
214
218
|
};
|
|
215
219
|
}
|
|
@@ -226,21 +230,17 @@ function createMemoObject(fn) {
|
|
|
226
230
|
if (!map.has(property)) {
|
|
227
231
|
runWithOwner(owner, () => map.set(property, createMemo(() => fn()[property])));
|
|
228
232
|
}
|
|
229
|
-
|
|
230
233
|
return map.get(property)();
|
|
231
234
|
},
|
|
232
|
-
|
|
233
235
|
getOwnPropertyDescriptor() {
|
|
234
236
|
return {
|
|
235
237
|
enumerable: true,
|
|
236
238
|
configurable: true
|
|
237
239
|
};
|
|
238
240
|
},
|
|
239
|
-
|
|
240
241
|
ownKeys() {
|
|
241
242
|
return Reflect.ownKeys(fn());
|
|
242
243
|
}
|
|
243
|
-
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
function mergeSearchString(search, params) {
|
|
@@ -260,17 +260,17 @@ function expandOptionals(pattern) {
|
|
|
260
260
|
if (!match) return [pattern];
|
|
261
261
|
let prefix = pattern.slice(0, match.index);
|
|
262
262
|
let suffix = pattern.slice(match.index + match[0].length);
|
|
263
|
-
const prefixes = [prefix, prefix += match[1]];
|
|
263
|
+
const prefixes = [prefix, prefix += match[1]];
|
|
264
|
+
|
|
265
|
+
// This section handles adjacent optional params. We don't actually want all permuations since
|
|
264
266
|
// that will lead to equivalent routes which have the same number of params. For example
|
|
265
267
|
// `/:a?/:b?/:c`? only has the unique expansion: `/`, `/:a`, `/:a/:b`, `/:a/:b/:c` and we can
|
|
266
268
|
// discard `/:b`, `/:c`, `/:b/:c` by building them up in order and not recursing. This also helps
|
|
267
269
|
// ensure predictability where earlier params have precidence.
|
|
268
|
-
|
|
269
270
|
while (match = /^(\/\:[^\/]+)\?/.exec(suffix)) {
|
|
270
271
|
prefixes.push(prefix += match[1]);
|
|
271
272
|
suffix = suffix.slice(match[0].length);
|
|
272
273
|
}
|
|
273
|
-
|
|
274
274
|
return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []);
|
|
275
275
|
}
|
|
276
276
|
|
|
@@ -296,25 +296,37 @@ const useLocation = () => useRouter().location;
|
|
|
296
296
|
const useIsRouting = () => useRouter().isRouting;
|
|
297
297
|
const useMatch = path => {
|
|
298
298
|
const location = useLocation();
|
|
299
|
-
const
|
|
300
|
-
return createMemo(() =>
|
|
299
|
+
const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path)));
|
|
300
|
+
return createMemo(() => {
|
|
301
|
+
for (const matcher of matchers()) {
|
|
302
|
+
const match = matcher(location.pathname);
|
|
303
|
+
if (match) return match;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
301
306
|
};
|
|
302
307
|
const useParams = () => useRoute().params;
|
|
303
308
|
const useRouteData = () => useRoute().data;
|
|
304
309
|
const useSearchParams = () => {
|
|
305
310
|
const location = useLocation();
|
|
306
311
|
const navigate = useNavigate();
|
|
307
|
-
|
|
308
312
|
const setSearchParams = (params, options) => {
|
|
309
313
|
const searchString = untrack(() => mergeSearchString(location.search, params));
|
|
310
|
-
navigate(location.pathname + searchString, {
|
|
314
|
+
navigate(location.pathname + searchString + location.hash, {
|
|
311
315
|
scroll: false,
|
|
316
|
+
resolve: false,
|
|
312
317
|
...options
|
|
313
318
|
});
|
|
314
319
|
};
|
|
315
|
-
|
|
316
320
|
return [location.query, setSearchParams];
|
|
317
321
|
};
|
|
322
|
+
const useBeforeLeave = listener => {
|
|
323
|
+
const s = useRouter().beforeLeave.subscribe({
|
|
324
|
+
listener,
|
|
325
|
+
location: useLocation(),
|
|
326
|
+
navigate: useNavigate()
|
|
327
|
+
});
|
|
328
|
+
onCleanup(s);
|
|
329
|
+
};
|
|
318
330
|
function createRoutes(routeDef, base = "", fallback) {
|
|
319
331
|
const {
|
|
320
332
|
component,
|
|
@@ -337,13 +349,13 @@ function createRoutes(routeDef, base = "", fallback) {
|
|
|
337
349
|
for (const originalPath of expandOptionals(path)) {
|
|
338
350
|
const path = joinPaths(base, originalPath);
|
|
339
351
|
const pattern = isLeaf ? path : path.split("/*", 1)[0];
|
|
340
|
-
acc.push({
|
|
352
|
+
acc.push({
|
|
353
|
+
...shared,
|
|
341
354
|
originalPath,
|
|
342
355
|
pattern,
|
|
343
356
|
matcher: createMatcher(pattern, !isLeaf)
|
|
344
357
|
});
|
|
345
358
|
}
|
|
346
|
-
|
|
347
359
|
return acc;
|
|
348
360
|
}, []);
|
|
349
361
|
}
|
|
@@ -351,76 +363,62 @@ function createBranch(routes, index = 0) {
|
|
|
351
363
|
return {
|
|
352
364
|
routes,
|
|
353
365
|
score: scoreRoute(routes[routes.length - 1]) * 10000 - index,
|
|
354
|
-
|
|
355
366
|
matcher(location) {
|
|
356
367
|
const matches = [];
|
|
357
|
-
|
|
358
368
|
for (let i = routes.length - 1; i >= 0; i--) {
|
|
359
369
|
const route = routes[i];
|
|
360
370
|
const match = route.matcher(location);
|
|
361
|
-
|
|
362
371
|
if (!match) {
|
|
363
372
|
return null;
|
|
364
373
|
}
|
|
365
|
-
|
|
366
|
-
|
|
374
|
+
matches.unshift({
|
|
375
|
+
...match,
|
|
367
376
|
route
|
|
368
377
|
});
|
|
369
378
|
}
|
|
370
|
-
|
|
371
379
|
return matches;
|
|
372
380
|
}
|
|
373
|
-
|
|
374
381
|
};
|
|
375
382
|
}
|
|
376
|
-
|
|
377
383
|
function asArray(value) {
|
|
378
384
|
return Array.isArray(value) ? value : [value];
|
|
379
385
|
}
|
|
380
|
-
|
|
381
386
|
function createBranches(routeDef, base = "", fallback, stack = [], branches = []) {
|
|
382
387
|
const routeDefs = asArray(routeDef);
|
|
383
|
-
|
|
384
388
|
for (let i = 0, len = routeDefs.length; i < len; i++) {
|
|
385
389
|
const def = routeDefs[i];
|
|
386
|
-
|
|
387
390
|
if (def && typeof def === "object" && def.hasOwnProperty("path")) {
|
|
388
391
|
const routes = createRoutes(def, base, fallback);
|
|
389
|
-
|
|
390
392
|
for (const route of routes) {
|
|
391
393
|
stack.push(route);
|
|
392
|
-
|
|
393
|
-
if (def.children) {
|
|
394
|
+
const isEmptyArray = Array.isArray(def.children) && def.children.length === 0;
|
|
395
|
+
if (def.children && !isEmptyArray) {
|
|
394
396
|
createBranches(def.children, route.pattern, fallback, stack, branches);
|
|
395
397
|
} else {
|
|
396
398
|
const branch = createBranch([...stack], branches.length);
|
|
397
399
|
branches.push(branch);
|
|
398
400
|
}
|
|
399
|
-
|
|
400
401
|
stack.pop();
|
|
401
402
|
}
|
|
402
403
|
}
|
|
403
|
-
}
|
|
404
|
-
|
|
404
|
+
}
|
|
405
405
|
|
|
406
|
+
// Stack will be empty on final return
|
|
406
407
|
return stack.length ? branches : branches.sort((a, b) => b.score - a.score);
|
|
407
408
|
}
|
|
408
409
|
function getRouteMatches(branches, location) {
|
|
409
410
|
for (let i = 0, len = branches.length; i < len; i++) {
|
|
410
411
|
const match = branches[i].matcher(location);
|
|
411
|
-
|
|
412
412
|
if (match) {
|
|
413
413
|
return match;
|
|
414
414
|
}
|
|
415
415
|
}
|
|
416
|
-
|
|
417
416
|
return [];
|
|
418
417
|
}
|
|
419
418
|
function createLocation(path, state) {
|
|
420
419
|
const origin = new URL("http://sar");
|
|
421
420
|
const url = createMemo(prev => {
|
|
422
421
|
const path_ = path();
|
|
423
|
-
|
|
424
422
|
try {
|
|
425
423
|
return new URL(path_, origin);
|
|
426
424
|
} catch (err) {
|
|
@@ -430,31 +428,26 @@ function createLocation(path, state) {
|
|
|
430
428
|
}, origin, {
|
|
431
429
|
equals: (a, b) => a.href === b.href
|
|
432
430
|
});
|
|
433
|
-
const pathname = createMemo(() =>
|
|
434
|
-
const search = createMemo(() =>
|
|
435
|
-
const hash = createMemo(() =>
|
|
431
|
+
const pathname = createMemo(() => url().pathname);
|
|
432
|
+
const search = createMemo(() => url().search, true);
|
|
433
|
+
const hash = createMemo(() => url().hash);
|
|
436
434
|
const key = createMemo(() => "");
|
|
437
435
|
return {
|
|
438
436
|
get pathname() {
|
|
439
437
|
return pathname();
|
|
440
438
|
},
|
|
441
|
-
|
|
442
439
|
get search() {
|
|
443
440
|
return search();
|
|
444
441
|
},
|
|
445
|
-
|
|
446
442
|
get hash() {
|
|
447
443
|
return hash();
|
|
448
444
|
},
|
|
449
|
-
|
|
450
445
|
get state() {
|
|
451
446
|
return state();
|
|
452
447
|
},
|
|
453
|
-
|
|
454
448
|
get key() {
|
|
455
449
|
return key();
|
|
456
450
|
},
|
|
457
|
-
|
|
458
451
|
query: createMemoObject(on(search, () => extractSearchParams(url())))
|
|
459
452
|
};
|
|
460
453
|
}
|
|
@@ -463,17 +456,14 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
463
456
|
signal: [source, setSource],
|
|
464
457
|
utils = {}
|
|
465
458
|
} = normalizeIntegration(integration);
|
|
466
|
-
|
|
467
459
|
const parsePath = utils.parsePath || (p => p);
|
|
468
|
-
|
|
469
460
|
const renderPath = utils.renderPath || (p => p);
|
|
470
|
-
|
|
461
|
+
const beforeLeave = utils.beforeLeave || createBeforeLeave();
|
|
471
462
|
const basePath = resolvePath("", base);
|
|
472
463
|
const output = isServer && out ? Object.assign(out, {
|
|
473
464
|
matches: [],
|
|
474
465
|
url: undefined
|
|
475
466
|
}) : undefined;
|
|
476
|
-
|
|
477
467
|
if (basePath === undefined) {
|
|
478
468
|
throw new Error(`${basePath} is not a valid base path`);
|
|
479
469
|
} else if (basePath && !source().value) {
|
|
@@ -483,19 +473,15 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
483
473
|
scroll: false
|
|
484
474
|
});
|
|
485
475
|
}
|
|
486
|
-
|
|
487
476
|
const [isRouting, setIsRouting] = createSignal(false);
|
|
488
|
-
|
|
489
477
|
const start = async callback => {
|
|
490
478
|
setIsRouting(true);
|
|
491
|
-
|
|
492
479
|
try {
|
|
493
480
|
await startTransition(callback);
|
|
494
481
|
} finally {
|
|
495
482
|
setIsRouting(false);
|
|
496
483
|
}
|
|
497
484
|
};
|
|
498
|
-
|
|
499
485
|
const [reference, setReference] = createSignal(source().value);
|
|
500
486
|
const [state, setState] = createSignal(source().state);
|
|
501
487
|
const location = createLocation(reference, state);
|
|
@@ -505,13 +491,10 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
505
491
|
params: {},
|
|
506
492
|
path: () => basePath,
|
|
507
493
|
outlet: () => null,
|
|
508
|
-
|
|
509
494
|
resolvePath(to) {
|
|
510
495
|
return resolvePath(basePath, to);
|
|
511
496
|
}
|
|
512
|
-
|
|
513
497
|
};
|
|
514
|
-
|
|
515
498
|
if (data) {
|
|
516
499
|
try {
|
|
517
500
|
TempRoute = baseRoute;
|
|
@@ -525,20 +508,17 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
525
508
|
TempRoute = undefined;
|
|
526
509
|
}
|
|
527
510
|
}
|
|
528
|
-
|
|
529
511
|
function navigateFromRoute(route, to, options) {
|
|
530
512
|
// Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
|
|
531
513
|
untrack(() => {
|
|
532
514
|
if (typeof to === "number") {
|
|
533
515
|
if (!to) ; else if (utils.go) {
|
|
534
|
-
utils.go(to);
|
|
516
|
+
beforeLeave.confirm(to, options) && utils.go(to);
|
|
535
517
|
} else {
|
|
536
518
|
console.warn("Router integration does not support relative routing");
|
|
537
519
|
}
|
|
538
|
-
|
|
539
520
|
return;
|
|
540
521
|
}
|
|
541
|
-
|
|
542
522
|
const {
|
|
543
523
|
replace,
|
|
544
524
|
resolve,
|
|
@@ -551,28 +531,24 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
551
531
|
...options
|
|
552
532
|
};
|
|
553
533
|
const resolvedTo = resolve ? route.resolvePath(to) : resolvePath("", to);
|
|
554
|
-
|
|
555
534
|
if (resolvedTo === undefined) {
|
|
556
535
|
throw new Error(`Path '${to}' is not a routable path`);
|
|
557
536
|
} else if (referrers.length >= MAX_REDIRECTS) {
|
|
558
537
|
throw new Error("Too many redirects");
|
|
559
538
|
}
|
|
560
|
-
|
|
561
539
|
const current = reference();
|
|
562
|
-
|
|
563
540
|
if (resolvedTo !== current || nextState !== state()) {
|
|
564
541
|
if (isServer) {
|
|
565
542
|
if (output) {
|
|
566
543
|
output.url = resolvedTo;
|
|
567
544
|
}
|
|
568
|
-
|
|
569
545
|
setSource({
|
|
570
546
|
value: resolvedTo,
|
|
571
547
|
replace,
|
|
572
548
|
scroll,
|
|
573
549
|
state: nextState
|
|
574
550
|
});
|
|
575
|
-
} else {
|
|
551
|
+
} else if (beforeLeave.confirm(resolvedTo, options)) {
|
|
576
552
|
const len = referrers.push({
|
|
577
553
|
value: current,
|
|
578
554
|
replace,
|
|
@@ -595,34 +571,30 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
595
571
|
}
|
|
596
572
|
});
|
|
597
573
|
}
|
|
598
|
-
|
|
599
574
|
function navigatorFactory(route) {
|
|
600
575
|
// Workaround for vite issue (https://github.com/vitejs/vite/issues/3803)
|
|
601
576
|
route = route || useContext(RouteContextObj) || baseRoute;
|
|
602
577
|
return (to, options) => navigateFromRoute(route, to, options);
|
|
603
578
|
}
|
|
604
|
-
|
|
605
579
|
function navigateEnd(next) {
|
|
606
580
|
const first = referrers[0];
|
|
607
|
-
|
|
608
581
|
if (first) {
|
|
609
582
|
if (next.value !== first.value || next.state !== first.state) {
|
|
610
|
-
setSource({
|
|
583
|
+
setSource({
|
|
584
|
+
...next,
|
|
611
585
|
replace: first.replace,
|
|
612
586
|
scroll: first.scroll
|
|
613
587
|
});
|
|
614
588
|
}
|
|
615
|
-
|
|
616
589
|
referrers.length = 0;
|
|
617
590
|
}
|
|
618
591
|
}
|
|
619
|
-
|
|
620
592
|
createRenderEffect(() => {
|
|
621
593
|
const {
|
|
622
594
|
value,
|
|
623
595
|
state
|
|
624
|
-
} = source();
|
|
625
|
-
|
|
596
|
+
} = source();
|
|
597
|
+
// Untrack this whole block so `start` doesn't cause Solid's Listener to be preserved
|
|
626
598
|
untrack(() => {
|
|
627
599
|
if (value !== reference()) {
|
|
628
600
|
start(() => {
|
|
@@ -632,7 +604,6 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
632
604
|
}
|
|
633
605
|
});
|
|
634
606
|
});
|
|
635
|
-
|
|
636
607
|
if (!isServer) {
|
|
637
608
|
function handleAnchorClick(evt) {
|
|
638
609
|
if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
|
|
@@ -643,9 +614,8 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
643
614
|
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
|
644
615
|
if (a.hasAttribute("download") || rel && rel.includes("external")) return;
|
|
645
616
|
const url = new URL(href);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const to = parsePath(pathname + urlDecode(url.search, true) + urlDecode(url.hash));
|
|
617
|
+
if (url.origin !== window.location.origin || basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
|
|
618
|
+
const to = parsePath(url.pathname + url.search + url.hash);
|
|
649
619
|
const state = a.getAttribute("state");
|
|
650
620
|
evt.preventDefault();
|
|
651
621
|
navigateFromRoute(baseRoute, to, {
|
|
@@ -654,14 +624,13 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
654
624
|
scroll: !a.hasAttribute("noscroll"),
|
|
655
625
|
state: state && JSON.parse(state)
|
|
656
626
|
});
|
|
657
|
-
}
|
|
658
|
-
|
|
627
|
+
}
|
|
659
628
|
|
|
629
|
+
// ensure delegated events run first
|
|
660
630
|
delegateEvents(["click"]);
|
|
661
631
|
document.addEventListener("click", handleAnchorClick);
|
|
662
632
|
onCleanup(() => document.removeEventListener("click", handleAnchorClick));
|
|
663
633
|
}
|
|
664
|
-
|
|
665
634
|
return {
|
|
666
635
|
base: baseRoute,
|
|
667
636
|
out: output,
|
|
@@ -669,7 +638,8 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
669
638
|
isRouting,
|
|
670
639
|
renderPath,
|
|
671
640
|
parsePath,
|
|
672
|
-
navigatorFactory
|
|
641
|
+
navigatorFactory,
|
|
642
|
+
beforeLeave
|
|
673
643
|
};
|
|
674
644
|
}
|
|
675
645
|
function createRouteContext(router, parent, child, match) {
|
|
@@ -690,22 +660,17 @@ function createRouteContext(router, parent, child, match) {
|
|
|
690
660
|
const route = {
|
|
691
661
|
parent,
|
|
692
662
|
pattern,
|
|
693
|
-
|
|
694
663
|
get child() {
|
|
695
664
|
return child();
|
|
696
665
|
},
|
|
697
|
-
|
|
698
666
|
path,
|
|
699
667
|
params,
|
|
700
668
|
data: parent.data,
|
|
701
669
|
outlet,
|
|
702
|
-
|
|
703
670
|
resolvePath(to) {
|
|
704
671
|
return resolvePath(base.path(), to, path());
|
|
705
672
|
}
|
|
706
|
-
|
|
707
673
|
};
|
|
708
|
-
|
|
709
674
|
if (data) {
|
|
710
675
|
try {
|
|
711
676
|
TempRoute = route;
|
|
@@ -719,7 +684,6 @@ function createRouteContext(router, parent, child, match) {
|
|
|
719
684
|
TempRoute = undefined;
|
|
720
685
|
}
|
|
721
686
|
}
|
|
722
|
-
|
|
723
687
|
return route;
|
|
724
688
|
}
|
|
725
689
|
|
|
@@ -738,11 +702,9 @@ const Router = props => {
|
|
|
738
702
|
const routerState = createRouterContext(integration, base, data, out);
|
|
739
703
|
return createComponent$1(RouterContextObj.Provider, {
|
|
740
704
|
value: routerState,
|
|
741
|
-
|
|
742
705
|
get children() {
|
|
743
706
|
return props.children;
|
|
744
707
|
}
|
|
745
|
-
|
|
746
708
|
});
|
|
747
709
|
};
|
|
748
710
|
const Routes = props => {
|
|
@@ -751,7 +713,6 @@ const Routes = props => {
|
|
|
751
713
|
const routeDefs = children(() => props.children);
|
|
752
714
|
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
|
|
753
715
|
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
|
|
754
|
-
|
|
755
716
|
if (router.out) {
|
|
756
717
|
router.out.matches.push(matches().map(({
|
|
757
718
|
route,
|
|
@@ -764,39 +725,31 @@ const Routes = props => {
|
|
|
764
725
|
params
|
|
765
726
|
})));
|
|
766
727
|
}
|
|
767
|
-
|
|
768
728
|
const disposers = [];
|
|
769
729
|
let root;
|
|
770
730
|
const routeStates = createMemo(on(matches, (nextMatches, prevMatches, prev) => {
|
|
771
731
|
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
|
772
732
|
const next = [];
|
|
773
|
-
|
|
774
733
|
for (let i = 0, len = nextMatches.length; i < len; i++) {
|
|
775
734
|
const prevMatch = prevMatches && prevMatches[i];
|
|
776
735
|
const nextMatch = nextMatches[i];
|
|
777
|
-
|
|
778
736
|
if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
|
|
779
737
|
next[i] = prev[i];
|
|
780
738
|
} else {
|
|
781
739
|
equal = false;
|
|
782
|
-
|
|
783
740
|
if (disposers[i]) {
|
|
784
741
|
disposers[i]();
|
|
785
742
|
}
|
|
786
|
-
|
|
787
743
|
createRoot(dispose => {
|
|
788
744
|
disposers[i] = dispose;
|
|
789
745
|
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i]);
|
|
790
746
|
});
|
|
791
747
|
}
|
|
792
748
|
}
|
|
793
|
-
|
|
794
749
|
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
795
|
-
|
|
796
750
|
if (prev && equal) {
|
|
797
751
|
return prev;
|
|
798
752
|
}
|
|
799
|
-
|
|
800
753
|
root = next[0];
|
|
801
754
|
return next;
|
|
802
755
|
}));
|
|
@@ -804,14 +757,11 @@ const Routes = props => {
|
|
|
804
757
|
get when() {
|
|
805
758
|
return routeStates() && root;
|
|
806
759
|
},
|
|
807
|
-
|
|
808
760
|
children: route => createComponent$1(RouteContextObj.Provider, {
|
|
809
761
|
value: route,
|
|
810
|
-
|
|
811
762
|
get children() {
|
|
812
763
|
return route.outlet();
|
|
813
764
|
}
|
|
814
|
-
|
|
815
765
|
})
|
|
816
766
|
});
|
|
817
767
|
};
|
|
@@ -827,7 +777,6 @@ const Route = props => {
|
|
|
827
777
|
get children() {
|
|
828
778
|
return childRoutes();
|
|
829
779
|
}
|
|
830
|
-
|
|
831
780
|
});
|
|
832
781
|
};
|
|
833
782
|
const Outlet = () => {
|
|
@@ -836,14 +785,11 @@ const Outlet = () => {
|
|
|
836
785
|
get when() {
|
|
837
786
|
return route.child;
|
|
838
787
|
},
|
|
839
|
-
|
|
840
788
|
children: child => createComponent$1(RouteContextObj.Provider, {
|
|
841
789
|
value: child,
|
|
842
|
-
|
|
843
790
|
get children() {
|
|
844
791
|
return child.outlet();
|
|
845
792
|
}
|
|
846
|
-
|
|
847
793
|
})
|
|
848
794
|
});
|
|
849
795
|
};
|
|
@@ -852,47 +798,43 @@ function A(props) {
|
|
|
852
798
|
inactiveClass: "inactive",
|
|
853
799
|
activeClass: "active"
|
|
854
800
|
}, props);
|
|
855
|
-
const [, rest] = splitProps(props, ["href", "state", "activeClass", "inactiveClass", "end"]);
|
|
801
|
+
const [, rest] = splitProps(props, ["href", "state", "class", "activeClass", "inactiveClass", "end"]);
|
|
856
802
|
const to = useResolvedPath(() => props.href);
|
|
857
803
|
const href = useHref(to);
|
|
858
804
|
const location = useLocation();
|
|
859
805
|
const isActive = createMemo(() => {
|
|
860
806
|
const to_ = to();
|
|
861
807
|
if (to_ === undefined) return false;
|
|
862
|
-
const path = to_.split(/[?#]/, 1)[0].toLowerCase();
|
|
863
|
-
const loc = location.pathname.toLowerCase();
|
|
808
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase();
|
|
809
|
+
const loc = normalizePath(location.pathname).toLowerCase();
|
|
864
810
|
return props.end ? path === loc : loc.startsWith(path);
|
|
865
811
|
});
|
|
866
812
|
return (() => {
|
|
867
813
|
const _el$ = _tmpl$.cloneNode(true);
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
effect(_p$ => {
|
|
872
|
-
const _v$ = href() || props.href,
|
|
873
|
-
_v$2 = JSON.stringify(props.state),
|
|
874
|
-
_v$3 = {
|
|
875
|
-
[props.inactiveClass]: !isActive(),
|
|
876
|
-
[props.activeClass]: isActive(),
|
|
877
|
-
...rest.classList
|
|
814
|
+
spread(_el$, mergeProps$1(rest, {
|
|
815
|
+
get href() {
|
|
816
|
+
return href() || props.href;
|
|
878
817
|
},
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
818
|
+
get state() {
|
|
819
|
+
return JSON.stringify(props.state);
|
|
820
|
+
},
|
|
821
|
+
get classList() {
|
|
822
|
+
return {
|
|
823
|
+
...(props.class && {
|
|
824
|
+
[props.class]: true
|
|
825
|
+
}),
|
|
826
|
+
[props.inactiveClass]: !isActive(),
|
|
827
|
+
[props.activeClass]: isActive(),
|
|
828
|
+
...rest.classList
|
|
829
|
+
};
|
|
830
|
+
},
|
|
831
|
+
get ["aria-current"]() {
|
|
832
|
+
return isActive() ? "page" : undefined;
|
|
833
|
+
}
|
|
834
|
+
}), false, false);
|
|
893
835
|
return _el$;
|
|
894
836
|
})();
|
|
895
|
-
}
|
|
837
|
+
}
|
|
896
838
|
function Navigate(props) {
|
|
897
839
|
const navigate = useNavigate();
|
|
898
840
|
const location = useLocation();
|
|
@@ -911,4 +853,4 @@ function Navigate(props) {
|
|
|
911
853
|
return null;
|
|
912
854
|
}
|
|
913
855
|
|
|
914
|
-
export { A, A as Link, A as NavLink, Navigate, Outlet, Route, Router, Routes, mergeSearchString as _mergeSearchString, createIntegration, hashIntegration, normalizeIntegration, pathIntegration, staticIntegration, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useRouteData, useRoutes, useSearchParams };
|
|
856
|
+
export { A, A as Link, A as NavLink, Navigate, Outlet, Route, Router, Routes, mergeSearchString as _mergeSearchString, createBeforeLeave, createIntegration, hashIntegration, normalizeIntegration, pathIntegration, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useRouteData, useRoutes, useSearchParams };
|
package/dist/index.jsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from "./components";
|
|
2
2
|
export * from "./integration";
|
|
3
|
-
export
|
|
3
|
+
export * from "./lifecycle";
|
|
4
|
+
export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
|
|
4
5
|
export { mergeSearchString as _mergeSearchString } from "./utils";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function createBeforeLeave() {
|
|
2
|
+
let listeners = new Set();
|
|
3
|
+
function subscribe(listener) {
|
|
4
|
+
listeners.add(listener);
|
|
5
|
+
return () => listeners.delete(listener);
|
|
6
|
+
}
|
|
7
|
+
let ignore = false;
|
|
8
|
+
function confirm(to, options) {
|
|
9
|
+
if (ignore)
|
|
10
|
+
return !(ignore = false);
|
|
11
|
+
const e = {
|
|
12
|
+
to,
|
|
13
|
+
options,
|
|
14
|
+
defaultPrevented: false,
|
|
15
|
+
preventDefault: () => (e.defaultPrevented = true)
|
|
16
|
+
};
|
|
17
|
+
for (const l of listeners)
|
|
18
|
+
l.listener({
|
|
19
|
+
...e,
|
|
20
|
+
from: l.location,
|
|
21
|
+
retry: (force) => {
|
|
22
|
+
force && (ignore = true);
|
|
23
|
+
l.navigate(to, options);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return !e.defaultPrevented;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
subscribe,
|
|
30
|
+
confirm
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/routing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component, Accessor } from "solid-js";
|
|
2
|
-
import type { Branch, Location, LocationChangeSignal, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDataFunc, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
|
|
2
|
+
import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, 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,11 +9,12 @@ 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: () => string) => Accessor<import("./types").PathMatch |
|
|
12
|
+
export declare const useMatch: (path: () => string) => 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>;
|
|
16
16
|
export declare const useSearchParams: <T extends Params>() => [T, (params: SetParams, options?: Partial<NavigateOptions>) => void];
|
|
17
|
+
export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
|
|
17
18
|
export declare function createRoutes(routeDef: RouteDefinition, base?: string, fallback?: Component): Route[];
|
|
18
19
|
export declare function createBranch(routes: Route[], index?: number): Branch;
|
|
19
20
|
export declare function createBranches(routeDef: RouteDefinition | RouteDefinition[], base?: string, fallback?: Component, stack?: Route[], branches?: Branch[]): Branch[];
|
package/dist/routing.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js";
|
|
2
2
|
import { isServer, delegateEvents } from "solid-js/web";
|
|
3
3
|
import { normalizeIntegration } from "./integration";
|
|
4
|
-
import {
|
|
4
|
+
import { createBeforeLeave } from "./lifecycle";
|
|
5
|
+
import { createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils";
|
|
5
6
|
const MAX_REDIRECTS = 100;
|
|
6
7
|
export const RouterContextObj = createContext();
|
|
7
8
|
export const RouteContextObj = createContext();
|
|
@@ -24,8 +25,14 @@ export const useLocation = () => useRouter().location;
|
|
|
24
25
|
export const useIsRouting = () => useRouter().isRouting;
|
|
25
26
|
export const useMatch = (path) => {
|
|
26
27
|
const location = useLocation();
|
|
27
|
-
const
|
|
28
|
-
return createMemo(() =>
|
|
28
|
+
const matchers = createMemo(() => expandOptionals(path()).map((path) => createMatcher(path)));
|
|
29
|
+
return createMemo(() => {
|
|
30
|
+
for (const matcher of matchers()) {
|
|
31
|
+
const match = matcher(location.pathname);
|
|
32
|
+
if (match)
|
|
33
|
+
return match;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
29
36
|
};
|
|
30
37
|
export const useParams = () => useRoute().params;
|
|
31
38
|
export const useRouteData = () => useRoute().data;
|
|
@@ -34,10 +41,14 @@ export const useSearchParams = () => {
|
|
|
34
41
|
const navigate = useNavigate();
|
|
35
42
|
const setSearchParams = (params, options) => {
|
|
36
43
|
const searchString = untrack(() => mergeSearchString(location.search, params));
|
|
37
|
-
navigate(location.pathname + searchString, { scroll: false, ...options });
|
|
44
|
+
navigate(location.pathname + searchString + location.hash, { scroll: false, resolve: false, ...options });
|
|
38
45
|
};
|
|
39
46
|
return [location.query, setSearchParams];
|
|
40
47
|
};
|
|
48
|
+
export const useBeforeLeave = (listener) => {
|
|
49
|
+
const s = useRouter().beforeLeave.subscribe({ listener, location: useLocation(), navigate: useNavigate() });
|
|
50
|
+
onCleanup(s);
|
|
51
|
+
};
|
|
41
52
|
export function createRoutes(routeDef, base = "", fallback) {
|
|
42
53
|
const { component, data, children } = routeDef;
|
|
43
54
|
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
|
@@ -102,7 +113,8 @@ export function createBranches(routeDef, base = "", fallback, stack = [], branch
|
|
|
102
113
|
const routes = createRoutes(def, base, fallback);
|
|
103
114
|
for (const route of routes) {
|
|
104
115
|
stack.push(route);
|
|
105
|
-
|
|
116
|
+
const isEmptyArray = Array.isArray(def.children) && def.children.length === 0;
|
|
117
|
+
if (def.children && !isEmptyArray) {
|
|
106
118
|
createBranches(def.children, route.pattern, fallback, stack, branches);
|
|
107
119
|
}
|
|
108
120
|
else {
|
|
@@ -139,9 +151,9 @@ export function createLocation(path, state) {
|
|
|
139
151
|
}, origin, {
|
|
140
152
|
equals: (a, b) => a.href === b.href
|
|
141
153
|
});
|
|
142
|
-
const pathname = createMemo(() =>
|
|
143
|
-
const search = createMemo(() =>
|
|
144
|
-
const hash = createMemo(() =>
|
|
154
|
+
const pathname = createMemo(() => url().pathname);
|
|
155
|
+
const search = createMemo(() => url().search, true);
|
|
156
|
+
const hash = createMemo(() => url().hash);
|
|
145
157
|
const key = createMemo(() => "");
|
|
146
158
|
return {
|
|
147
159
|
get pathname() {
|
|
@@ -166,6 +178,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
166
178
|
const { signal: [source, setSource], utils = {} } = normalizeIntegration(integration);
|
|
167
179
|
const parsePath = utils.parsePath || (p => p);
|
|
168
180
|
const renderPath = utils.renderPath || (p => p);
|
|
181
|
+
const beforeLeave = utils.beforeLeave || createBeforeLeave();
|
|
169
182
|
const basePath = resolvePath("", base);
|
|
170
183
|
const output = isServer && out
|
|
171
184
|
? Object.assign(out, {
|
|
@@ -224,7 +237,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
224
237
|
// A delta of 0 means stay at the current location, so it is ignored
|
|
225
238
|
}
|
|
226
239
|
else if (utils.go) {
|
|
227
|
-
utils.go(to);
|
|
240
|
+
beforeLeave.confirm(to, options) && utils.go(to);
|
|
228
241
|
}
|
|
229
242
|
else {
|
|
230
243
|
console.warn("Router integration does not support relative routing");
|
|
@@ -252,7 +265,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
252
265
|
}
|
|
253
266
|
setSource({ value: resolvedTo, replace, scroll, state: nextState });
|
|
254
267
|
}
|
|
255
|
-
else {
|
|
268
|
+
else if (beforeLeave.confirm(resolvedTo, options)) {
|
|
256
269
|
const len = referrers.push({ value: current, replace, scroll, state: state() });
|
|
257
270
|
start(() => {
|
|
258
271
|
setReference(resolvedTo);
|
|
@@ -321,11 +334,10 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
321
334
|
if (a.hasAttribute("download") || (rel && rel.includes("external")))
|
|
322
335
|
return;
|
|
323
336
|
const url = new URL(href);
|
|
324
|
-
const pathname = urlDecode(url.pathname);
|
|
325
337
|
if (url.origin !== window.location.origin ||
|
|
326
|
-
(basePath && pathname && !pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
338
|
+
(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
327
339
|
return;
|
|
328
|
-
const to = parsePath(pathname +
|
|
340
|
+
const to = parsePath(url.pathname + url.search + url.hash);
|
|
329
341
|
const state = a.getAttribute("state");
|
|
330
342
|
evt.preventDefault();
|
|
331
343
|
navigateFromRoute(baseRoute, to, {
|
|
@@ -347,7 +359,8 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
347
359
|
isRouting,
|
|
348
360
|
renderPath,
|
|
349
361
|
parsePath,
|
|
350
|
-
navigatorFactory
|
|
362
|
+
navigatorFactory,
|
|
363
|
+
beforeLeave
|
|
351
364
|
};
|
|
352
365
|
}
|
|
353
366
|
export function createRouteContext(router, parent, child, match) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, JSX } from "solid-js";
|
|
2
|
-
export
|
|
3
|
-
export
|
|
2
|
+
export type Params = Record<string, string>;
|
|
3
|
+
export type SetParams = Record<string, string | number | boolean | null | undefined>;
|
|
4
4
|
export interface Path {
|
|
5
5
|
pathname: string;
|
|
6
6
|
search: string;
|
|
@@ -21,14 +21,14 @@ export interface Navigator {
|
|
|
21
21
|
(to: string, options?: Partial<NavigateOptions>): void;
|
|
22
22
|
(delta: number): void;
|
|
23
23
|
}
|
|
24
|
-
export
|
|
24
|
+
export type NavigatorFactory = (route?: RouteContext) => Navigator;
|
|
25
25
|
export interface LocationChange<S = unknown> {
|
|
26
26
|
value: string;
|
|
27
27
|
replace?: boolean;
|
|
28
28
|
scroll?: boolean;
|
|
29
29
|
state?: S;
|
|
30
30
|
}
|
|
31
|
-
export
|
|
31
|
+
export type LocationChangeSignal = [() => LocationChange, (next: LocationChange) => void];
|
|
32
32
|
export interface RouterIntegration {
|
|
33
33
|
signal: LocationChangeSignal;
|
|
34
34
|
utils?: Partial<RouterUtils>;
|
|
@@ -39,8 +39,8 @@ export interface RouteDataFuncArgs<T = unknown> {
|
|
|
39
39
|
location: Location;
|
|
40
40
|
navigate: Navigator;
|
|
41
41
|
}
|
|
42
|
-
export
|
|
43
|
-
export
|
|
42
|
+
export type RouteDataFunc<T = unknown, R = unknown> = (args: RouteDataFuncArgs<T>) => R;
|
|
43
|
+
export type RouteDefinition = {
|
|
44
44
|
path: string | string[];
|
|
45
45
|
data?: RouteDataFunc;
|
|
46
46
|
children?: RouteDefinition | RouteDefinition[];
|
|
@@ -93,6 +93,7 @@ export interface RouterUtils {
|
|
|
93
93
|
renderPath(path: string): string;
|
|
94
94
|
parsePath(str: string): string;
|
|
95
95
|
go(delta: number): void;
|
|
96
|
+
beforeLeave: BeforeLeaveLifecycle;
|
|
96
97
|
}
|
|
97
98
|
export interface OutputMatch {
|
|
98
99
|
originalPath: string;
|
|
@@ -112,4 +113,22 @@ export interface RouterContext {
|
|
|
112
113
|
isRouting: () => boolean;
|
|
113
114
|
renderPath(path: string): string;
|
|
114
115
|
parsePath(str: string): string;
|
|
116
|
+
beforeLeave: BeforeLeaveLifecycle;
|
|
117
|
+
}
|
|
118
|
+
export interface BeforeLeaveEventArgs {
|
|
119
|
+
from: Location;
|
|
120
|
+
to: string | number;
|
|
121
|
+
options?: Partial<NavigateOptions>;
|
|
122
|
+
readonly defaultPrevented: boolean;
|
|
123
|
+
preventDefault(): void;
|
|
124
|
+
retry(force?: boolean): void;
|
|
125
|
+
}
|
|
126
|
+
export interface BeforeLeaveListener {
|
|
127
|
+
listener(e: BeforeLeaveEventArgs): void;
|
|
128
|
+
location: Location;
|
|
129
|
+
navigate: Navigator;
|
|
130
|
+
}
|
|
131
|
+
export interface BeforeLeaveLifecycle {
|
|
132
|
+
subscribe(listener: BeforeLeaveListener): () => void;
|
|
133
|
+
confirm(to: string | number, options?: Partial<NavigateOptions>): boolean;
|
|
115
134
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Params, PathMatch, Route, SetParams } from "./types";
|
|
2
|
+
export declare function normalizePath(path: string, omitSlash?: boolean): string;
|
|
2
3
|
export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
|
|
3
4
|
export declare function invariant<T>(value: T | null | undefined, message: string): T;
|
|
4
5
|
export declare function joinPaths(from: string, to: string): string;
|
|
5
6
|
export declare function extractSearchParams(url: URL): Params;
|
|
6
|
-
export declare function urlDecode(str: string, isQuery?: boolean): string;
|
|
7
7
|
export declare function createMatcher(path: string, partial?: boolean): (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;
|
package/dist/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createMemo, getOwner, runWithOwner } from "solid-js";
|
|
2
2
|
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
|
3
3
|
const trimPathRegex = /^\/+|\/+$/g;
|
|
4
|
-
function
|
|
4
|
+
export function normalizePath(path, omitSlash = false) {
|
|
5
5
|
const s = path.replace(trimPathRegex, "");
|
|
6
6
|
return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : "";
|
|
7
7
|
}
|
|
@@ -9,8 +9,8 @@ export function resolvePath(base, path, from) {
|
|
|
9
9
|
if (hasSchemeRegex.test(path)) {
|
|
10
10
|
return undefined;
|
|
11
11
|
}
|
|
12
|
-
const basePath =
|
|
13
|
-
const fromPath = from &&
|
|
12
|
+
const basePath = normalizePath(base);
|
|
13
|
+
const fromPath = from && normalizePath(from);
|
|
14
14
|
let result = "";
|
|
15
15
|
if (!fromPath || path.startsWith("/")) {
|
|
16
16
|
result = basePath;
|
|
@@ -21,7 +21,7 @@ export function resolvePath(base, path, from) {
|
|
|
21
21
|
else {
|
|
22
22
|
result = fromPath;
|
|
23
23
|
}
|
|
24
|
-
return (result || "/") +
|
|
24
|
+
return (result || "/") + normalizePath(path, !result);
|
|
25
25
|
}
|
|
26
26
|
export function invariant(value, message) {
|
|
27
27
|
if (value == null) {
|
|
@@ -30,7 +30,7 @@ export function invariant(value, message) {
|
|
|
30
30
|
return value;
|
|
31
31
|
}
|
|
32
32
|
export function joinPaths(from, to) {
|
|
33
|
-
return
|
|
33
|
+
return normalizePath(from).replace(/\/*(\*.*)?$/g, "") + normalizePath(to);
|
|
34
34
|
}
|
|
35
35
|
export function extractSearchParams(url) {
|
|
36
36
|
const params = {};
|
|
@@ -39,9 +39,6 @@ export function extractSearchParams(url) {
|
|
|
39
39
|
});
|
|
40
40
|
return params;
|
|
41
41
|
}
|
|
42
|
-
export function urlDecode(str, isQuery) {
|
|
43
|
-
return decodeURIComponent(isQuery ? str.replace(/\+/g, " ") : str);
|
|
44
|
-
}
|
|
45
42
|
export function createMatcher(path, partial) {
|
|
46
43
|
const [pattern, splat] = path.split("/*", 2);
|
|
47
44
|
const segments = pattern.split("/").filter(Boolean);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.6.0",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -28,36 +28,36 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "tsc && rollup -c",
|
|
33
|
-
"prepublishOnly": "npm run build",
|
|
34
|
-
"test": "jest && npm run test:types",
|
|
35
|
-
"test:watch": "jest --watch",
|
|
36
|
-
"test:coverage": "jest --coverage && npm run test:types",
|
|
37
|
-
"test:types": "tsc --project tsconfig.test.json",
|
|
38
|
-
"pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\""
|
|
39
|
-
},
|
|
40
31
|
"devDependencies": {
|
|
41
32
|
"@babel/core": "^7.18.13",
|
|
42
33
|
"@babel/preset-typescript": "^7.18.6",
|
|
43
|
-
"@rollup/plugin-babel": "
|
|
44
|
-
"@rollup/plugin-node-resolve": "
|
|
34
|
+
"@rollup/plugin-babel": "6.0.3",
|
|
35
|
+
"@rollup/plugin-node-resolve": "15.0.1",
|
|
36
|
+
"@rollup/plugin-terser": "0.2.0",
|
|
45
37
|
"@types/jest": "^29.0.0",
|
|
46
38
|
"@types/node": "^18.7.14",
|
|
47
|
-
"babel-
|
|
39
|
+
"babel-jest": "^29.0.1",
|
|
40
|
+
"babel-preset-solid": "^1.6.6",
|
|
48
41
|
"jest": "^29.0.1",
|
|
49
|
-
"jest-environment-jsdom": "^29.1
|
|
42
|
+
"jest-environment-jsdom": "^29.2.1",
|
|
50
43
|
"prettier": "^2.7.1",
|
|
51
|
-
"rollup": "^
|
|
52
|
-
"rollup-plugin-terser": "^7.0.2",
|
|
44
|
+
"rollup": "^3.7.5",
|
|
53
45
|
"solid-jest": "^0.2.0",
|
|
54
|
-
"solid-js": "^1.
|
|
55
|
-
"typescript": "^4.
|
|
46
|
+
"solid-js": "^1.6.6",
|
|
47
|
+
"typescript": "^4.9.4"
|
|
56
48
|
},
|
|
57
49
|
"peerDependencies": {
|
|
58
50
|
"solid-js": "^1.5.3"
|
|
59
51
|
},
|
|
60
52
|
"jest": {
|
|
61
53
|
"preset": "solid-jest/preset/browser"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc && rollup -c",
|
|
57
|
+
"test": "jest && npm run test:types",
|
|
58
|
+
"test:watch": "jest --watch",
|
|
59
|
+
"test:coverage": "jest --coverage && npm run test:types",
|
|
60
|
+
"test:types": "tsc --project tsconfig.test.json",
|
|
61
|
+
"pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\""
|
|
62
62
|
}
|
|
63
|
-
}
|
|
63
|
+
}
|