From 2f482afaabdb4206b87e2453d0099257693c4653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Mon, 8 May 2023 19:22:47 +0200 Subject: [PATCH] feat(theme): add focus trap to local search dialog (#2324) Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> --- package.json | 2 + pnpm-lock.yaml | 66 +++++++++++++++++++ .../components/VPLocalSearchBox.vue | 22 +++++-- 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9eba1f2e..cf967e5d 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,9 @@ "@vitejs/plugin-vue": "^4.2.1", "@vue/devtools-api": "^6.5.0", "@vueuse/core": "^10.1.0", + "@vueuse/integrations": "^10.1.0", "body-scroll-lock": "4.0.0-beta.0", + "focus-trap": "^7.4.0", "mark.js": "8.11.1", "minisearch": "^6.0.1", "shiki": "^0.14.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75561cd3..98a9864f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,9 +19,15 @@ importers: '@vueuse/core': specifier: ^10.1.0 version: 10.1.0(vue@3.2.47) + '@vueuse/integrations': + specifier: ^10.1.0 + version: 10.1.0(focus-trap@7.4.0)(vue@3.2.47) body-scroll-lock: specifier: 4.0.0-beta.0 version: 4.0.0-beta.0 + focus-trap: + specifier: ^7.4.0 + version: 7.4.0 mark.js: specifier: 8.11.1 version: 8.11.1 @@ -1276,6 +1282,56 @@ packages: - vue dev: false + /@vueuse/integrations@10.1.0(focus-trap@7.4.0)(vue@3.2.47): + resolution: {integrity: sha512-eSGdRYSFIspSYQIC0hzivi95Jv/BEH9Z47BrpNNKZi6k/LcHDAANmiNeiPN1BxlmO2PovG8dN9Et/AZC4WZ2YQ==} + peerDependencies: + async-validator: '*' + axios: '*' + change-case: '*' + drauu: '*' + focus-trap: '*' + fuse.js: '*' + idb-keyval: '*' + jwt-decode: '*' + nprogress: '*' + qrcode: '*' + sortablejs: '*' + universal-cookie: '*' + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + dependencies: + '@vueuse/core': 10.1.0(vue@3.2.47) + '@vueuse/shared': 10.1.0(vue@3.2.47) + focus-trap: 7.4.0 + vue-demi: 0.14.0(vue@3.2.47) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + /@vueuse/metadata@10.1.0: resolution: {integrity: sha512-cM28HjDEw5FIrPE9rgSPFZvQ0ZYnOLAOr8hl1XM6tFl80U3WAR5ROdnAqiYybniwP5gt9MKKAJAqd/ab2aHkqg==} dev: false @@ -2249,6 +2305,12 @@ packages: path-exists: 5.0.0 dev: true + /focus-trap@7.4.0: + resolution: {integrity: sha512-yI7FwUqU4TVb+7t6PaQ3spT/42r/KLEi8mtdGoQo2li/kFzmu9URmalTvw7xCCJtSOyhBxscvEAmvjeN9iHARg==} + dependencies: + tabbable: 6.1.1 + dev: false + /follow-redirects@1.15.2(debug@4.3.4): resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -4156,6 +4218,10 @@ packages: engines: {node: '>= 0.4'} dev: true + /tabbable@6.1.1: + resolution: {integrity: sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==} + dev: false + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} diff --git a/src/client/theme-default/components/VPLocalSearchBox.vue b/src/client/theme-default/components/VPLocalSearchBox.vue index 8f630248..0230d141 100644 --- a/src/client/theme-default/components/VPLocalSearchBox.vue +++ b/src/client/theme-default/components/VPLocalSearchBox.vue @@ -9,6 +9,7 @@ import { useScrollLock, useSessionStorage } from '@vueuse/core' +import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' import Mark from 'mark.js/src/vanilla.js' import MiniSearch, { type SearchResult } from 'minisearch' import { useRouter } from 'vitepress' @@ -26,11 +27,11 @@ import { type Ref } from 'vue' import type { ModalTranslations } from '../../../../types/local-search' +import { dataSymbol } from '../../app/data' import { pathToFile } from '../../app/utils' import { slash } from '../../shared' import { useData } from '../composables/data' import { createTranslate } from '../support/translation' -import { dataSymbol } from '../../app/data' defineProps<{ placeholder: string @@ -64,6 +65,12 @@ 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 () => markRaw( @@ -84,14 +91,14 @@ const searchIndex = computedAsync(async () => const disableQueryPersistence = computed(() => { return ( - theme.value.search?.provider === 'local' && - theme.value.search.options?.disableQueryPersistence === true + theme.value.search?.provider === 'local' && + theme.value.search.options?.disableQueryPersistence === true ) }) const filterText = disableQueryPersistence.value - ? ref('') - : useSessionStorage('vitepress:local-search-filter', '') + ? ref('') + : useSessionStorage('vitepress:local-search-filter', '') const showDetailedList = useLocalStorage( 'vitepress:local-search-detailed-list', @@ -334,6 +341,7 @@ onMounted(() => { body.value = document.body nextTick(() => { isLocked.value = true + nextTick().then(() => activate()) }) }) @@ -474,6 +482,7 @@ function formMarkRegex(terms: Set) { }" :aria-label="[...p.titles, p.title].join(' > ')" @mouseenter="!disableMouseOver && (selectedIndex = index)" + @focusin="selectedIndex = index" @click="$emit('close')" >
@@ -498,7 +507,7 @@ function formMarkRegex(terms: Set) {
-
+
@@ -727,6 +736,7 @@ function formMarkRegex(terms: Set) { transition: none; line-height: 1rem; border: solid 2px var(--vp-local-search-result-border); + outline: none; } .result > div {