Repository: sveltejs/kit (js)

Issue:
Unable to `disableScrollHandling` from `sveltekit:navigation-start` event
### Describe the bug

I'm not sure whether to classify this as a bug or a feature request. `disableScrollHandling` is very restrictive as to when it can be called. It seems like only actions and `onMount` work. However, I would like to disable scroll handling for every page on which a specific component is mounted, so I can't use onMount because the component might be mounted across multiple pages. The `sveletekit:navigation-start` event seems perfect for this, but it is not possible to disable scroll handling from there.

It's worth noting that I tried `$: { if ($navigating) { disableScrollHandling() } }` and `beforeUpdate(disableScrollHandling)` and those aren't allowed either

### Reproduction

https://stackblitz.com/edit/sveltekit-yzgemn?file=src/routes/index.svelte

Clicking on "About" link causes `Error: Can only disable scroll handling during navigation`.

### Logs

```shell
start.js:516 Uncaught (in promise) Error: Can only disable scroll handling during navigation
    at Renderer.disable_scroll_handling (start.js:516)
    at disableScrollHandling_ (navigation.js:27)
    at Router._navigate (start.js:272)
    at start.js:176
```
```


### System Info

```shell
System:
    OS: Linux undefined
    CPU: (4) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: Unknown - /bin/jsh
  Binaries:
    Node: 14.16.0 - /usr/local/bin/node
    Yarn: 1.22.10 - /bin/yarn
    npm: 7.17.0 - /bin/npm
  npmPackages:
    @sveltejs/kit: ^1.0.0-next.216 => 1.0.0-next.216 
    svelte: ^3.42.6 => 3.44.3 
```
```


### Severity

serious, but I can work around it

### Additional Information

_No response_

Pull request description:
allow `disableScrollHandling()` to be called in `afterNavigate`
fixes #3220

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/polite-files-explain.md b/.changeset/polite-files-explain.md
new file mode 100644
index 000000000..7934d76e7
--- /dev/null
+++ b/.changeset/polite-files-explain.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+allow disableScrollHandling to be called in afterNavigate
diff --git a/documentation/faq/50-aliases.md b/documentation/faq/50-aliases.md
index 99b8a954d..fcfeebca7 100644
--- a/documentation/faq/50-aliases.md
+++ b/documentation/faq/50-aliases.md
@@ -2,40 +2,6 @@
 title: How do I setup a path alias?
 ---
 
-First, you need to add it to the Vite configuration. In `svelte.config.js` add [`vite.resolve.alias`](https://vitejs.dev/config/#resolve-alias):
+Aliases can be set in `svelte.config.js` as described in the [`configuration`](/docs/configuration#alias) docs.
 
-```js
-/// file: svelte.config.js
-// @filename: ambient.d.ts
-declare module 'path';
-
-// @filename: index.js
-import path from 'path';
-
-/** @type {import('@sveltejs/kit').Config} */
-const config = {
-	kit: {
-		vite: {
-			resolve: {
-				alias: {
-					$utils: path.resolve('./src/utils')
-				}
-			}
-		}
-	}
-};
-
-export default config;
-```
-
-Then, to make TypeScript aware of the alias, add it to `tsconfig.json` (for TypeScript users) or `jsconfig.json`:
-
-```json
-{
-	"compilerOptions": {
-		"paths": {
-			"$utils/*": ["src/utils/*"]
-		}
-	}
-}
-```
+Then run `npm run sync` or `npm run dev` (which will execute `sync`). SvelteKit will automatically generate the required alias configuration in `jsconfig.json` or `tsconfig.json`.
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 3962f7d92..dc1c81616 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -232,8 +232,9 @@ export function create_client({ target, session, base, trailing_slash }) {
 	 * @param {string[]} redirect_chain
 	 * @param {boolean} no_cache
 	 * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null}} [opts]
+	 * @param {() => void} [callback]
 	 */
-	async function update(url, redirect_chain, no_cache, opts) {
+	async function update(url, redirect_chain, no_cache, opts, callback) {
 		const intent = get_navigation_intent(url);
 
 		const current_token = (token = {});
@@ -360,7 +361,6 @@ export function create_client({ target, session, base, trailing_slash }) {
 		load_cache.promise = null;
 		load_cache.id = null;
 		autoscroll = true;
-		updating = false;
 
 		if (navigation_result.props.page) {
 			page = navigation_result.props.page;
@@ -369,7 +369,9 @@ export function create_client({ target, session, base, trailing_slash }) {
 		const leaf_node = navigation_result.state.branch[navigation_result.state.branch.length - 1];
 		router_enabled = leaf_node?.module.router !== false;
 
-		return true;
+		if (callback) callback();
+
+		updating = false;
 	}
 
 	/** @param {import('./types').NavigationResult} result */
@@ -387,12 +389,12 @@ export function create_client({ target, session, base, trailing_slash }) {
 			hydrate: true
 		});
 
-		started = true;
-
 		if (router_enabled) {
 			const navigation = { from: null, to: new URL(location.href) };
 			callbacks.after_navigate.forEach((fn) => fn(navigation));
 		}
+
+		started = true;
 	}
 
 	/**
@@ -978,18 +980,22 @@ export function create_client({ target, session, base, trailing_slash }) {
 			});
 		}
 
-		const completed = await update(normalized, redirect_chain, false, {
-			scroll,
-			keepfocus,
-			details
-		});
-
-		if (completed) {
-			const navigation = { from, to: normalized };
-			callbacks.after_navigate.forEach((fn) => fn(navigation));
+		await update(
+			normalized,
+			redirect_chain,
+			false,
+			{
+				scroll,
+				keepfocus,
+				details
+			},
+			() => {
+				const navigation = { from, to: normalized };
+				callbacks.after_navigate.forEach((fn) => fn(navigation));
 
-			stores.navigating.set(null);
-		}
+				stores.navigating.set(null);
+			}
+		);
 	}
 
 	/**
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 5d1e7da7b..89a0285f2 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -88,7 +88,7 @@ declare module '$app/env' {
  */
 declare module '$app/navigation' {
 	/**
-	 * If called when the page is being updated following a navigation (in `onMount` or an action, for example), this disables SvelteKit's built-in scroll handling.
+	 * 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.
 	 */
 	export function disableScrollHandling(): void;


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor-afternavigate.svelte b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor-afternavigate.svelte
new file mode 100644
index 000000000..8ef1440bd
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor-afternavigate.svelte
@@ -0,0 +1,17 @@
+<script>
+	import { afterNavigate, disableScrollHandling } from '$app/navigation';
+
+	afterNavigate(() => {
+		disableScrollHandling();
+		document.getElementById('abcde')?.scrollIntoView();
+	});
+</script>
+
+<div style="height: 180vh; background-color: hotpink;">They (don't) see me...</div>
+<div style="height: 180vh; background-color: peru;">
+	<p id="go-to-element">The browser scrolls to me</p>
+</div>
+<p id="abcde" style="height: 180vh; background-color: hotpink;">I take precedence</p>
+<div />
+
+<a href="/anchor-with-manual-scroll/anchor-afternavigate?x=y#go-to-element">reload me</a>
\ No newline at end of file
diff --git a/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor.svelte b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor-onmount.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor.svelte
rename to packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor-onmount.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/index.svelte b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/index.svelte
index 9a292a21b..7608357a8 100644
--- a/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/index.svelte
+++ b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/index.svelte
@@ -1,5 +1,5 @@
 <h1>Welcome to a test project</h1>
-<a href="/anchor-with-manual-scroll/anchor#go-to-element">Anchor demo</a>
+<a href="/anchor-with-manual-scroll/anchor-onmount#go-to-element">Anchor demo</a>
 
 <style>
 	:global(body) {
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 56ba9abb4..9d60f4964 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -259,7 +259,19 @@ test.describe('Scrolling', () => {
 		page,
 		in_view
 	}) => {
-		await page.goto('/anchor-with-manual-scroll/anchor#go-to-element');
+		await page.goto('/anchor-with-manual-scroll/anchor-onmount#go-to-element');
+		expect(await in_view('#abcde')).toBe(true);
+	});
+
+	test('url-supplied anchor is ignored with afterNavigate() scrolling on direct page load', async ({
+		page,
+		in_view,
+		clicknav
+	}) => {
+		await page.goto('/anchor-with-manual-scroll/anchor-afternavigate#go-to-element');
+		expect(await in_view('#abcde')).toBe(true);
+
+		await clicknav('[href="/anchor-with-manual-scroll/anchor-afternavigate?x=y#go-to-element"]');
 		expect(await in_view('#abcde')).toBe(true);
 	});
 
@@ -270,7 +282,7 @@ test.describe('Scrolling', () => {
 		in_view
 	}) => {
 		await page.goto('/anchor-with-manual-scroll');
-		await clicknav('[href="/anchor-with-manual-scroll/anchor#go-to-element"]');
+		await clicknav('[href="/anchor-with-manual-scroll/anchor-onmount#go-to-element"]');
 		if (javaScriptEnabled) expect(await in_view('#abcde')).toBe(true);
 		else expect(await in_view('#go-to-element')).toBe(true);
 	});