|
|
@ -54,7 +54,7 @@ export interface Router {
|
|
|
|
export const RouterSymbol: InjectionKey<Router> = Symbol()
|
|
|
|
export const RouterSymbol: InjectionKey<Router> = Symbol()
|
|
|
|
|
|
|
|
|
|
|
|
// we are just using URL to parse the pathname and hash - the base doesn't
|
|
|
|
// 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 fakeHost = 'http://a.com'
|
|
|
|
|
|
|
|
|
|
|
|
const getDefaultRoute = (): Route => ({
|
|
|
|
const getDefaultRoute = (): Route => ({
|
|
|
@ -261,35 +261,57 @@ export function scrollTo(hash: string, smooth = false, scrollPosition = 0) {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let target: Element | null = null
|
|
|
|
let target: HTMLElement | null = null
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
target = document.getElementById(decodeURIComponent(hash).slice(1))
|
|
|
|
target = document.getElementById(decodeURIComponent(hash).slice(1))
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(e)
|
|
|
|
console.warn(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!target) return
|
|
|
|
if (target) {
|
|
|
|
|
|
|
|
const targetPadding = parseInt(
|
|
|
|
|
|
|
|
window.getComputedStyle(target).paddingTop,
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const targetTop =
|
|
|
|
const targetTop =
|
|
|
|
window.scrollY +
|
|
|
|
window.scrollY +
|
|
|
|
target.getBoundingClientRect().top -
|
|
|
|
target.getBoundingClientRect().top -
|
|
|
|
getScrollOffset() +
|
|
|
|
getScrollOffset() +
|
|
|
|
targetPadding
|
|
|
|
Number.parseInt(window.getComputedStyle(target).paddingTop, 10) || 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const behavior = window.matchMedia('(prefers-reduced-motion)').matches
|
|
|
|
|
|
|
|
? 'instant'
|
|
|
|
|
|
|
|
: // only smooth scroll if distance is smaller than screen height
|
|
|
|
|
|
|
|
smooth && Math.abs(targetTop - window.scrollY) <= window.innerHeight
|
|
|
|
|
|
|
|
? 'smooth'
|
|
|
|
|
|
|
|
: 'auto'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const scrollToTarget = () => {
|
|
|
|
|
|
|
|
window.scrollTo({ left: 0, top: targetTop, behavior })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// focus the target element for better accessibility
|
|
|
|
|
|
|
|
target.focus({ preventScroll: true })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// return if focus worked
|
|
|
|
|
|
|
|
if (document.activeElement === target) return
|
|
|
|
|
|
|
|
|
|
|
|
function scrollToTarget() {
|
|
|
|
// element has tabindex already, likely not focusable
|
|
|
|
// only smooth scroll if distance is smaller than screen height.
|
|
|
|
// because of some other reason, bail out
|
|
|
|
if (!smooth || Math.abs(targetTop - window.scrollY) > window.innerHeight)
|
|
|
|
if (target.hasAttribute('tabindex')) return
|
|
|
|
window.scrollTo(0, targetTop)
|
|
|
|
|
|
|
|
else window.scrollTo({ left: 0, top: targetTop, behavior: 'smooth' })
|
|
|
|
const restoreTabindex = () => {
|
|
|
|
|
|
|
|
target.removeAttribute('tabindex')
|
|
|
|
|
|
|
|
target.removeEventListener('blur', restoreTabindex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(scrollToTarget)
|
|
|
|
// temporarily make the target element focusable
|
|
|
|
|
|
|
|
target.setAttribute('tabindex', '-1')
|
|
|
|
|
|
|
|
target.addEventListener('blur', restoreTabindex)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// try to focus again
|
|
|
|
|
|
|
|
target.focus({ preventScroll: true })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// remove tabindex and event listener if focus still not worked
|
|
|
|
|
|
|
|
if (document.activeElement !== target) restoreTabindex()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(scrollToTarget)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleHMR(route: Route): void {
|
|
|
|
function handleHMR(route: Route): void {
|
|
|
@ -313,7 +335,7 @@ function shouldHotReload(payload: PageDataPayload): boolean {
|
|
|
|
function normalizeHref(href: string): string {
|
|
|
|
function normalizeHref(href: string): string {
|
|
|
|
const url = new URL(href, fakeHost)
|
|
|
|
const url = new URL(href, fakeHost)
|
|
|
|
url.pathname = url.pathname.replace(/(^|\/)index(\.html)?$/, '$1')
|
|
|
|
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) {
|
|
|
|
if (siteDataRef.value.cleanUrls) {
|
|
|
|
url.pathname = url.pathname.replace(/\.html$/, '')
|
|
|
|
url.pathname = url.pathname.replace(/\.html$/, '')
|
|
|
|
} else if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
|
|
|
|
} else if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {
|
|
|
|