Last active
March 1, 2021 13:51
-
-
Save jssteinberg/e1c67f8a49251947fcd5b08e27ab5a4c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script context="module"> | |
import { writable } from 'svelte/store'; | |
/** @type {string} a11yTitle - A store to use for the title to announce on | |
* route change for screen readers. */ | |
export const a11yTitle = writable(''); | |
</script> | |
<script> | |
import { stores } from '@sapper/app'; | |
import { tick } from 'svelte'; | |
const { page } = stores(); | |
const accessibleRouteChange = async () => { | |
if (!elWrapper) return; | |
// Two ticks are needed for some reason for Sapper to finish route change | |
// stuff, including scrolling to top | |
await tick(); await tick(); | |
// Focus wrapper without scroll (Sapper does that) | |
elWrapper.focus({ preventScroll: true }); | |
}; | |
const focusMain = () => { | |
if (!elMain) return; | |
elMain.focus({ preventScroll: true }); | |
elMain.scrollIntoView({ behavior: 'smooth' }); | |
}; | |
const focusTop = () => { | |
if (!elWrapper) return; | |
elWrapper.focus({ preventScroll: true }); | |
elWrapper.scrollIntoView({ behavior: 'smooth' }); | |
}; | |
let classes = null; | |
let language = null; | |
let elMain; | |
let elWrapper; | |
let pathname = ''; | |
export { | |
classes as class, | |
language as lang, | |
}; | |
export let toTopLabel = 'Top of page'; | |
page.subscribe(val => { | |
pathname = val.path.replace(/\/$/, ''); | |
accessibleRouteChange(); | |
}); | |
</script> | |
<div | |
tabindex="-1" | |
bind:this={elWrapper} | |
class="{classes ? classes : undefined}" | |
lang="{language ? language : undefined}" | |
> | |
<a | |
href={pathname}#main | |
class="sr-only-focusable link-skip to-main" | |
aria-label="Skip to main content" | |
on:click|preventDefault={focusMain} | |
> | |
<span class=inner-wrapper aria-hidden=true> | |
Main content | |
</span> | |
</a> | |
<header> | |
<slot name="header"></slot> | |
</header> | |
<main id="main" tabindex="-1" bind:this="{elMain}"> | |
<slot></slot> | |
</main> | |
<footer> | |
<slot name="footer"> | |
<hr> | |
<!-- Part of a11y SPA routing: announce title (equivalent) on route change. --> | |
<p aria-live="polite" role="region"> | |
<em> | |
{$a11yTitle || pathname.replace(/^\//, '').replace(/^\w/, c => c.toUpperCase()) || 'Front'} | |
</em> | |
</p> | |
</slot> | |
<a | |
href="{pathname}#" | |
class="link-skip to-top" | |
on:click|preventDefault={focusTop} | |
> | |
{toTopLabel} | |
</a> | |
</footer> | |
</div> | |
<style> | |
[tabindex="-1"] { | |
outline: none; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment