Skip to content

Commit 4dd5892

Browse files
authored
feat: cancel pending navigation if new one is started (#74)
1 parent f6d82e3 commit 4dd5892

File tree

4 files changed

+31
-29
lines changed

4 files changed

+31
-29
lines changed

.changeset/wise-spiders-serve.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'sv-router': minor
3+
---
4+
5+
Cancel pending navigation if new one is started

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default [
3636
eslintPluginUnicorn.configs['flat/recommended'],
3737
{
3838
rules: {
39+
'unicorn/error-message': 'off',
3940
'unicorn/filename-case': [
4041
'error',
4142
{

src/create-router.svelte.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@ import { BROWSER, DEV } from 'esm-env';
22
import { isActive } from './helpers/is-active.js';
33
import { matchRoute } from './helpers/match-route.js';
44
import { preloadOnHover } from './helpers/preload-on-hover.js';
5-
import {
6-
constructPath,
7-
join,
8-
resolveRouteComponents,
9-
wrapInViewTransition,
10-
} from './helpers/utils.js';
5+
import { constructPath, join, resolveRouteComponents } from './helpers/utils.js';
116
import { syncSearchParams } from './search-params.svelte.js';
127

138
/** @type {import('./index.d.ts').Routes} */
@@ -21,6 +16,9 @@ export let params = $state({ value: {} });
2116

2217
export let location = $state(updatedLocation());
2318

19+
let navigationIndex = 0;
20+
let pendingNavigationIndex = 0;
21+
2422
/** @type {{ name?: string }} */
2523
export const base = {
2624
name: undefined,
@@ -95,6 +93,10 @@ export async function onNavigate(path, options = {}) {
9593
if (!routes) {
9694
throw new Error('Router not initialized: `createRouter` was not called.');
9795
}
96+
97+
navigationIndex++;
98+
const currentNavigationIndex = navigationIndex;
99+
98100
let matchPath = path || globalThis.location.pathname;
99101
if (base.name && matchPath.startsWith(base.name)) {
100102
matchPath = matchPath.slice(base.name.length) || '/';
@@ -103,15 +105,22 @@ export async function onNavigate(path, options = {}) {
103105

104106
for (const { beforeLoad } of hooks) {
105107
try {
108+
pendingNavigationIndex = currentNavigationIndex;
106109
await beforeLoad?.();
107110
} catch {
108111
return;
109112
}
110113
}
111114

112-
await wrapInViewTransition(async () => {
113-
componentTree.value = await resolveRouteComponents(match ? [...layouts, match] : layouts);
114-
}, options.viewTransition);
115+
const fromBeforeLoadHook = new Error().stack?.includes('beforeLoad');
116+
117+
const routeComponents = await resolveRouteComponents(match ? [...layouts, match] : layouts);
118+
if (
119+
navigationIndex !== currentNavigationIndex ||
120+
(fromBeforeLoadHook && pendingNavigationIndex + 1 !== currentNavigationIndex)
121+
) {
122+
return;
123+
}
115124

116125
if (path) {
117126
if (options.search) path += options.search;
@@ -121,6 +130,13 @@ export async function onNavigate(path, options = {}) {
121130
globalThis.history[historyMethod](options.state || {}, '', to);
122131
}
123132

133+
if (options.viewTransition && document.startViewTransition !== undefined) {
134+
document.startViewTransition(() => {
135+
componentTree.value = routeComponents;
136+
});
137+
} else {
138+
componentTree.value = routeComponents;
139+
}
124140
params.value = newParams || {};
125141
syncSearchParams();
126142
Object.assign(location, updatedLocation());

src/helpers/utils.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,3 @@ export function join(...parts) {
5959
}
6060
return result;
6161
}
62-
63-
/**
64-
* @param {() => void | Promise<void>} callback
65-
* @param {boolean} [enable]
66-
* @returns
67-
*/
68-
export function wrapInViewTransition(callback, enable) {
69-
return /** @type {Promise<void>} */ (
70-
new Promise((resolve) => {
71-
if (enable && document.startViewTransition !== undefined) {
72-
document.startViewTransition(async () => {
73-
await callback();
74-
resolve();
75-
});
76-
} else {
77-
Promise.resolve(callback()).then(resolve);
78-
}
79-
})
80-
);
81-
}

0 commit comments

Comments
 (0)