fix: settings pages preserve focus (#1666)

fixes #1658
This commit is contained in:
Nolan Lawson 2019-12-08 18:03:26 -08:00 committed by GitHub
parent 69d0038fc0
commit 4f9fb5f253
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 36 deletions

View file

@ -1,10 +1,12 @@
<SettingsNav {page} {label}/> <FocusRestoration realm={page}>
<SettingsNav {page} {label}/>
<div class="settings"> <div class="settings">
<FreeTextLayout> <FreeTextLayout>
<slot></slot> <slot></slot>
</FreeTextLayout> </FreeTextLayout>
</div> </div>
</FocusRestoration>
<style> <style>
.settings { .settings {
margin: 20px; margin: 20px;
@ -24,11 +26,13 @@
<script> <script>
import SettingsNav from './SettingsNav.html' import SettingsNav from './SettingsNav.html'
import FreeTextLayout from '../FreeTextLayout' import FreeTextLayout from '../FreeTextLayout'
import FocusRestoration from '../FocusRestoration.html'
export default { export default {
components: { components: {
FreeTextLayout, FreeTextLayout,
SettingsNav SettingsNav,
FocusRestoration
} }
} }
</script> </script>

View file

@ -1,4 +1,5 @@
<a {href} <a {href}
id="settings-list-button-{href}"
on:click="fire('click', event)" on:click="fire('click', event)"
rel="prefetch" rel="prefetch"
aria-label={ariaLabel || label} aria-label={ariaLabel || label}

View file

@ -1,4 +1,5 @@
<a class="settings-nav-item {className}" <a class="settings-nav-item {className}"
id="settings-nav-item-{href}"
aria-label={ariaLabel} aria-label={ariaLabel}
rel="prefetch" rel="prefetch"
{href} > {href} >

View file

@ -2,35 +2,40 @@
<h1>Instances</h1> <h1>Instances</h1>
{#if $isUserLoggedIn} {#if $isUserLoggedIn}
<p>Instances you've logged in to:</p> <p>Instances you've logged in to:</p>
<RadioGroup id="instance-switch" label="Switch to instance" length={numInstances}> <RadioGroup id="instance-switch" label="Switch to instance" length={numInstances}>
<SettingsList label="Instances"> <SettingsList label="Instances">
{#each instanceStates as instance} {#each instanceStates as instance}
<SettingsListRow> <SettingsListRow>
<SettingsListButton className="instance-switcher-instance-name" <SettingsListButton className="instance-switcher-instance-name"
href="/settings/instances/{instance.name}" href="/settings/instances/{instance.name}"
label={instance.name} label={instance.name}
ariaLabel={instance.label} /> ariaLabel={instance.label} />
<div class="instance-switcher-button-wrapper"> <div class="instance-switcher-button-wrapper">
<RadioGroupButton <RadioGroupButton
id="instance-switch" id="instance-switch"
className="instance-switcher-button" className="instance-switcher-button"
label={instance.switchLabel} label={instance.switchLabel}
checked={instance.current} checked={instance.current}
index={instance.index} index={instance.index}
on:click="onSwitchToThisInstance(event, instance.name)"> on:click="onSwitchToThisInstance(event, instance.name)">
<SvgIcon className="instance-switcher-button-svg" <SvgIcon className="instance-switcher-button-svg"
href={instance.current ? '#fa-star' : '#fa-star-o'} /> href={instance.current ? '#fa-star' : '#fa-star-o'} />
</RadioGroupButton> </RadioGroupButton>
</div> </div>
</SettingsListRow> </SettingsListRow>
{/each} {/each}
</SettingsList> </SettingsList>
</RadioGroup> </RadioGroup>
<p><a rel="prefetch" href="/settings/instances/add">Add another instance</a></p> <p>
<a rel="prefetch" href="/settings/instances/add" id="log-in-link-1">Add another instance</a>
</p>
{:else} {:else}
<p>You're not logged in to any instances.</p> <p>You're not logged in to any instances.</p>
<p><a rel="prefetch" href="/settings/instances/add">Log in to an instance</a> to start using Pinafore.</p> <p>
<a rel="prefetch" href="/settings/instances/add" id="log-in-link-2">Log in to an instance</a>
to start using Pinafore.
</p>
{/if} {/if}
</SettingsLayout> </SettingsLayout>
<style> <style>

View file

@ -14,7 +14,7 @@ import {
getActiveElementTagName, getActiveElementTagName,
getActiveElementClassList, getActiveElementClassList,
getNthStatusSensitiveMediaButton, getNthStatusSensitiveMediaButton,
getActiveElementAriaLabel getActiveElementAriaLabel, settingsNavButton, getActiveElementHref
} from '../utils' } from '../utils'
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe' import { Selector as $ } from 'testcafe'
@ -155,3 +155,30 @@ test('preserves focus two levels deep', async t => {
.expect(getUrl()).eql('http://localhost:4002/') .expect(getUrl()).eql('http://localhost:4002/')
.expect(getActiveElementClassList()).contains('status-author-name') .expect(getActiveElementClassList()).contains('status-author-name')
}) })
test('preserves focus on settings page', async t => {
await loginAsFoobar(t)
await t
.click(settingsNavButton)
.click($('a[href="/settings/instances"]'))
.expect(getUrl()).eql('http://localhost:4002/settings/instances')
.click($('a[href="/settings/instances/add"]'))
.expect(getUrl()).eql('http://localhost:4002/settings/instances/add')
await goBack()
await t
.expect(getUrl()).eql('http://localhost:4002/settings/instances')
.expect(getActiveElementHref()).eql('/settings/instances/add')
await goBack()
await t
.expect(getUrl()).eql('http://localhost:4002/settings')
.expect(getActiveElementHref()).eql('/settings/instances')
.click($('a[href="/settings/instances"]'))
.expect(getUrl()).eql('http://localhost:4002/settings/instances')
.click($('a.settings-nav-item[href="/settings"]'))
.expect(getUrl()).eql('http://localhost:4002/settings')
await goBack()
await t
.expect(getUrl()).eql('http://localhost:4002/settings/instances')
.expect(getActiveElementHref()).eql('/settings')
.expect(getActiveElementClassList()).contains('settings-nav-item')
})

View file

@ -132,6 +132,10 @@ export const getActiveElementClassList = exec(() =>
(document.activeElement && (document.activeElement.getAttribute('class') || '').split(/\s+/)) || [] (document.activeElement && (document.activeElement.getAttribute('class') || '').split(/\s+/)) || []
) )
export const getActiveElementHref = exec(() =>
(document.activeElement && (document.activeElement.getAttribute('href') || ''))
)
export const getActiveElementTagName = exec(() => export const getActiveElementTagName = exec(() =>
(document.activeElement && document.activeElement.tagName) || '' (document.activeElement && document.activeElement.tagName) || ''
) )