@solidjs/router 0.5.0 → 0.5.1
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.jsx +5 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +97 -156
- package/dist/index.jsx +2 -1
- package/dist/lifecycle.d.ts +2 -0
- package/dist/lifecycle.js +32 -0
- package/dist/routing.d.ts +2 -1
- package/dist/routing.js +14 -6
- package/dist/types.d.ts +19 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +5 -5
- package/package.json +2 -2
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.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 = {};
|
|
@@ -181,20 +195,16 @@ function createMatcher(path, partial) {
|
|
|
181
195
|
return location => {
|
|
182
196
|
const locSegments = location.split("/").filter(Boolean);
|
|
183
197
|
const lenDiff = locSegments.length - len;
|
|
184
|
-
|
|
185
198
|
if (lenDiff < 0 || lenDiff > 0 && splat === undefined && !partial) {
|
|
186
199
|
return null;
|
|
187
200
|
}
|
|
188
|
-
|
|
189
201
|
const match = {
|
|
190
202
|
path: len ? "" : "/",
|
|
191
203
|
params: {}
|
|
192
204
|
};
|
|
193
|
-
|
|
194
205
|
for (let i = 0; i < len; i++) {
|
|
195
206
|
const segment = segments[i];
|
|
196
207
|
const locSegment = locSegments[i];
|
|
197
|
-
|
|
198
208
|
if (segment[0] === ":") {
|
|
199
209
|
match.params[segment.slice(1)] = locSegment;
|
|
200
210
|
} else if (segment.localeCompare(locSegment, undefined, {
|
|
@@ -202,14 +212,11 @@ function createMatcher(path, partial) {
|
|
|
202
212
|
}) !== 0) {
|
|
203
213
|
return null;
|
|
204
214
|
}
|
|
205
|
-
|
|
206
215
|
match.path += `/${locSegment}`;
|
|
207
216
|
}
|
|
208
|
-
|
|
209
217
|
if (splat) {
|
|
210
218
|
match.params[splat] = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
|
211
219
|
}
|
|
212
|
-
|
|
213
220
|
return match;
|
|
214
221
|
};
|
|
215
222
|
}
|
|
@@ -226,21 +233,17 @@ function createMemoObject(fn) {
|
|
|
226
233
|
if (!map.has(property)) {
|
|
227
234
|
runWithOwner(owner, () => map.set(property, createMemo(() => fn()[property])));
|
|
228
235
|
}
|
|
229
|
-
|
|
230
236
|
return map.get(property)();
|
|
231
237
|
},
|
|
232
|
-
|
|
233
238
|
getOwnPropertyDescriptor() {
|
|
234
239
|
return {
|
|
235
240
|
enumerable: true,
|
|
236
241
|
configurable: true
|
|
237
242
|
};
|
|
238
243
|
},
|
|
239
|
-
|
|
240
244
|
ownKeys() {
|
|
241
245
|
return Reflect.ownKeys(fn());
|
|
242
246
|
}
|
|
243
|
-
|
|
244
247
|
});
|
|
245
248
|
}
|
|
246
249
|
function mergeSearchString(search, params) {
|
|
@@ -260,17 +263,17 @@ function expandOptionals(pattern) {
|
|
|
260
263
|
if (!match) return [pattern];
|
|
261
264
|
let prefix = pattern.slice(0, match.index);
|
|
262
265
|
let suffix = pattern.slice(match.index + match[0].length);
|
|
263
|
-
const prefixes = [prefix, prefix += match[1]];
|
|
266
|
+
const prefixes = [prefix, prefix += match[1]];
|
|
267
|
+
|
|
268
|
+
// This section handles adjacent optional params. We don't actually want all permuations since
|
|
264
269
|
// that will lead to equivalent routes which have the same number of params. For example
|
|
265
270
|
// `/:a?/:b?/:c`? only has the unique expansion: `/`, `/:a`, `/:a/:b`, `/:a/:b/:c` and we can
|
|
266
271
|
// discard `/:b`, `/:c`, `/:b/:c` by building them up in order and not recursing. This also helps
|
|
267
272
|
// ensure predictability where earlier params have precidence.
|
|
268
|
-
|
|
269
273
|
while (match = /^(\/\:[^\/]+)\?/.exec(suffix)) {
|
|
270
274
|
prefixes.push(prefix += match[1]);
|
|
271
275
|
suffix = suffix.slice(match[0].length);
|
|
272
276
|
}
|
|
273
|
-
|
|
274
277
|
return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []);
|
|
275
278
|
}
|
|
276
279
|
|
|
@@ -304,17 +307,24 @@ const useRouteData = () => useRoute().data;
|
|
|
304
307
|
const useSearchParams = () => {
|
|
305
308
|
const location = useLocation();
|
|
306
309
|
const navigate = useNavigate();
|
|
307
|
-
|
|
308
310
|
const setSearchParams = (params, options) => {
|
|
309
311
|
const searchString = untrack(() => mergeSearchString(location.search, params));
|
|
310
|
-
navigate(location.pathname + searchString, {
|
|
312
|
+
navigate(location.pathname + searchString + location.hash, {
|
|
311
313
|
scroll: false,
|
|
314
|
+
resolve: false,
|
|
312
315
|
...options
|
|
313
316
|
});
|
|
314
317
|
};
|
|
315
|
-
|
|
316
318
|
return [location.query, setSearchParams];
|
|
317
319
|
};
|
|
320
|
+
const useBeforeLeave = listener => {
|
|
321
|
+
const s = useRouter().beforeLeave.subscribe({
|
|
322
|
+
listener,
|
|
323
|
+
location: useLocation(),
|
|
324
|
+
navigate: useNavigate()
|
|
325
|
+
});
|
|
326
|
+
onCleanup(s);
|
|
327
|
+
};
|
|
318
328
|
function createRoutes(routeDef, base = "", fallback) {
|
|
319
329
|
const {
|
|
320
330
|
component,
|
|
@@ -337,13 +347,13 @@ function createRoutes(routeDef, base = "", fallback) {
|
|
|
337
347
|
for (const originalPath of expandOptionals(path)) {
|
|
338
348
|
const path = joinPaths(base, originalPath);
|
|
339
349
|
const pattern = isLeaf ? path : path.split("/*", 1)[0];
|
|
340
|
-
acc.push({
|
|
350
|
+
acc.push({
|
|
351
|
+
...shared,
|
|
341
352
|
originalPath,
|
|
342
353
|
pattern,
|
|
343
354
|
matcher: createMatcher(pattern, !isLeaf)
|
|
344
355
|
});
|
|
345
356
|
}
|
|
346
|
-
|
|
347
357
|
return acc;
|
|
348
358
|
}, []);
|
|
349
359
|
}
|
|
@@ -351,76 +361,62 @@ function createBranch(routes, index = 0) {
|
|
|
351
361
|
return {
|
|
352
362
|
routes,
|
|
353
363
|
score: scoreRoute(routes[routes.length - 1]) * 10000 - index,
|
|
354
|
-
|
|
355
364
|
matcher(location) {
|
|
356
365
|
const matches = [];
|
|
357
|
-
|
|
358
366
|
for (let i = routes.length - 1; i >= 0; i--) {
|
|
359
367
|
const route = routes[i];
|
|
360
368
|
const match = route.matcher(location);
|
|
361
|
-
|
|
362
369
|
if (!match) {
|
|
363
370
|
return null;
|
|
364
371
|
}
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
matches.unshift({
|
|
373
|
+
...match,
|
|
367
374
|
route
|
|
368
375
|
});
|
|
369
376
|
}
|
|
370
|
-
|
|
371
377
|
return matches;
|
|
372
378
|
}
|
|
373
|
-
|
|
374
379
|
};
|
|
375
380
|
}
|
|
376
|
-
|
|
377
381
|
function asArray(value) {
|
|
378
382
|
return Array.isArray(value) ? value : [value];
|
|
379
383
|
}
|
|
380
|
-
|
|
381
384
|
function createBranches(routeDef, base = "", fallback, stack = [], branches = []) {
|
|
382
385
|
const routeDefs = asArray(routeDef);
|
|
383
|
-
|
|
384
386
|
for (let i = 0, len = routeDefs.length; i < len; i++) {
|
|
385
387
|
const def = routeDefs[i];
|
|
386
|
-
|
|
387
388
|
if (def && typeof def === "object" && def.hasOwnProperty("path")) {
|
|
388
389
|
const routes = createRoutes(def, base, fallback);
|
|
389
|
-
|
|
390
390
|
for (const route of routes) {
|
|
391
391
|
stack.push(route);
|
|
392
|
-
|
|
393
|
-
if (def.children) {
|
|
392
|
+
const isEmptyArray = Array.isArray(def.children) && def.children.length === 0;
|
|
393
|
+
if (def.children && !isEmptyArray) {
|
|
394
394
|
createBranches(def.children, route.pattern, fallback, stack, branches);
|
|
395
395
|
} else {
|
|
396
396
|
const branch = createBranch([...stack], branches.length);
|
|
397
397
|
branches.push(branch);
|
|
398
398
|
}
|
|
399
|
-
|
|
400
399
|
stack.pop();
|
|
401
400
|
}
|
|
402
401
|
}
|
|
403
|
-
}
|
|
404
|
-
|
|
402
|
+
}
|
|
405
403
|
|
|
404
|
+
// Stack will be empty on final return
|
|
406
405
|
return stack.length ? branches : branches.sort((a, b) => b.score - a.score);
|
|
407
406
|
}
|
|
408
407
|
function getRouteMatches(branches, location) {
|
|
409
408
|
for (let i = 0, len = branches.length; i < len; i++) {
|
|
410
409
|
const match = branches[i].matcher(location);
|
|
411
|
-
|
|
412
410
|
if (match) {
|
|
413
411
|
return match;
|
|
414
412
|
}
|
|
415
413
|
}
|
|
416
|
-
|
|
417
414
|
return [];
|
|
418
415
|
}
|
|
419
416
|
function createLocation(path, state) {
|
|
420
417
|
const origin = new URL("http://sar");
|
|
421
418
|
const url = createMemo(prev => {
|
|
422
419
|
const path_ = path();
|
|
423
|
-
|
|
424
420
|
try {
|
|
425
421
|
return new URL(path_, origin);
|
|
426
422
|
} catch (err) {
|
|
@@ -438,23 +434,18 @@ function createLocation(path, state) {
|
|
|
438
434
|
get pathname() {
|
|
439
435
|
return pathname();
|
|
440
436
|
},
|
|
441
|
-
|
|
442
437
|
get search() {
|
|
443
438
|
return search();
|
|
444
439
|
},
|
|
445
|
-
|
|
446
440
|
get hash() {
|
|
447
441
|
return hash();
|
|
448
442
|
},
|
|
449
|
-
|
|
450
443
|
get state() {
|
|
451
444
|
return state();
|
|
452
445
|
},
|
|
453
|
-
|
|
454
446
|
get key() {
|
|
455
447
|
return key();
|
|
456
448
|
},
|
|
457
|
-
|
|
458
449
|
query: createMemoObject(on(search, () => extractSearchParams(url())))
|
|
459
450
|
};
|
|
460
451
|
}
|
|
@@ -463,17 +454,14 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
463
454
|
signal: [source, setSource],
|
|
464
455
|
utils = {}
|
|
465
456
|
} = normalizeIntegration(integration);
|
|
466
|
-
|
|
467
457
|
const parsePath = utils.parsePath || (p => p);
|
|
468
|
-
|
|
469
458
|
const renderPath = utils.renderPath || (p => p);
|
|
470
|
-
|
|
459
|
+
const beforeLeave = utils.beforeLeave || createBeforeLeave();
|
|
471
460
|
const basePath = resolvePath("", base);
|
|
472
461
|
const output = isServer && out ? Object.assign(out, {
|
|
473
462
|
matches: [],
|
|
474
463
|
url: undefined
|
|
475
464
|
}) : undefined;
|
|
476
|
-
|
|
477
465
|
if (basePath === undefined) {
|
|
478
466
|
throw new Error(`${basePath} is not a valid base path`);
|
|
479
467
|
} else if (basePath && !source().value) {
|
|
@@ -483,19 +471,15 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
483
471
|
scroll: false
|
|
484
472
|
});
|
|
485
473
|
}
|
|
486
|
-
|
|
487
474
|
const [isRouting, setIsRouting] = createSignal(false);
|
|
488
|
-
|
|
489
475
|
const start = async callback => {
|
|
490
476
|
setIsRouting(true);
|
|
491
|
-
|
|
492
477
|
try {
|
|
493
478
|
await startTransition(callback);
|
|
494
479
|
} finally {
|
|
495
480
|
setIsRouting(false);
|
|
496
481
|
}
|
|
497
482
|
};
|
|
498
|
-
|
|
499
483
|
const [reference, setReference] = createSignal(source().value);
|
|
500
484
|
const [state, setState] = createSignal(source().state);
|
|
501
485
|
const location = createLocation(reference, state);
|
|
@@ -505,13 +489,10 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
505
489
|
params: {},
|
|
506
490
|
path: () => basePath,
|
|
507
491
|
outlet: () => null,
|
|
508
|
-
|
|
509
492
|
resolvePath(to) {
|
|
510
493
|
return resolvePath(basePath, to);
|
|
511
494
|
}
|
|
512
|
-
|
|
513
495
|
};
|
|
514
|
-
|
|
515
496
|
if (data) {
|
|
516
497
|
try {
|
|
517
498
|
TempRoute = baseRoute;
|
|
@@ -525,20 +506,17 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
525
506
|
TempRoute = undefined;
|
|
526
507
|
}
|
|
527
508
|
}
|
|
528
|
-
|
|
529
509
|
function navigateFromRoute(route, to, options) {
|
|
530
510
|
// Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
|
|
531
511
|
untrack(() => {
|
|
532
512
|
if (typeof to === "number") {
|
|
533
513
|
if (!to) ; else if (utils.go) {
|
|
534
|
-
utils.go(to);
|
|
514
|
+
beforeLeave.confirm(to, options) && utils.go(to);
|
|
535
515
|
} else {
|
|
536
516
|
console.warn("Router integration does not support relative routing");
|
|
537
517
|
}
|
|
538
|
-
|
|
539
518
|
return;
|
|
540
519
|
}
|
|
541
|
-
|
|
542
520
|
const {
|
|
543
521
|
replace,
|
|
544
522
|
resolve,
|
|
@@ -551,28 +529,24 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
551
529
|
...options
|
|
552
530
|
};
|
|
553
531
|
const resolvedTo = resolve ? route.resolvePath(to) : resolvePath("", to);
|
|
554
|
-
|
|
555
532
|
if (resolvedTo === undefined) {
|
|
556
533
|
throw new Error(`Path '${to}' is not a routable path`);
|
|
557
534
|
} else if (referrers.length >= MAX_REDIRECTS) {
|
|
558
535
|
throw new Error("Too many redirects");
|
|
559
536
|
}
|
|
560
|
-
|
|
561
537
|
const current = reference();
|
|
562
|
-
|
|
563
538
|
if (resolvedTo !== current || nextState !== state()) {
|
|
564
539
|
if (isServer) {
|
|
565
540
|
if (output) {
|
|
566
541
|
output.url = resolvedTo;
|
|
567
542
|
}
|
|
568
|
-
|
|
569
543
|
setSource({
|
|
570
544
|
value: resolvedTo,
|
|
571
545
|
replace,
|
|
572
546
|
scroll,
|
|
573
547
|
state: nextState
|
|
574
548
|
});
|
|
575
|
-
} else {
|
|
549
|
+
} else if (beforeLeave.confirm(resolvedTo, options)) {
|
|
576
550
|
const len = referrers.push({
|
|
577
551
|
value: current,
|
|
578
552
|
replace,
|
|
@@ -595,34 +569,30 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
595
569
|
}
|
|
596
570
|
});
|
|
597
571
|
}
|
|
598
|
-
|
|
599
572
|
function navigatorFactory(route) {
|
|
600
573
|
// Workaround for vite issue (https://github.com/vitejs/vite/issues/3803)
|
|
601
574
|
route = route || useContext(RouteContextObj) || baseRoute;
|
|
602
575
|
return (to, options) => navigateFromRoute(route, to, options);
|
|
603
576
|
}
|
|
604
|
-
|
|
605
577
|
function navigateEnd(next) {
|
|
606
578
|
const first = referrers[0];
|
|
607
|
-
|
|
608
579
|
if (first) {
|
|
609
580
|
if (next.value !== first.value || next.state !== first.state) {
|
|
610
|
-
setSource({
|
|
581
|
+
setSource({
|
|
582
|
+
...next,
|
|
611
583
|
replace: first.replace,
|
|
612
584
|
scroll: first.scroll
|
|
613
585
|
});
|
|
614
586
|
}
|
|
615
|
-
|
|
616
587
|
referrers.length = 0;
|
|
617
588
|
}
|
|
618
589
|
}
|
|
619
|
-
|
|
620
590
|
createRenderEffect(() => {
|
|
621
591
|
const {
|
|
622
592
|
value,
|
|
623
593
|
state
|
|
624
|
-
} = source();
|
|
625
|
-
|
|
594
|
+
} = source();
|
|
595
|
+
// Untrack this whole block so `start` doesn't cause Solid's Listener to be preserved
|
|
626
596
|
untrack(() => {
|
|
627
597
|
if (value !== reference()) {
|
|
628
598
|
start(() => {
|
|
@@ -632,7 +602,6 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
632
602
|
}
|
|
633
603
|
});
|
|
634
604
|
});
|
|
635
|
-
|
|
636
605
|
if (!isServer) {
|
|
637
606
|
function handleAnchorClick(evt) {
|
|
638
607
|
if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
|
|
@@ -645,7 +614,7 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
645
614
|
const url = new URL(href);
|
|
646
615
|
const pathname = urlDecode(url.pathname);
|
|
647
616
|
if (url.origin !== window.location.origin || basePath && pathname && !pathname.toLowerCase().startsWith(basePath.toLowerCase())) return;
|
|
648
|
-
const to = parsePath(pathname +
|
|
617
|
+
const to = parsePath(url.pathname + url.search + url.hash);
|
|
649
618
|
const state = a.getAttribute("state");
|
|
650
619
|
evt.preventDefault();
|
|
651
620
|
navigateFromRoute(baseRoute, to, {
|
|
@@ -654,14 +623,13 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
654
623
|
scroll: !a.hasAttribute("noscroll"),
|
|
655
624
|
state: state && JSON.parse(state)
|
|
656
625
|
});
|
|
657
|
-
}
|
|
658
|
-
|
|
626
|
+
}
|
|
659
627
|
|
|
628
|
+
// ensure delegated events run first
|
|
660
629
|
delegateEvents(["click"]);
|
|
661
630
|
document.addEventListener("click", handleAnchorClick);
|
|
662
631
|
onCleanup(() => document.removeEventListener("click", handleAnchorClick));
|
|
663
632
|
}
|
|
664
|
-
|
|
665
633
|
return {
|
|
666
634
|
base: baseRoute,
|
|
667
635
|
out: output,
|
|
@@ -669,7 +637,8 @@ function createRouterContext(integration, base = "", data, out) {
|
|
|
669
637
|
isRouting,
|
|
670
638
|
renderPath,
|
|
671
639
|
parsePath,
|
|
672
|
-
navigatorFactory
|
|
640
|
+
navigatorFactory,
|
|
641
|
+
beforeLeave
|
|
673
642
|
};
|
|
674
643
|
}
|
|
675
644
|
function createRouteContext(router, parent, child, match) {
|
|
@@ -690,22 +659,17 @@ function createRouteContext(router, parent, child, match) {
|
|
|
690
659
|
const route = {
|
|
691
660
|
parent,
|
|
692
661
|
pattern,
|
|
693
|
-
|
|
694
662
|
get child() {
|
|
695
663
|
return child();
|
|
696
664
|
},
|
|
697
|
-
|
|
698
665
|
path,
|
|
699
666
|
params,
|
|
700
667
|
data: parent.data,
|
|
701
668
|
outlet,
|
|
702
|
-
|
|
703
669
|
resolvePath(to) {
|
|
704
670
|
return resolvePath(base.path(), to, path());
|
|
705
671
|
}
|
|
706
|
-
|
|
707
672
|
};
|
|
708
|
-
|
|
709
673
|
if (data) {
|
|
710
674
|
try {
|
|
711
675
|
TempRoute = route;
|
|
@@ -719,7 +683,6 @@ function createRouteContext(router, parent, child, match) {
|
|
|
719
683
|
TempRoute = undefined;
|
|
720
684
|
}
|
|
721
685
|
}
|
|
722
|
-
|
|
723
686
|
return route;
|
|
724
687
|
}
|
|
725
688
|
|
|
@@ -738,11 +701,9 @@ const Router = props => {
|
|
|
738
701
|
const routerState = createRouterContext(integration, base, data, out);
|
|
739
702
|
return createComponent$1(RouterContextObj.Provider, {
|
|
740
703
|
value: routerState,
|
|
741
|
-
|
|
742
704
|
get children() {
|
|
743
705
|
return props.children;
|
|
744
706
|
}
|
|
745
|
-
|
|
746
707
|
});
|
|
747
708
|
};
|
|
748
709
|
const Routes = props => {
|
|
@@ -751,7 +712,6 @@ const Routes = props => {
|
|
|
751
712
|
const routeDefs = children(() => props.children);
|
|
752
713
|
const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
|
|
753
714
|
const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
|
|
754
|
-
|
|
755
715
|
if (router.out) {
|
|
756
716
|
router.out.matches.push(matches().map(({
|
|
757
717
|
route,
|
|
@@ -764,39 +724,31 @@ const Routes = props => {
|
|
|
764
724
|
params
|
|
765
725
|
})));
|
|
766
726
|
}
|
|
767
|
-
|
|
768
727
|
const disposers = [];
|
|
769
728
|
let root;
|
|
770
729
|
const routeStates = createMemo(on(matches, (nextMatches, prevMatches, prev) => {
|
|
771
730
|
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
|
772
731
|
const next = [];
|
|
773
|
-
|
|
774
732
|
for (let i = 0, len = nextMatches.length; i < len; i++) {
|
|
775
733
|
const prevMatch = prevMatches && prevMatches[i];
|
|
776
734
|
const nextMatch = nextMatches[i];
|
|
777
|
-
|
|
778
735
|
if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
|
|
779
736
|
next[i] = prev[i];
|
|
780
737
|
} else {
|
|
781
738
|
equal = false;
|
|
782
|
-
|
|
783
739
|
if (disposers[i]) {
|
|
784
740
|
disposers[i]();
|
|
785
741
|
}
|
|
786
|
-
|
|
787
742
|
createRoot(dispose => {
|
|
788
743
|
disposers[i] = dispose;
|
|
789
744
|
next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i]);
|
|
790
745
|
});
|
|
791
746
|
}
|
|
792
747
|
}
|
|
793
|
-
|
|
794
748
|
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
|
795
|
-
|
|
796
749
|
if (prev && equal) {
|
|
797
750
|
return prev;
|
|
798
751
|
}
|
|
799
|
-
|
|
800
752
|
root = next[0];
|
|
801
753
|
return next;
|
|
802
754
|
}));
|
|
@@ -804,14 +756,11 @@ const Routes = props => {
|
|
|
804
756
|
get when() {
|
|
805
757
|
return routeStates() && root;
|
|
806
758
|
},
|
|
807
|
-
|
|
808
759
|
children: route => createComponent$1(RouteContextObj.Provider, {
|
|
809
760
|
value: route,
|
|
810
|
-
|
|
811
761
|
get children() {
|
|
812
762
|
return route.outlet();
|
|
813
763
|
}
|
|
814
|
-
|
|
815
764
|
})
|
|
816
765
|
});
|
|
817
766
|
};
|
|
@@ -827,7 +776,6 @@ const Route = props => {
|
|
|
827
776
|
get children() {
|
|
828
777
|
return childRoutes();
|
|
829
778
|
}
|
|
830
|
-
|
|
831
779
|
});
|
|
832
780
|
};
|
|
833
781
|
const Outlet = () => {
|
|
@@ -836,14 +784,11 @@ const Outlet = () => {
|
|
|
836
784
|
get when() {
|
|
837
785
|
return route.child;
|
|
838
786
|
},
|
|
839
|
-
|
|
840
787
|
children: child => createComponent$1(RouteContextObj.Provider, {
|
|
841
788
|
value: child,
|
|
842
|
-
|
|
843
789
|
get children() {
|
|
844
790
|
return child.outlet();
|
|
845
791
|
}
|
|
846
|
-
|
|
847
792
|
})
|
|
848
793
|
});
|
|
849
794
|
};
|
|
@@ -852,47 +797,43 @@ function A(props) {
|
|
|
852
797
|
inactiveClass: "inactive",
|
|
853
798
|
activeClass: "active"
|
|
854
799
|
}, props);
|
|
855
|
-
const [, rest] = splitProps(props, ["href", "state", "activeClass", "inactiveClass", "end"]);
|
|
800
|
+
const [, rest] = splitProps(props, ["href", "state", "class", "activeClass", "inactiveClass", "end"]);
|
|
856
801
|
const to = useResolvedPath(() => props.href);
|
|
857
802
|
const href = useHref(to);
|
|
858
803
|
const location = useLocation();
|
|
859
804
|
const isActive = createMemo(() => {
|
|
860
805
|
const to_ = to();
|
|
861
806
|
if (to_ === undefined) return false;
|
|
862
|
-
const path = to_.split(/[?#]/, 1)[0].toLowerCase();
|
|
863
|
-
const loc = location.pathname.toLowerCase();
|
|
807
|
+
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase();
|
|
808
|
+
const loc = normalizePath(location.pathname).toLowerCase();
|
|
864
809
|
return props.end ? path === loc : loc.startsWith(path);
|
|
865
810
|
});
|
|
866
811
|
return (() => {
|
|
867
812
|
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
|
|
813
|
+
spread(_el$, mergeProps$1(rest, {
|
|
814
|
+
get href() {
|
|
815
|
+
return href() || props.href;
|
|
878
816
|
},
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
817
|
+
get state() {
|
|
818
|
+
return JSON.stringify(props.state);
|
|
819
|
+
},
|
|
820
|
+
get classList() {
|
|
821
|
+
return {
|
|
822
|
+
...(props.class && {
|
|
823
|
+
[props.class]: true
|
|
824
|
+
}),
|
|
825
|
+
[props.inactiveClass]: !isActive(),
|
|
826
|
+
[props.activeClass]: isActive(),
|
|
827
|
+
...rest.classList
|
|
828
|
+
};
|
|
829
|
+
},
|
|
830
|
+
get ["aria-current"]() {
|
|
831
|
+
return isActive() ? "page" : undefined;
|
|
832
|
+
}
|
|
833
|
+
}), false, false);
|
|
893
834
|
return _el$;
|
|
894
835
|
})();
|
|
895
|
-
}
|
|
836
|
+
}
|
|
896
837
|
function Navigate(props) {
|
|
897
838
|
const navigate = useNavigate();
|
|
898
839
|
const location = useLocation();
|
|
@@ -911,4 +852,4 @@ function Navigate(props) {
|
|
|
911
852
|
return null;
|
|
912
853
|
}
|
|
913
854
|
|
|
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 };
|
|
855
|
+
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;
|
|
@@ -14,6 +14,7 @@ export declare const useParams: <T extends Params>() => T;
|
|
|
14
14
|
declare 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,6 +1,7 @@
|
|
|
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 { createBeforeLeave } from "./lifecycle";
|
|
4
5
|
import { createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, urlDecode, expandOptionals } from "./utils";
|
|
5
6
|
const MAX_REDIRECTS = 100;
|
|
6
7
|
export const RouterContextObj = createContext();
|
|
@@ -34,10 +35,14 @@ export const useSearchParams = () => {
|
|
|
34
35
|
const navigate = useNavigate();
|
|
35
36
|
const setSearchParams = (params, options) => {
|
|
36
37
|
const searchString = untrack(() => mergeSearchString(location.search, params));
|
|
37
|
-
navigate(location.pathname + searchString, { scroll: false, ...options });
|
|
38
|
+
navigate(location.pathname + searchString + location.hash, { scroll: false, resolve: false, ...options });
|
|
38
39
|
};
|
|
39
40
|
return [location.query, setSearchParams];
|
|
40
41
|
};
|
|
42
|
+
export const useBeforeLeave = (listener) => {
|
|
43
|
+
const s = useRouter().beforeLeave.subscribe({ listener, location: useLocation(), navigate: useNavigate() });
|
|
44
|
+
onCleanup(s);
|
|
45
|
+
};
|
|
41
46
|
export function createRoutes(routeDef, base = "", fallback) {
|
|
42
47
|
const { component, data, children } = routeDef;
|
|
43
48
|
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
|
@@ -102,7 +107,8 @@ export function createBranches(routeDef, base = "", fallback, stack = [], branch
|
|
|
102
107
|
const routes = createRoutes(def, base, fallback);
|
|
103
108
|
for (const route of routes) {
|
|
104
109
|
stack.push(route);
|
|
105
|
-
|
|
110
|
+
const isEmptyArray = Array.isArray(def.children) && def.children.length === 0;
|
|
111
|
+
if (def.children && !isEmptyArray) {
|
|
106
112
|
createBranches(def.children, route.pattern, fallback, stack, branches);
|
|
107
113
|
}
|
|
108
114
|
else {
|
|
@@ -166,6 +172,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
166
172
|
const { signal: [source, setSource], utils = {} } = normalizeIntegration(integration);
|
|
167
173
|
const parsePath = utils.parsePath || (p => p);
|
|
168
174
|
const renderPath = utils.renderPath || (p => p);
|
|
175
|
+
const beforeLeave = utils.beforeLeave || createBeforeLeave();
|
|
169
176
|
const basePath = resolvePath("", base);
|
|
170
177
|
const output = isServer && out
|
|
171
178
|
? Object.assign(out, {
|
|
@@ -224,7 +231,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
224
231
|
// A delta of 0 means stay at the current location, so it is ignored
|
|
225
232
|
}
|
|
226
233
|
else if (utils.go) {
|
|
227
|
-
utils.go(to);
|
|
234
|
+
beforeLeave.confirm(to, options) && utils.go(to);
|
|
228
235
|
}
|
|
229
236
|
else {
|
|
230
237
|
console.warn("Router integration does not support relative routing");
|
|
@@ -252,7 +259,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
252
259
|
}
|
|
253
260
|
setSource({ value: resolvedTo, replace, scroll, state: nextState });
|
|
254
261
|
}
|
|
255
|
-
else {
|
|
262
|
+
else if (beforeLeave.confirm(resolvedTo, options)) {
|
|
256
263
|
const len = referrers.push({ value: current, replace, scroll, state: state() });
|
|
257
264
|
start(() => {
|
|
258
265
|
setReference(resolvedTo);
|
|
@@ -325,7 +332,7 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
325
332
|
if (url.origin !== window.location.origin ||
|
|
326
333
|
(basePath && pathname && !pathname.toLowerCase().startsWith(basePath.toLowerCase())))
|
|
327
334
|
return;
|
|
328
|
-
const to = parsePath(pathname +
|
|
335
|
+
const to = parsePath(url.pathname + url.search + url.hash);
|
|
329
336
|
const state = a.getAttribute("state");
|
|
330
337
|
evt.preventDefault();
|
|
331
338
|
navigateFromRoute(baseRoute, to, {
|
|
@@ -347,7 +354,8 @@ export function createRouterContext(integration, base = "", data, out) {
|
|
|
347
354
|
isRouting,
|
|
348
355
|
renderPath,
|
|
349
356
|
parsePath,
|
|
350
|
-
navigatorFactory
|
|
357
|
+
navigatorFactory,
|
|
358
|
+
beforeLeave
|
|
351
359
|
};
|
|
352
360
|
}
|
|
353
361
|
export function createRouteContext(router, parent, child, match) {
|
package/dist/types.d.ts
CHANGED
|
@@ -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,4 +1,5 @@
|
|
|
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;
|
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 = {};
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"Ryan Turnquist"
|
|
7
7
|
],
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "0.5.
|
|
9
|
+
"version": "0.5.1",
|
|
10
10
|
"homepage": "https://github.com/solidjs/solid-router#readme",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@types/node": "^18.7.14",
|
|
47
47
|
"babel-preset-solid": "^1.5.3",
|
|
48
48
|
"jest": "^29.0.1",
|
|
49
|
-
"jest-environment-jsdom": "^29.1
|
|
49
|
+
"jest-environment-jsdom": "^29.2.1",
|
|
50
50
|
"prettier": "^2.7.1",
|
|
51
51
|
"rollup": "^2.79.0",
|
|
52
52
|
"rollup-plugin-terser": "^7.0.2",
|