pull/3392/merge
Joaquín Sánchez 1 year ago committed by GitHub
commit 2cae7c9d63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,6 +9,7 @@ import VPNav from './components/VPNav.vue'
import VPSidebar from './components/VPSidebar.vue'
import VPSkipLink from './components/VPSkipLink.vue'
import { useData } from './composables/data'
import { useInert } from './composables/inert'
import { useCloseSidebarOnEscape, useSidebar } from './composables/sidebar'
const {
@ -24,6 +25,8 @@ useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
const { frontmatter } = useData()
const inert = useInert()
const slots = useSlots()
const heroImageSlotExists = computed(() => !!slots['home-hero-image'])
@ -33,9 +36,9 @@ provide('hero-image-slot-exists', heroImageSlotExists)
<template>
<div v-if="frontmatter.layout !== false" class="Layout" :class="frontmatter.pageClass" >
<slot name="layout-top" />
<VPSkipLink />
<VPSkipLink :inert="inert.skipLink" />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
<VPNav>
<VPNav :inert="inert.nav">
<template #nav-bar-title-before><slot name="nav-bar-title-before" /></template>
<template #nav-bar-title-after><slot name="nav-bar-title-after" /></template>
<template #nav-bar-content-before><slot name="nav-bar-content-before" /></template>
@ -43,14 +46,14 @@ provide('hero-image-slot-exists', heroImageSlotExists)
<template #nav-screen-content-before><slot name="nav-screen-content-before" /></template>
<template #nav-screen-content-after><slot name="nav-screen-content-after" /></template>
</VPNav>
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
<VPLocalNav :inert="inert.localNav" :open="isSidebarOpen" @open-menu="openSidebar" />
<VPSidebar :open="isSidebarOpen">
<VPSidebar :inert="inert.sidebar" :open="isSidebarOpen">
<template #sidebar-nav-before><slot name="sidebar-nav-before" /></template>
<template #sidebar-nav-after><slot name="sidebar-nav-after" /></template>
</VPSidebar>
<VPContent>
<VPContent :inert="inert.content">
<template #page-top><slot name="page-top" /></template>
<template #page-bottom><slot name="page-bottom" /></template>
@ -79,7 +82,7 @@ provide('hero-image-slot-exists', heroImageSlotExists)
<template #aside-ads-after><slot name="aside-ads-after" /></template>
</VPContent>
<VPFooter />
<VPFooter :inert="inert.footer" />
<slot name="layout-bottom" />
</div>
<Content v-else />

@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, watchEffect, onMounted } from 'vue'
import { computed } from 'vue'
import { useData } from '../composables/data'
import { clientComputed } from '../support/reactivity'
const { theme, page, frontmatter, lang } = useData()
@ -8,21 +9,13 @@ const date = computed(
() => new Date(frontmatter.value.lastUpdated ?? page.value.lastUpdated)
)
const isoDatetime = computed(() => date.value.toISOString())
const datetime = ref('')
// set time on mounted hook to avoid hydration mismatch due to
// potential differences in timezones of the server and clients
onMounted(() => {
watchEffect(() => {
datetime.value = new Intl.DateTimeFormat(
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? {
dateStyle: 'short',
timeStyle: 'short'
}
).format(date.value)
})
})
const datetime = clientComputed(() => {
return new Intl.DateTimeFormat(
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? { dateStyle: 'short', timeStyle: 'short' }
).format(date.value)
}, '')
</script>
<template>

@ -1,5 +1,5 @@
<script setup lang="ts">
import { onClickOutside, onKeyStroke } from '@vueuse/core'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { onContentUpdated } from 'vitepress'
import { nextTick, ref } from 'vue'
import { useData } from '../composables/data'
@ -11,26 +11,31 @@ const props = defineProps<{
navHeight: number
}>()
const { theme } = useData()
const open = ref(false)
const vh = ref(0)
const main = ref<HTMLDivElement>()
const items = ref<HTMLDivElement>()
onClickOutside(main, () => {
open.value = false
})
const open = ref(false)
onKeyStroke('Escape', () => {
open.value = false
const { theme } = useData()
const { activate, deactivate } = useFocusTrap(items, {
immediate: true,
clickOutsideDeactivates: true,
onDeactivate: () => {
open.value = false
}
})
onContentUpdated(() => {
open.value = false
})
const vh = ref(0)
onContentUpdated(deactivate)
function toggle() {
open.value = !open.value
if (open.value) {
deactivate()
} else {
open.value = true
nextTick(() => activate())
}
vh.value = window.innerHeight + Math.min(window.scrollY - props.navHeight, 0)
}
@ -40,14 +45,12 @@ function onItemClick(e: Event) {
if (items.value) {
items.value.style.transition = 'none'
}
nextTick(() => {
open.value = false
})
nextTick(() => deactivate())
}
}
function scrollToTop() {
open.value = false
deactivate()
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
}
</script>
@ -158,12 +161,12 @@ function scrollToTop() {
.header {
background-color: var(--vp-c-bg-soft);
padding: 2px 16px;
}
.top-link {
display: block;
padding: 0 16px;
line-height: 48px;
line-height: 44px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);

@ -62,9 +62,7 @@ interface Result {
const vitePressData = useData()
const { activate } = useFocusTrap(el, {
immediate: true,
allowOutsideClick: true,
clickOutsideDeactivates: true,
escapeDeactivates: true
})
const { localeIndex, theme } = vitePressData
const searchIndex = computedAsync(async () =>

@ -22,7 +22,9 @@ defineEmits<{
const { y } = useWindowScroll()
const { hasSidebar } = useSidebar()
// const { hasLocalNav } = useLocalNav()
const { frontmatter } = useData()
const navbar = ref<HTMLElement>()
const classes = ref<Record<string, boolean>>({})
@ -36,7 +38,7 @@ watchPostEffect(() => {
</script>
<template>
<div class="VPNavBar" :class="classes">
<div class="VPNavBar" :class="classes" ref="navbar">
<div class="wrapper">
<div class="container">
<div class="title">
@ -255,7 +257,7 @@ watchPostEffect(() => {
background-color: var(--vp-c-gutter);
}
@media (min-width: 960px) {
@media (min-width: 960px) {
.VPNavBar:not(.home.top) .divider-line {
background-color: var(--vp-c-gutter);
}

@ -27,14 +27,16 @@ function focusOnTargetAnchor({ target }: Event) {
</script>
<template>
<span ref="backToTop" tabindex="-1" />
<a
href="#VPContent"
class="VPSkipLink visually-hidden"
@click="focusOnTargetAnchor"
>
Skip to content
</a>
<div>
<span ref="backToTop" tabindex="-1" />
<a
href="#VPContent"
class="VPSkipLink visually-hidden"
@click="focusOnTargetAnchor"
>
Skip to content
</a>
</div>
</template>
<style scoped>

@ -0,0 +1,19 @@
import { reactive } from 'vue'
import { clientComputed } from '../support/reactivity'
export const inertControls = reactive({
isScreenOpen: false,
isSidebarOpen: false,
isSidebarVisible: false
})
export function useInert() {
return clientComputed(() => ({
skipLink: inertControls.isSidebarOpen || inertControls.isScreenOpen,
nav: inertControls.isSidebarOpen,
localNav: inertControls.isSidebarOpen || inertControls.isScreenOpen,
sidebar: !inertControls.isSidebarVisible || inertControls.isScreenOpen,
content: inertControls.isSidebarOpen || inertControls.isScreenOpen,
footer: inertControls.isSidebarOpen || inertControls.isScreenOpen
}))
}

@ -1,29 +1,41 @@
import { ref, watch } from 'vue'
import { useRoute } from 'vitepress'
import { inertControls } from './inert'
import { useMediaQuery } from '@vueuse/core'
export function useNav() {
const is768 = useMediaQuery('(min-width: 768px)')
const isScreenOpen = ref(false)
function openScreen() {
isScreenOpen.value = true
window.addEventListener('resize', closeScreenOnTabletWindow)
}
function closeScreen() {
isScreenOpen.value = false
window.removeEventListener('resize', closeScreenOnTabletWindow)
}
function toggleScreen() {
isScreenOpen.value ? closeScreen() : openScreen()
}
/**
* Close screen when the user resizes the window wider than tablet size.
*/
function closeScreenOnTabletWindow() {
window.outerWidth >= 768 && closeScreen()
}
watch(is768, (mq) => {
if (mq) {
isScreenOpen.value = false
}
})
watch(
() => [isScreenOpen.value, is768.value],
([screenOpen, mq]) => {
if (mq) {
inertControls.isScreenOpen = false
} else {
inertControls.isScreenOpen = screenOpen
}
},
{ immediate: true }
)
const route = useRoute()
watch(() => route.path, closeScreen)

@ -18,6 +18,7 @@ import {
getSidebarGroups
} from '../support/sidebar'
import { useData } from './data'
import { inertControls } from './inert'
export interface SidebarControl {
collapsed: Ref<boolean>
@ -72,6 +73,20 @@ export function useSidebar() {
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
watch(
() => [isSidebarEnabled.value, isOpen.value],
([sidebarEnabled, o]) => {
if (o) {
inertControls.isSidebarOpen = true
inertControls.isSidebarVisible = true
} else {
inertControls.isSidebarOpen = false
inertControls.isSidebarVisible = sidebarEnabled
}
},
{ immediate: true }
)
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
})

@ -0,0 +1,24 @@
import {
onMounted,
shallowReadonly,
shallowRef,
toValue,
watchEffect,
type ShallowRef
} from 'vue'
export function clientComputed<T extends {}>(
fn: () => T,
defaultValue: any = {},
options?: { flush?: 'pre' | 'post' | 'sync' }
): Readonly<ShallowRef<T>> {
const data = shallowRef<T>(toValue(defaultValue))
onMounted(() => {
watchEffect(() => {
data.value = toValue(fn)
}, options)
})
return shallowReadonly(data)
}

@ -159,7 +159,8 @@ export async function createVitePressPlugin(
include: [
'vue',
'vitepress > @vue/devtools-api',
'vitepress > @vueuse/core'
'vitepress > @vueuse/core',
'vitepress > @vueuse/integrations/useFocusTrap'
],
exclude: ['@docsearch/js', 'vitepress']
},

@ -163,7 +163,6 @@ export async function localSearchPlugin(
config: () => ({
optimizeDeps: {
include: [
'vitepress > @vueuse/integrations/useFocusTrap',
'vitepress > mark.js/src/vanilla.js',
'vitepress > minisearch'
]

Loading…
Cancel
Save