diff --git a/runtime/src/app/goto/index.ts b/runtime/src/app/goto/index.ts index 0241b2c65..79cfb8d3f 100644 --- a/runtime/src/app/goto/index.ts +++ b/runtime/src/app/goto/index.ts @@ -1,11 +1,11 @@ import { history, select_target, navigate, cid } from '../app'; -export default function goto(href: string, opts = { replaceState: false }) { +export default function goto(href: string, opts = { noscroll: false, replaceState: false }) { const target = select_target(new URL(href, document.baseURI)); if (target) { history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href); - return navigate(target, null).then(() => {}); + return navigate(target, null, opts.noscroll).then(() => {}); } location.href = href; diff --git a/runtime/src/app/start/index.ts b/runtime/src/app/start/index.ts index 01c1f47ec..5f6027682 100644 --- a/runtime/src/app/start/index.ts +++ b/runtime/src/app/start/index.ts @@ -110,7 +110,7 @@ function handle_click(event: MouseEvent) { const target = select_target(url); if (target) { - const noscroll = a.hasAttribute('sapper-noscroll'); + const noscroll = a.hasAttribute('sapper:noscroll'); navigate(target, null, noscroll, url.hash); event.preventDefault(); history.pushState({ id: cid }, '', url.href); diff --git a/site/content/docs/03-client-api.md b/site/content/docs/03-client-api.md index 479249d65..e1401a128 100644 --- a/site/content/docs/03-client-api.md +++ b/site/content/docs/03-client-api.md @@ -27,7 +27,9 @@ sapper.start({ ### goto(href, options?) * `href` — the page to go to -* `options` — can include a `replaceState` property, which determines whether to use `history.pushState` (the default) or `history.replaceState`. Not required +* `options` — not required + * `replaceState` (`boolean`, default `false`) — determines whether to use `history.pushState` (the default) or `history.replaceState`. + * `noscroll` (`boolean`, default `false`) — prevent scroll to top after navigation. Programmatically navigates to the given `href`. If the destination is a Sapper route, Sapper will handle the navigation, otherwise the page will be reloaded with the new `href`. In other words, the behaviour is as though the user clicked on a link with this `href`. diff --git a/site/content/docs/08-link-options.md b/site/content/docs/08-link-options.md index 7f1a5f90b..f4c860cc3 100644 --- a/site/content/docs/08-link-options.md +++ b/site/content/docs/08-link-options.md @@ -31,3 +31,15 @@ Adding a `rel=external` attribute to a link... ``` ...will trigger a browser navigation when the link is clicked. + +### sapper:noscroll + +When navigating to internal links, Sapper will change the scroll position to 0,0 so that the user is at the very top left of the page. When a hash is defined, it will scroll to the element with a matching ID. + +In certain cases, you may wish to disable this behaviour. Adding a `sapper:noscroll` attribute to a link... + +```html +Path +``` + +...will prevent scrolling after the link is clicked. diff --git a/test/apps/scroll/src/routes/search-form.svelte b/test/apps/scroll/src/routes/search-form.svelte new file mode 100644 index 000000000..6824ac1ea --- /dev/null +++ b/test/apps/scroll/src/routes/search-form.svelte @@ -0,0 +1,20 @@ + + +

A search form

+ +
+ + diff --git a/test/apps/scroll/test.ts b/test/apps/scroll/test.ts index 30c269ffb..b071e6426 100644 --- a/test/apps/scroll/test.ts +++ b/test/apps/scroll/test.ts @@ -99,7 +99,33 @@ describe('scroll', function() { const secondScrollY = await r.page.evaluate(() => window.scrollY); assert.equal(firstScrollY, secondScrollY); - }); + }); + + it('scrolls to the top when navigating with goto', async () => { + await r.load(`/search-form#search`); + await r.sapper.start(); + + let initialScrollY = await r.page.evaluate(() => window.scrollY); + assert.ok(initialScrollY > 0, String(initialScrollY)); + + await r.page.click(`button#scroll`); + + let scrollY = await r.page.evaluate(() => window.scrollY); + assert.ok(scrollY === 0, String(scrollY)); + }); + + it('preserves scroll when noscroll: true is passed to goto', async () => { + await r.load(`/search-form#search`); + await r.sapper.start(); + + let initialScrollY = await r.page.evaluate(() => window.scrollY); + assert.ok(initialScrollY > 0, String(initialScrollY)); + + await r.page.click(`button#preserve`); + + let scrollY = await r.page.evaluate(() => window.scrollY); + assert.ok(scrollY === initialScrollY, String(scrollY)); + }); it('survives the tests with no server errors', () => { assert.deepEqual(r.errors, []);