Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add navigation (but not invalidation) types #6537

Merged
merged 7 commits into from
Sep 3, 2022
Merged
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/little-beers-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[feat] add `type` to navigation object
68 changes: 48 additions & 20 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ export function create_client({ target, base, trailing_slash }) {
};

const callbacks = {
/** @type {Array<(opts: { from: URL, to: URL | null, cancel: () => void }) => void>} */
/** @type {Array<(navigation: import('types').Navigation & { cancel: () => void }) => void>} */
before_navigate: [],

/** @type {Array<(opts: { from: URL | null, to: URL }) => void>} */
/** @type {Array<(navigation: import('types').Navigation) => void>} */
after_navigate: []
};

Expand Down Expand Up @@ -142,8 +142,10 @@ export function create_client({ target, base, trailing_slash }) {

function invalidate() {
if (!invalidating) {
const url = new URL(location.href);

invalidating = Promise.resolve().then(async () => {
await update(new URL(location.href), []);
await update(url, []);

invalidating = null;
force_invalidation = false;
Expand Down Expand Up @@ -177,7 +179,8 @@ export function create_client({ target, base, trailing_slash }) {
replaceState
},
accepted: () => {},
blocked: () => {}
blocked: () => {},
type: 'goto'
});
}

Expand Down Expand Up @@ -395,7 +398,8 @@ export function create_client({ target, base, trailing_slash }) {
});
}

const navigation = { from: null, to: new URL(location.href) };
/** @type {import('types').Navigation} */
const navigation = { from: null, to: new URL(location.href), type: 'load' };
callbacks.after_navigate.forEach((fn) => fn(navigation));

started = true;
Expand Down Expand Up @@ -977,21 +981,44 @@ export function create_client({ target, base, trailing_slash }) {
* replaceState: boolean;
* state: any;
* } | null;
* type: import('types').NavigationType;
* delta?: number;
* accepted: () => void;
* blocked: () => void;
* }} opts
*/
async function navigate({ url, scroll, keepfocus, redirect_chain, details, accepted, blocked }) {
const from = current.url;
async function navigate({
url,
scroll,
keepfocus,
redirect_chain,
details,
type,
delta,
accepted,
blocked
}) {
let should_block = false;

/** @type {import('types').Navigation} */
const navigation = {
from,
from: current.url,
to: url,
cancel: () => (should_block = true)
type
};

if (delta !== undefined) {
navigation.delta = delta;
}

const cancellable = {
...navigation,
cancel: () => {
should_block = true;
}
};

callbacks.before_navigate.forEach((fn) => fn(navigation));
callbacks.before_navigate.forEach((fn) => fn(cancellable));

if (should_block) {
blocked();
Expand All @@ -1003,10 +1030,7 @@ export function create_client({ target, base, trailing_slash }) {
accepted();

if (started) {
stores.navigating.set({
from: current.url,
to: url
});
stores.navigating.set(navigation);
}

await update(
Expand All @@ -1018,9 +1042,7 @@ export function create_client({ target, base, trailing_slash }) {
details
},
() => {
const navigation = { from, to: url };
callbacks.after_navigate.forEach((fn) => fn(navigation));

stores.navigating.set(null);
}
);
Expand Down Expand Up @@ -1129,9 +1151,11 @@ export function create_client({ target, base, trailing_slash }) {
addEventListener('beforeunload', (e) => {
let should_block = false;

/** @type {import('types').Navigation & { cancel: () => void }} */
const navigation = {
from: current.url,
to: null,
type: 'unload',
cancel: () => (should_block = true)
};

Expand Down Expand Up @@ -1244,7 +1268,8 @@ export function create_client({ target, base, trailing_slash }) {
replaceState: url.href === location.href
},
accepted: () => event.preventDefault(),
blocked: () => event.preventDefault()
blocked: () => event.preventDefault(),
type: 'link'
});
});

Expand All @@ -1254,6 +1279,8 @@ export function create_client({ target, base, trailing_slash }) {
// with history.go, which means we end up back here, hence this check
if (event.state[INDEX_KEY] === current_history_index) return;

const delta = event.state[INDEX_KEY] - current_history_index;

navigate({
url: new URL(location.href),
scroll: scroll_positions[event.state[INDEX_KEY]],
Expand All @@ -1264,9 +1291,10 @@ export function create_client({ target, base, trailing_slash }) {
current_history_index = event.state[INDEX_KEY];
},
blocked: () => {
const delta = current_history_index - event.state[INDEX_KEY];
history.go(delta);
}
history.go(-delta);
},
type: 'popstate',
delta
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

<div id="nav-status">
{#if $navigating}
<p id="navigating">navigating from {$navigating.from.pathname} to {$navigating.to.pathname}</p>
<p id="navigating">
navigating from {$navigating.from.pathname} to {$navigating.to.pathname} ({$navigating.type})
</p>
{:else}
<p id="not-navigating">not currently navigating</p>
{/if}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('./$types').PageLoad} */
export async function load() {
await new Promise((f) => setTimeout(f, 50));
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/** @type {import('@sveltejs/kit').Load} */
/** @type {import('./$types').PageLoad} */
export async function load() {
await new Promise((f) => setTimeout(f, 50));
return {};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @type {import('@sveltejs/kit').Load} */
/** @type {import('./$types').PageLoad} */
export async function load() {
await new Promise((f) => setTimeout(f, 1000));
return {};
Expand Down
10 changes: 8 additions & 2 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,7 @@ test.describe('$app/stores', () => {
expect(JSON.parse(await page.textContent('#store-data'))).toEqual(stuff3);
});

test('navigating store contains from and to', async ({ app, page, javaScriptEnabled }) => {
test('navigating store contains from, to and type', async ({ app, page, javaScriptEnabled }) => {
await page.goto('/store/navigating/a');

expect(await page.textContent('#nav-status')).toBe('not currently navigating');
Expand All @@ -1148,10 +1148,16 @@ test.describe('$app/stores', () => {
page.textContent('#navigating')
]);

expect(res[1]).toBe('navigating from /store/navigating/a to /store/navigating/b');
expect(res[1]).toBe('navigating from /store/navigating/a to /store/navigating/b (link)');

await page.waitForSelector('#not-navigating');
expect(await page.textContent('#nav-status')).toBe('not currently navigating');

page.goBack();
await page.waitForSelector('#navigating');
expect(await page.textContent('#navigating')).toBe(
'navigating from /store/navigating/b to /store/navigating/a (popstate)'
);
}
});

Expand Down
20 changes: 14 additions & 6 deletions packages/kit/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ declare module '$app/environment' {
* ```
*/
declare module '$app/navigation' {
import { Navigation } from '@sveltejs/kit';

/**
* If called when the page is being updated following a navigation (in `onMount` or `afterNavigate` or an action, for example), this disables SvelteKit's built-in scroll handling.
* This is generally discouraged, since it breaks user expectations.
Expand Down Expand Up @@ -158,17 +160,23 @@ declare module '$app/navigation' {
export function prefetchRoutes(routes?: string[]): Promise<void>;

/**
* A navigation interceptor that triggers before we navigate to a new URL (internal or external) whether by clicking a link, calling `goto`, or using the browser back/forward controls.
* This is helpful if we want to conditionally prevent a navigation from completing or lookup the upcoming url.
* A navigation interceptor that triggers before we navigate to a new URL, whether by clicking a link, calling `goto(...)`, or using the browser back/forward controls.
* Calling `cancel()` will prevent the navigation from completing.
*
* When navigating to an external URL, `navigation.to` will be `null`.
*
* `beforeNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
*/
export function beforeNavigate(
fn: (navigation: { from: URL; to: URL | null; cancel: () => void }) => void
callback: (navigation: Navigation & { cancel: () => void }) => void
): void;

/**
* A lifecycle function that runs when the page mounts, and also whenever SvelteKit navigates to a new URL but stays on this component.
* A lifecycle function that runs the supplied `callback` when the current component mounts, and also whenever we navigate to a new URL.
*
* `afterNavigate` must be called during a component initialization. It remains active as long as the component is mounted.
*/
export function afterNavigate(fn: (navigation: { from: URL | null; to: URL }) => void): void;
export function afterNavigate(callback: (navigation: Navigation) => void): void;
}

/**
Expand Down Expand Up @@ -210,7 +218,7 @@ declare module '$app/stores' {
export const page: Readable<Page>;
/**
* A readable store.
* When navigating starts, its value is `{ from: URL, to: URL }`,
* When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
* When navigating finishes, its value reverts to `null`.
*/
export const navigating: Readable<Navigation | null>;
Expand Down
8 changes: 6 additions & 2 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,13 @@ export interface LoadEvent<
depends: (...deps: string[]) => void;
}

export type NavigationType = 'load' | 'unload' | 'link' | 'goto' | 'popstate';

export interface Navigation {
from: URL;
to: URL;
from: URL | null;
to: URL | null;
type: NavigationType;
delta?: number;
}

export interface Page<Params extends Record<string, string> = Record<string, string>> {
Expand Down