From e3b698e082d1dafdf88f41da524e22a9629165b2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 6 Jan 2021 17:54:11 -0500 Subject: [PATCH] reset focus properly on navigation (#307) --- .changeset/tidy-books-reply.md | 5 ++++ .../kit/src/runtime/internal/router/index.js | 7 +++--- .../basics/src/routes/focus/$layout.svelte | 3 +++ .../apps/basics/src/routes/focus/__tests__.js | 21 ++++++++++++++++ test/apps/basics/src/routes/focus/a.svelte | 1 + test/apps/basics/src/routes/focus/b.svelte | 1 + test/runner.js | 24 ++++++++++--------- 7 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 .changeset/tidy-books-reply.md create mode 100644 test/apps/basics/src/routes/focus/$layout.svelte create mode 100644 test/apps/basics/src/routes/focus/__tests__.js create mode 100644 test/apps/basics/src/routes/focus/a.svelte create mode 100644 test/apps/basics/src/routes/focus/b.svelte diff --git a/.changeset/tidy-books-reply.md b/.changeset/tidy-books-reply.md new file mode 100644 index 000000000000..d744b1ea4212 --- /dev/null +++ b/.changeset/tidy-books-reply.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Reset focus properly diff --git a/packages/kit/src/runtime/internal/router/index.js b/packages/kit/src/runtime/internal/router/index.js index 55e8c271dad5..2e9680f4ad5a 100644 --- a/packages/kit/src/runtime/internal/router/index.js +++ b/packages/kit/src/runtime/internal/router/index.js @@ -124,6 +124,9 @@ export class Router { const selected = this.select(new URL(location.href)); if (selected) return this.renderer.start(selected); + + // make it possible to reset focus + document.body.setAttribute('tabindex', '-1'); } select(url) { @@ -177,9 +180,7 @@ export class Router { await this.renderer.render(selected); - if (document.activeElement instanceof HTMLElement) { - document.activeElement.blur(); - } + document.body.focus(); const deep_linked = hash && document.getElementById(hash.slice(1)); if (scroll) { diff --git a/test/apps/basics/src/routes/focus/$layout.svelte b/test/apps/basics/src/routes/focus/$layout.svelte new file mode 100644 index 000000000000..2ee09c61af46 --- /dev/null +++ b/test/apps/basics/src/routes/focus/$layout.svelte @@ -0,0 +1,3 @@ + + + diff --git a/test/apps/basics/src/routes/focus/__tests__.js b/test/apps/basics/src/routes/focus/__tests__.js new file mode 100644 index 000000000000..8168a2e93327 --- /dev/null +++ b/test/apps/basics/src/routes/focus/__tests__.js @@ -0,0 +1,21 @@ +import * as assert from 'uvu/assert'; + +export default function (test) { + test.only('resets focus', async ({ visit, click, keyboard, evaluate, contains }) => { + await visit('/focus/a'); + + await click('[href="/focus/b"]'); + assert.ok(await contains('b')); + assert.equal(await evaluate(() => document.activeElement.nodeName), 'BODY'); + await keyboard.press('Tab'); + assert.equal(await evaluate(() => document.activeElement.nodeName), 'A'); + assert.equal(await evaluate(() => document.activeElement.textContent), 'a'); + + await click('[href="/focus/a"]'); + assert.ok(await contains('a')); + assert.equal(await evaluate(() => document.activeElement.nodeName), 'BODY'); + await keyboard.press('Tab'); + assert.equal(await evaluate(() => document.activeElement.nodeName), 'A'); + assert.equal(await evaluate(() => document.activeElement.textContent), 'a'); + }); +} diff --git a/test/apps/basics/src/routes/focus/a.svelte b/test/apps/basics/src/routes/focus/a.svelte new file mode 100644 index 000000000000..3a8b953071ad --- /dev/null +++ b/test/apps/basics/src/routes/focus/a.svelte @@ -0,0 +1 @@ +

a

diff --git a/test/apps/basics/src/routes/focus/b.svelte b/test/apps/basics/src/routes/focus/b.svelte new file mode 100644 index 000000000000..db2e4f2757c0 --- /dev/null +++ b/test/apps/basics/src/routes/focus/b.svelte @@ -0,0 +1 @@ +

b

diff --git a/test/runner.js b/test/runner.js index 5ad8f56793dd..e670d45bdb1d 100644 --- a/test/runner.js +++ b/test/runner.js @@ -15,7 +15,8 @@ async function setup({ port }) { await page .waitForFunction( ({ expectedValue, selector }) => - document.querySelector(selector) && document.querySelector(selector).textContent === expectedValue, + document.querySelector(selector) && + document.querySelector(selector).textContent === expectedValue, { expectedValue, selector }, { timeout: defaultTimeout } ) @@ -28,7 +29,7 @@ async function setup({ port }) { const capture_requests = async (operations) => { const requests = []; - const on_request = request => requests.push(request.url()); + const on_request = (request) => requests.push(request.url()); page.on('request', on_request); try { @@ -56,9 +57,9 @@ async function setup({ port }) { return { base, - visit: path => page.goto(base + path), - contains: async str => (await page.innerHTML('body')).includes(str), - html: async selector => await page.innerHTML(selector, { timeout: defaultTimeout }), + visit: (path) => page.goto(base + path), + contains: async (str) => (await page.innerHTML('body')).includes(str), + html: async (selector) => await page.innerHTML(selector, { timeout: defaultTimeout }), fetch: (url, opts) => fetch(`${base}${url}`, opts), text, evaluate: (fn) => page.evaluate(fn), @@ -73,9 +74,10 @@ async function setup({ port }) { wait_for_function: (fn, arg, options) => page.waitForFunction(fn, arg, { timeout: defaultTimeout, ...options }), capture_requests, - set_extra_http_headers: headers => page.setExtraHTTPHeaders(headers), + set_extra_http_headers: (headers) => page.setExtraHTTPHeaders(headers), pathname: () => page.url().replace(base, ''), - $: selector => page.$(selector) + keyboard: page.keyboard, + $: (selector) => page.$(selector) }; } @@ -88,7 +90,7 @@ export function runner(callback, options = {}) { const duplicate = (test_fn) => { return (name, callback) => { - test_fn(`${name} [no js]`, async context => { + test_fn(`${name} [no js]`, async (context) => { await callback({ ...context, js: false @@ -96,7 +98,7 @@ export function runner(callback, options = {}) { }); if (!options.amp) { - test_fn(`${name} [js]`, async context => { + test_fn(`${name} [js]`, async (context) => { await callback({ ...context, js: true, @@ -108,8 +110,8 @@ export function runner(callback, options = {}) { }); }); } - } - } + }; + }; const test = duplicate(suite); test.skip = duplicate(suite.skip);