Skip to content

Commit eeb2c57

Browse files
committed
refactor: Switch to Navigation API
1 parent 3efae13 commit eeb2c57

File tree

2 files changed

+32
-53
lines changed

2 files changed

+32
-53
lines changed

src/router.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ interface LocationHook {
4040
path: string;
4141
pathParams: Record<string, string>;
4242
searchParams: Record<string, string>;
43-
route: (url: string, replace?: boolean) => void;
4443
}
4544
export const useLocation: () => LocationHook;
4645

src/router.js

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,37 @@
11
import { h, createContext, cloneElement, toChildArray } from 'preact';
22
import { useContext, useMemo, useReducer, useLayoutEffect, useRef } from 'preact/hooks';
33

4+
5+
let scope;
6+
7+
/**
8+
* @param {string} state
9+
* @param {NavigateEvent} e
10+
*/
11+
function handleNav(state, e) {
12+
if (!e.canIntercept) return state;
13+
if (e.hashChange || e.downloadRequest !== null) return state;
14+
15+
const url = new URL(e.destination.url);
16+
if (
17+
scope && (typeof scope == 'string'
18+
? !url.pathname.startsWith(scope)
19+
: !scope.test(url.pathname)
20+
)
21+
) {
22+
return state;
23+
}
24+
25+
e.intercept();
26+
return url.href.replace(url.origin, '');
27+
}
28+
429
/**
530
* @template T
631
* @typedef {import('preact').RefObject<T>} RefObject
732
* @typedef {import('./internal.d.ts').VNode} VNode
833
*/
934

10-
let push, scope;
11-
const UPDATE = (state, url) => {
12-
push = undefined;
13-
if (url && url.type === 'click') {
14-
// ignore events the browser takes care of already:
15-
if (url.ctrlKey || url.metaKey || url.altKey || url.shiftKey || url.button !== 0) {
16-
return state;
17-
}
18-
19-
const link = url.target.closest('a[href]'),
20-
href = link && link.getAttribute('href');
21-
if (
22-
!link ||
23-
link.origin != location.origin ||
24-
/^#/.test(href) ||
25-
!/^(_?self)?$/i.test(link.target) ||
26-
scope && (typeof scope == 'string'
27-
? !href.startsWith(scope)
28-
: !scope.test(href)
29-
)
30-
) {
31-
return state;
32-
}
33-
34-
push = true;
35-
url.preventDefault();
36-
url = link.href.replace(location.origin, '');
37-
} else if (typeof url === 'string') {
38-
push = true;
39-
} else if (url && url.url) {
40-
push = !url.replace;
41-
url = url.url;
42-
} else {
43-
url = location.pathname + location.search;
44-
}
45-
46-
if (push === true) history.pushState(null, '', url);
47-
else if (push === false) history.replaceState(null, '', url);
48-
return url;
49-
};
5035

5136
export const exec = (url, route, matches = {}) => {
5237
url = url.split('/').filter(Boolean);
@@ -80,9 +65,8 @@ export const exec = (url, route, matches = {}) => {
8065
* @type {import('./router.d.ts').LocationProvider}
8166
*/
8267
export function LocationProvider(props) {
83-
const [url, route] = useReducer(UPDATE, location.pathname + location.search);
68+
const [url, route] = useReducer(handleNav, location.pathname + location.search);
8469
if (props.scope) scope = props.scope;
85-
const wasPush = push === true;
8670

8771
const value = useMemo(() => {
8872
const u = new URL(url, location.origin);
@@ -93,18 +77,14 @@ export function LocationProvider(props) {
9377
path,
9478
pathParams: {},
9579
searchParams: Object.fromEntries(u.searchParams),
96-
route: (url, replace) => route({ url, replace }),
97-
wasPush
9880
};
9981
}, [url]);
10082

10183
useLayoutEffect(() => {
102-
addEventListener('click', route);
103-
addEventListener('popstate', route);
84+
navigation.addEventListener('navigate', route);
10485

10586
return () => {
106-
removeEventListener('click', route);
107-
removeEventListener('popstate', route);
87+
navigation.removeEventListener('navigate', route);
10888
};
10989
}, []);
11090

@@ -116,7 +96,7 @@ const RESOLVED = Promise.resolve();
11696
export function Router(props) {
11797
const [c, update] = useReducer(c => c + 1, 0);
11898

119-
const { url, path, pathParams, searchParams, wasPush } = useLocation();
99+
const { url, path, pathParams, searchParams } = useLocation();
120100

121101
const isLoading = useRef(false);
122102
const prevRoute = useRef(path);
@@ -236,15 +216,15 @@ export function Router(props) {
236216

237217
// The route is loaded and rendered.
238218
if (prevRoute.current !== path) {
239-
if (wasPush) scrollTo(0, 0);
219+
scrollTo(0, 0);
240220
if (props.onRouteChange) props.onRouteChange(url);
241221

242222
prevRoute.current = path;
243223
}
244224

245225
if (props.onLoadEnd && isLoading.current) props.onLoadEnd(url);
246226
isLoading.current = false;
247-
}, [path, wasPush, c]);
227+
}, [path, c]);
248228

249229
// Note: cur MUST render first in order to set didSuspend & prev.
250230
return routeChanged
@@ -261,7 +241,7 @@ const RenderRef = ({ r }) => r.current;
261241
Router.Provider = LocationProvider;
262242

263243
LocationProvider.ctx = createContext(
264-
/** @type {import('./router.d.ts').LocationHook & { wasPush: boolean }} */ ({})
244+
/** @type {import('./router.d.ts').LocationHook}} */ ({})
265245
);
266246

267247
export const Route = props => h(props.component, props);

0 commit comments

Comments
 (0)