Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slimy-suits-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: export event for navigation handler when navigation is popstate or link
105 changes: 91 additions & 14 deletions packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ export interface NavigationTarget<
*/
export type NavigationType = 'enter' | 'form' | 'leave' | 'link' | 'goto' | 'popstate';

export interface Navigation {
export interface NavigationBase {
/**
* Where navigation was triggered from
*/
Expand All @@ -1212,6 +1212,18 @@ export interface Navigation {
* Where navigation is going to/has gone to
*/
to: NavigationTarget | null;
/**
* Whether or not the navigation will result in the page being unloaded (i.e. not a client-side navigation)
*/
willUnload: boolean;
/**
* A promise that resolves once the navigation is complete, and rejects if the navigation
* fails or is aborted. In the case of a `willUnload` navigation, the promise will never resolve
*/
complete: Promise<void>;
}

export interface NavigationEnter extends NavigationBase {
/**
* The type of navigation:
* - `form`: The user submitted a `<form method="GET">`
Expand All @@ -1220,36 +1232,101 @@ export interface Navigation {
* - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
* - `popstate`: Navigation was triggered by back/forward navigation
*/
type: Exclude<NavigationType, 'enter'>;
type: 'enter';

/**
* Whether or not the navigation will result in the page being unloaded (i.e. not a client-side navigation)
* In case of a history back/forward navigation, the number of steps to go back/forward
*/
willUnload: boolean;
delta?: undefined;

/**
* Dispatched `Event` object when navigation occured by `popstate` or `link`.
*/
event?: undefined;
}

export interface NavigationExternal extends NavigationBase {
/**
* The type of navigation:
* - `form`: The user submitted a `<form method="GET">`
* - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
* - `link`: Navigation was triggered by a link click
* - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
* - `popstate`: Navigation was triggered by back/forward navigation
*/
type: Exclude<NavigationType, 'enter' | 'popstate' | 'link'>;

/**
* In case of a history back/forward navigation, the number of steps to go back/forward
*/
delta?: number;
delta?: undefined;

/**
* A promise that resolves once the navigation is complete, and rejects if the navigation
* fails or is aborted. In the case of a `willUnload` navigation, the promise will never resolve
* Dispatched `Event` object when navigation occured by `popstate` or `link`.
*/
complete: Promise<void>;
event?: undefined;
}

export interface NavigationPopState extends NavigationBase {
/**
* The type of navigation:
* - `form`: The user submitted a `<form method="GET">`
* - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
* - `link`: Navigation was triggered by a link click
* - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
* - `popstate`: Navigation was triggered by back/forward navigation
*/
type: 'popstate';

/**
* In case of a history back/forward navigation, the number of steps to go back/forward
*/
delta: number;

/**
* Dispatched `PopStateEvent` object when the user navigates back or forward.
*/
event: PopStateEvent;
}

export interface NavigationLink extends NavigationBase {
/**
* The type of navigation:
* - `form`: The user submitted a `<form method="GET">`
* - `leave`: The app is being left either because the tab is being closed or a navigation to a different document is occurring
* - `link`: Navigation was triggered by a link click
* - `goto`: Navigation was triggered by a `goto(...)` call or a redirect
* - `popstate`: Navigation was triggered by back/forward navigation
*/
type: 'link';

/**
* In case of a history back/forward navigation, the number of steps to go back/forward
*/
delta?: undefined;

/**
* Dispatched `MouseEvent` object when the user clicks link element.
*/
event: MouseEvent;
}

export type Navigation = NavigationExternal | NavigationPopState | NavigationLink;

/**
* The argument passed to [`beforeNavigate`](https://svelte.dev/docs/kit/$app-navigation#beforeNavigate) callbacks.
*/
export interface BeforeNavigate extends Navigation {
export type BeforeNavigate = Navigation & {
/**
* Call this to prevent the navigation from starting.
*/
cancel: () => void;
}
};

/**
* The argument passed to [`onNavigate`](https://svelte.dev/docs/kit/$app-navigation#onNavigate) callbacks.
*/
export interface OnNavigate extends Navigation {
export type OnNavigate = Navigation & {
/**
* The type of navigation:
* - `form`: The user submitted a `<form method="GET">`
Expand All @@ -1262,12 +1339,12 @@ export interface OnNavigate extends Navigation {
* Since `onNavigate` callbacks are called immediately before a client-side navigation, they will never be called with a navigation that unloads the page.
*/
willUnload: false;
}
};

/**
* The argument passed to [`afterNavigate`](https://svelte.dev/docs/kit/$app-navigation#afterNavigate) callbacks.
*/
export interface AfterNavigate extends Omit<Navigation, 'type'> {
export type AfterNavigate = (Navigation | NavigationEnter) & {
/**
* The type of navigation:
* - `enter`: The app has hydrated/started
Expand All @@ -1281,7 +1358,7 @@ export interface AfterNavigate extends Omit<Navigation, 'type'> {
* Since `afterNavigate` callbacks are called after a navigation completes, they will never be called with a navigation that unloads the page.
*/
willUnload: false;
}
};

/**
* The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store.
Expand Down
34 changes: 25 additions & 9 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -1402,9 +1402,10 @@ function get_page_key(url) {
* type: import('@sveltejs/kit').Navigation["type"];
* intent?: import('./types.js').NavigationIntent;
* delta?: number;
* event?: PopStateEvent | MouseEvent;
* }} opts
*/
function _before_navigate({ url, type, intent, delta }) {
function _before_navigate({ url, type, intent, delta, event }) {
let should_block = false;

const nav = create_navigation(current, intent, url, type);
Expand All @@ -1413,6 +1414,10 @@ function _before_navigate({ url, type, intent, delta }) {
nav.navigation.delta = delta;
}

if (event !== undefined) {
nav.navigation.event = event;
}

const cancellable = {
...nav.navigation,
cancel: () => {
Expand All @@ -1437,6 +1442,7 @@ function _before_navigate({ url, type, intent, delta }) {
* state: Record<string, any>;
* scroll: { x: number, y: number };
* delta: number;
* popStateEvent?: PopStateEvent;
* };
* keepfocus?: boolean;
* noscroll?: boolean;
Expand All @@ -1446,6 +1452,7 @@ function _before_navigate({ url, type, intent, delta }) {
* nav_token?: {};
* accept?: () => void;
* block?: () => void;
* mouseEvent?: MouseEvent
* }} opts
*/
async function navigate({
Expand All @@ -1459,7 +1466,8 @@ async function navigate({
redirect_count = 0,
nav_token = {},
accept = noop,
block = noop
block = noop,
mouseEvent
}) {
const prev_token = token;
token = nav_token;
Expand All @@ -1468,7 +1476,13 @@ async function navigate({
const nav =
type === 'enter'
? create_navigation(current, intent, url, type)
: _before_navigate({ url, type, delta: popped?.delta, intent });
: _before_navigate({
url,
type,
delta: popped?.delta,
intent,
event: popped?.popStateEvent || mouseEvent
});

if (!nav) {
block();
Expand Down Expand Up @@ -2381,7 +2395,7 @@ function _start_router() {

// Ignore the following but fire beforeNavigate
if (external || (options.reload && (!same_pathname || !hash))) {
if (_before_navigate({ url, type: 'link' })) {
if (_before_navigate({ url, type: 'link', event })) {
// set `navigating` to `true` to prevent `beforeNavigate` callbacks
// being called when the page unloads
is_navigating = true;
Expand Down Expand Up @@ -2450,7 +2464,8 @@ function _start_router() {
url,
keepfocus: options.keepfocus,
noscroll: options.noscroll,
replace_state: options.replace_state ?? url.href === location.href
replace_state: options.replace_state ?? url.href === location.href,
mouseEvent: event
});
});

Expand Down Expand Up @@ -2550,7 +2565,8 @@ function _start_router() {
popped: {
state,
scroll,
delta
delta,
popStateEvent: event
},
accept: () => {
current_history_index = history_index;
Expand Down Expand Up @@ -2995,8 +3011,8 @@ function create_navigation(current, intent, url, type) {
// Handle any errors off-chain so that it doesn't show up as an unhandled rejection
complete.catch(() => {});

/** @type {Omit<import('@sveltejs/kit').Navigation, 'type'> & { type: T }} */
const navigation = {
/** @type {(import('@sveltejs/kit').Navigation | import('@sveltejs/kit').AfterNavigate) & { type: T }} */
const navigation = /** @type {any} */ ({
from: {
params: current.params,
route: { id: current.route?.id ?? null },
Expand All @@ -3010,7 +3026,7 @@ function create_navigation(current, intent, url, type) {
willUnload: !intent,
type,
complete
};
});

return {
navigation,
Expand Down
Loading
Loading