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

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

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

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

@ -22,7 +22,9 @@ defineEmits<{
const { y } = useWindowScroll() const { y } = useWindowScroll()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
// const { hasLocalNav } = useLocalNav()
const { frontmatter } = useData() const { frontmatter } = useData()
const navbar = ref<HTMLElement>()
const classes = ref<Record<string, boolean>>({}) const classes = ref<Record<string, boolean>>({})
@ -36,7 +38,7 @@ watchPostEffect(() => {
</script> </script>
<template> <template>
<div class="VPNavBar" :class="classes"> <div class="VPNavBar" :class="classes" ref="navbar">
<div class="wrapper"> <div class="wrapper">
<div class="container"> <div class="container">
<div class="title"> <div class="title">

@ -27,14 +27,16 @@ function focusOnTargetAnchor({ target }: Event) {
</script> </script>
<template> <template>
<span ref="backToTop" tabindex="-1" /> <div>
<a <span ref="backToTop" tabindex="-1" />
href="#VPContent" <a
class="VPSkipLink visually-hidden" href="#VPContent"
@click="focusOnTargetAnchor" class="VPSkipLink visually-hidden"
> @click="focusOnTargetAnchor"
Skip to content >
</a> Skip to content
</a>
</div>
</template> </template>
<style scoped> <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 { ref, watch } from 'vue'
import { useRoute } from 'vitepress' import { useRoute } from 'vitepress'
import { inertControls } from './inert'
import { useMediaQuery } from '@vueuse/core'
export function useNav() { export function useNav() {
const is768 = useMediaQuery('(min-width: 768px)')
const isScreenOpen = ref(false) const isScreenOpen = ref(false)
function openScreen() { function openScreen() {
isScreenOpen.value = true isScreenOpen.value = true
window.addEventListener('resize', closeScreenOnTabletWindow)
} }
function closeScreen() { function closeScreen() {
isScreenOpen.value = false isScreenOpen.value = false
window.removeEventListener('resize', closeScreenOnTabletWindow)
} }
function toggleScreen() { function toggleScreen() {
isScreenOpen.value ? closeScreen() : openScreen() isScreenOpen.value ? closeScreen() : openScreen()
} }
/** watch(is768, (mq) => {
* Close screen when the user resizes the window wider than tablet size. if (mq) {
*/ isScreenOpen.value = false
function closeScreenOnTabletWindow() { }
window.outerWidth >= 768 && closeScreen() })
}
watch(
() => [isScreenOpen.value, is768.value],
([screenOpen, mq]) => {
if (mq) {
inertControls.isScreenOpen = false
} else {
inertControls.isScreenOpen = screenOpen
}
},
{ immediate: true }
)
const route = useRoute() const route = useRoute()
watch(() => route.path, closeScreen) watch(() => route.path, closeScreen)

@ -18,6 +18,7 @@ import {
getSidebarGroups getSidebarGroups
} from '../support/sidebar' } from '../support/sidebar'
import { useData } from './data' import { useData } from './data'
import { inertControls } from './inert'
export interface SidebarControl { export interface SidebarControl {
collapsed: Ref<boolean> collapsed: Ref<boolean>
@ -72,6 +73,20 @@ export function useSidebar() {
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value) 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(() => { const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [] 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: [ include: [
'vue', 'vue',
'vitepress > @vue/devtools-api', 'vitepress > @vue/devtools-api',
'vitepress > @vueuse/core' 'vitepress > @vueuse/core',
'vitepress > @vueuse/integrations/useFocusTrap'
], ],
exclude: ['@docsearch/js', 'vitepress'] exclude: ['@docsearch/js', 'vitepress']
}, },

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

Loading…
Cancel
Save