diff --git a/.changeset/wicked-plums-count.md b/.changeset/wicked-plums-count.md new file mode 100644 index 000000000000..3651a46c63b1 --- /dev/null +++ b/.changeset/wicked-plums-count.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +[fix] scroll to elements provided via URL hash diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 79d15f9600d5..649cf07b45bc 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -275,12 +275,17 @@ export class Renderer { this._init(navigation_result); } - if (opts) { - const { hash, scroll, keepfocus } = opts; + if (!opts?.keepfocus) { + document.body.focus(); + } - if (!keepfocus) { - document.body.focus(); - } + await 0; + + // After `await 0`, the onMount() function in the component executed. + // If there was no scrolling happening (checked via pageYOffset), + // continue on our custom scroll handling + if (pageYOffset === 0 && opts) { + const { hash, scroll } = opts; const deep_linked = hash && document.getElementById(hash.slice(1)); if (scroll) { @@ -295,8 +300,6 @@ export class Renderer { } } - await 0; - this.loading.promise = null; this.loading.id = null; diff --git a/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/_tests.js b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/_tests.js new file mode 100644 index 000000000000..ebc787a52896 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/_tests.js @@ -0,0 +1,31 @@ +import * as assert from 'uvu/assert'; + +/** + * A suite of tests that checks that SvelteKit URL anchor is ignored when the + * onMount() lifecycle method contains custom scroll logic. + * @type {import('test').TestMaker} + */ +export default function (test) { + test( + 'url-supplied anchor is ignored with onMount() scrolling on direct page load', + '/anchor-with-manual-scroll/anchor#go-to-element', + async ({ page, js }) => { + if (js) { + const p = await page.$('#abcde'); + assert.ok(p && (await p.isVisible())); + } + } + ); + + test( + 'url-supplied anchor is ignored with onMount() scrolling on navigation to page', + '/anchor-with-manual-scroll', + async ({ page, clicknav, js }) => { + await clicknav('[href="/anchor-with-manual-scroll/anchor#go-to-element"]'); + if (js) { + const p = await page.$('#abcde'); + assert.ok(p && (await p.isVisible())); + } + } + ); +} 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.svelte new file mode 100644 index 000000000000..f6cf0bb44e59 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/anchor.svelte @@ -0,0 +1,20 @@ + + +
They (don't) see me...
+
+

The browser scrolls to me

+
+

I take precedence

+ + 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 new file mode 100644 index 000000000000..9a292a21b748 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/anchor-with-manual-scroll/index.svelte @@ -0,0 +1,15 @@ +

Welcome to a test project

+Anchor demo + + diff --git a/packages/kit/test/apps/basics/src/routes/anchor/_tests.js b/packages/kit/test/apps/basics/src/routes/anchor/_tests.js new file mode 100644 index 000000000000..660bf1457a14 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/anchor/_tests.js @@ -0,0 +1,31 @@ +import * as assert from 'uvu/assert'; + +/** + * A suite of tests that checks that SvelteKit URL anchor is respected and the + * element with the anchor ID is scrolled into view. + * @type {import('test').TestMaker} + */ +export default function (test) { + test( + 'url-supplied anchor works on direct page load', + '/anchor/anchor#go-to-element', + async ({ page, js }) => { + if (js) { + const p = await page.$('#go-to-element'); + assert.ok(p && (await p.isVisible())); + } + } + ); + + test( + 'url-supplied anchor works on navigation to page', + '/anchor', + async ({ page, clicknav, js }) => { + await clicknav('[href="/anchor/anchor#go-to-element"]'); + if (js) { + const p = await page.$('#go-to-element'); + assert.ok(p && (await p.isVisible())); + } + } + ); +} diff --git a/packages/kit/test/apps/basics/src/routes/anchor/anchor.svelte b/packages/kit/test/apps/basics/src/routes/anchor/anchor.svelte new file mode 100644 index 000000000000..2c4e5feeebf8 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/anchor/anchor.svelte @@ -0,0 +1,11 @@ +
They (don't) see me...
+
+

The browser scrolls to me

+
+ + diff --git a/packages/kit/test/apps/basics/src/routes/anchor/index.svelte b/packages/kit/test/apps/basics/src/routes/anchor/index.svelte new file mode 100644 index 000000000000..b89b04ad2874 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/anchor/index.svelte @@ -0,0 +1,15 @@ +

Welcome to a test project

+Anchor demo + + diff --git a/packages/kit/test/apps/basics/src/routes/use-action/_tests.js b/packages/kit/test/apps/basics/src/routes/use-action/_tests.js index aa9ee67ea3ce..007ba55e55ef 100644 --- a/packages/kit/test/apps/basics/src/routes/use-action/_tests.js +++ b/packages/kit/test/apps/basics/src/routes/use-action/_tests.js @@ -12,7 +12,7 @@ export default function (test) { async ({ page, js }) => { if (js) { const input = await page.$('#input'); - assert.ok(input && input.isVisible); + assert.ok(input && (await input.isVisible())); assert.ok(await page.$eval('#input', (el) => el === document.activeElement)); } } @@ -25,7 +25,7 @@ export default function (test) { await clicknav('[href="/use-action/focus-and-scroll"]'); if (js) { const input = await page.$('#input'); - assert.ok(input && input.isVisible); + assert.ok(input && (await input.isVisible())); assert.ok(await page.$eval('#input', (el) => el === document.activeElement)); } }