diff --git a/.changeset/hot-pugs-worry.md b/.changeset/hot-pugs-worry.md new file mode 100644 index 0000000..bc6cf26 --- /dev/null +++ b/.changeset/hot-pugs-worry.md @@ -0,0 +1,5 @@ +--- +'sv-router': patch +--- + +correctly handle cancellation of navigations, return Error in navigate diff --git a/src/create-router.svelte.js b/src/create-router.svelte.js index 563b557..5778b41 100644 --- a/src/create-router.svelte.js +++ b/src/create-router.svelte.js @@ -12,6 +12,7 @@ import { stripBase, updatedLocation, } from './helpers/utils.js'; +import { Navigation } from './navigation.js'; import { syncSearchParams } from './search-params.svelte.js'; /** @type {import('./index.d.ts').Routes} */ @@ -114,11 +115,12 @@ export function createRouter(r) { * params?: Record; * search?: import('./index.d.ts').Search; * }} options + * @returns Promise */ function navigate(path, options = {}) { if (typeof path === 'number') { globalThis.history.go(path); - return; + return new Navigation(`History entry: ${path}`); } path = constructPath(path, options.params); @@ -128,6 +130,7 @@ function navigate(path, options = {}) { options.hash = '#' + options.hash; } onNavigate(path, options); + return new Navigation(`${path}${options?.search ?? ''}${options?.hash ?? ''}`); } /** @@ -142,7 +145,7 @@ export async function onNavigate(path, options = {}) { navigationIndex++; const currentNavigationIndex = navigationIndex; - let matchPath = getMatchPath(path); + const matchPath = getMatchPath(path); const { match, layouts, hooks, meta: newMeta, params: newParams } = matchRoute(matchPath, routes); const search = parseSearch(options.search); @@ -176,7 +179,7 @@ export async function onNavigate(path, options = {}) { } if ( navigationIndex !== currentNavigationIndex || - (fromBeforeLoadHook && pendingNavigationIndex + 1 !== currentNavigationIndex) + (fromBeforeLoadHook && pendingNavigationIndex !== currentNavigationIndex) ) { return; } @@ -253,7 +256,7 @@ export function onGlobalClick(event) { event.preventDefault(); const { replace, state, scrollToTop, viewTransition } = anchor.dataset; - onNavigate(path, { + void onNavigate(path, { replace: replace === '' || replace === 'true', search: url.search, state, diff --git a/src/index.d.ts b/src/index.d.ts index e5b540e..6f8c8c1 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -106,6 +106,10 @@ export type IsActiveLink = Action< // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RouteMeta {} +export class Navigation extends Error { + constructor(target: string); +} + export type RouterApi = { /** * Construct a path while ensuring type safety. @@ -139,8 +143,10 @@ export type RouterApi = { * * @param route The route to navigate to. * @param options The navigation options. + * + * Returns an Error for use with `throw navigate(...)` inside hooks. */ - navigate>(...args: NavigateArgs): void; + navigate>(...args: NavigateArgs): Error; /** * Will return `true` if the given path is active. diff --git a/src/index.js b/src/index.js index 15091c0..802c774 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ export { isActiveLink } from './actions.svelte.js'; export { createRouter } from './create-router.svelte.js'; export { serializeSearch } from './helpers/utils.js'; +export { Navigation } from './navigation.js'; export { default as Router } from './Router.svelte'; export { searchParams } from './search-params.svelte.js'; diff --git a/src/navigation.js b/src/navigation.js new file mode 100644 index 0000000..ad2719a --- /dev/null +++ b/src/navigation.js @@ -0,0 +1,9 @@ +export class Navigation extends Error { + constructor( + /** @type {string} target */ + target, + ) { + super(`Navigating to: ${target}`); + this.name = 'Redirect'; + } +}