fix(a11y): improve focus handling in router

pull/4943/head
Divyansh Singh 1 week ago
parent 850c429f14
commit b3f6772df7

@ -261,35 +261,45 @@ export function scrollTo(hash: string, smooth = false, scrollPosition = 0) {
return
}
let target: Element | null = null
let target: HTMLElement | null = null
try {
target = document.getElementById(decodeURIComponent(hash).slice(1))
} catch (e) {
console.warn(e)
}
if (target) {
const targetPadding = parseInt(
window.getComputedStyle(target).paddingTop,
10
if (!target) return
const targetPadding = parseInt(window.getComputedStyle(target).paddingTop, 10)
const targetTop =
window.scrollY +
target.getBoundingClientRect().top -
getScrollOffset() +
targetPadding
const scrollToTarget = () => {
// only smooth scroll if distance is smaller than screen height.
if (!smooth || Math.abs(targetTop - window.scrollY) > window.innerHeight)
window.scrollTo(0, targetTop)
else window.scrollTo({ left: 0, top: targetTop, behavior: 'smooth' })
// focus the target element for better accessibility
target.focus({ preventScroll: true })
if (document.activeElement === target) return
// target is not focusable, make it temporarily focusable
target.setAttribute('tabindex', '-1')
target.addEventListener(
'blur',
() => {
target.removeAttribute('tabindex')
},
{ once: true }
)
const targetTop =
window.scrollY +
target.getBoundingClientRect().top -
getScrollOffset() +
targetPadding
function scrollToTarget() {
// only smooth scroll if distance is smaller than screen height.
if (!smooth || Math.abs(targetTop - window.scrollY) > window.innerHeight)
window.scrollTo(0, targetTop)
else window.scrollTo({ left: 0, top: targetTop, behavior: 'smooth' })
}
requestAnimationFrame(scrollToTarget)
target.focus({ preventScroll: true })
}
requestAnimationFrame(scrollToTarget)
}
function handleHMR(route: Route): void {

@ -5,18 +5,12 @@ defineProps<{
headers: DefaultTheme.OutlineItem[]
root?: boolean
}>()
function onClick({ target: el }: Event) {
const id = (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.getElementById(decodeURIComponent(id))
heading?.focus({ preventScroll: true })
}
</script>
<template>
<ul class="VPDocOutlineItem" :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers">
<a class="outline-link" :href="link" @click="onClick" :title>
<a class="outline-link" :href="link" :title>
{{ title }}
</a>
<template v-if="children?.length">

@ -8,39 +8,18 @@ const route = useRoute()
const backToTop = ref()
watch(() => route.path, () => backToTop.value.focus())
function focusOnTargetAnchor({ target }: Event) {
const el = document.getElementById(
decodeURIComponent((target as HTMLAnchorElement).hash).slice(1)
)
if (el) {
const removeTabIndex = () => {
el.removeAttribute('tabindex')
el.removeEventListener('blur', removeTabIndex)
}
el.setAttribute('tabindex', '-1')
el.addEventListener('blur', removeTabIndex)
el.focus()
window.scrollTo(0, 0)
}
}
</script>
<template>
<span ref="backToTop" tabindex="-1" />
<a
href="#VPContent"
class="VPSkipLink visually-hidden"
@click="focusOnTargetAnchor"
>
<a href="#VPContent" class="VPSkipLink visually-hidden">
{{ theme.skipToContentLabel || 'Skip to content' }}
</a>
</template>
<style scoped>
.VPSkipLink {
position: fixed;
top: 8px;
left: 8px;
padding: 8px 16px;

Loading…
Cancel
Save