Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 36 additions & 24 deletions src/client/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface Router {
export const RouterSymbol: InjectionKey<Router> = Symbol()

// we are just using URL to parse the pathname and hash - the base doesn't
// matter and is only passed to support same-host hrefs.
// matter and is only passed to support same-host hrefs
const fakeHost = 'http://a.com'

const getDefaultRoute = (): Route => ({
Expand Down Expand Up @@ -261,35 +261,47 @@ 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
const tabindex = target.getAttribute('tabindex')
target.setAttribute('tabindex', '-1')
target.addEventListener(
'blur',
() => {
if (tabindex == null) target.removeAttribute('tabindex')
else target.setAttribute('tabindex', 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 {
Expand All @@ -313,7 +325,7 @@ function shouldHotReload(payload: PageDataPayload): boolean {
function normalizeHref(href: string): string {
const url = new URL(href, fakeHost)
url.pathname = url.pathname.replace(/(^|\/)index(\.html)?$/, '$1')
// ensure correct deep link so page refresh lands on correct files.
// ensure correct deep link so page refresh lands on correct files
if (siteDataRef.value.cleanUrls) {
url.pathname = url.pathname.replace(/\.html$/, '')
} else if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
Expand Down
8 changes: 1 addition & 7 deletions src/client/theme-default/components/VPDocOutlineItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
25 changes: 2 additions & 23 deletions src/client/theme-default/components/VPSkipLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading