feat: add modal focus trap
continuous-integration/drone/pr Build is failing Details

This commit is contained in:
Dominik Pschenitschni 2022-11-22 20:56:56 +01:00
parent e232c0c20b
commit 73390a28ad
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
3 changed files with 104 additions and 2 deletions

View File

@ -31,6 +31,7 @@
"@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.0",
"@vueuse/core": "9.5.0",
"@vueuse/integrations": "^9.6.0",
"@vueuse/router": "9.5.0",
"axios": "0.27.2",
"blurhash": "2.0.4",
@ -45,6 +46,7 @@
"flatpickr": "4.6.13",
"flexsearch": "0.7.21",
"floating-vue": "2.0.0-beta.20",
"focus-trap": "^7.1.0",
"highlight.js": "11.6.0",
"is-touch-device": "1.0.1",
"lodash.clonedeep": "4.5.0",

View File

@ -33,6 +33,7 @@ specifiers:
'@vue/test-utils': 2.2.4
'@vue/tsconfig': 0.1.3
'@vueuse/core': 9.5.0
'@vueuse/integrations': ^9.6.0
'@vueuse/router': 9.5.0
autoprefixer: 10.4.13
axios: 0.27.2
@ -56,6 +57,7 @@ specifiers:
flatpickr: 4.6.13
flexsearch: 0.7.21
floating-vue: 2.0.0-beta.20
focus-trap: ^7.1.0
happy-dom: 7.7.0
highlight.js: 11.6.0
is-touch-device: 1.0.1
@ -104,6 +106,7 @@ dependencies:
'@types/lodash.clonedeep': 4.5.7
'@types/sortablejs': 1.15.0
'@vueuse/core': 9.5.0_vue@3.2.45
'@vueuse/integrations': 9.6.0_v6so3rexzmt44vm4rsvyznwkka
'@vueuse/router': 9.5.0_xsxatmlnmmg5bcuv3xdnj6fj7y
axios: 0.27.2
blurhash: 2.0.4
@ -118,6 +121,7 @@ dependencies:
flatpickr: 4.6.13
flexsearch: 0.7.21
floating-vue: 2.0.0-beta.20_vue@3.2.45
focus-trap: 7.1.0
highlight.js: 11.6.0
is-touch-device: 1.0.1
lodash.clonedeep: 4.5.0
@ -3661,10 +3665,74 @@ packages:
- vue
dev: false
/@vueuse/core/9.6.0_vue@3.2.45:
resolution: {integrity: sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==}
dependencies:
'@types/web-bluetooth': 0.0.16
'@vueuse/metadata': 9.6.0
'@vueuse/shared': 9.6.0_vue@3.2.45
vue-demi: 0.12.1_vue@3.2.45
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/integrations/9.6.0_v6so3rexzmt44vm4rsvyznwkka:
resolution: {integrity: sha512-+rs2OWY/3spxoAGQMnlHQpxf8ErAYf4D1bT0aXaPnxphmtYgexm6KIjTFpBbcQnHwVi1g2ET1SJoQL16yDrgWA==}
peerDependencies:
async-validator: '*'
axios: '*'
change-case: '*'
drauu: '*'
focus-trap: '*'
fuse.js: '*'
idb-keyval: '*'
jwt-decode: '*'
nprogress: '*'
qrcode: '*'
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
universal-cookie:
optional: true
dependencies:
'@vueuse/core': 9.6.0_vue@3.2.45
'@vueuse/shared': 9.6.0_vue@3.2.45
axios: 0.27.2
focus-trap: 7.1.0
vue-demi: 0.12.1_vue@3.2.45
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/metadata/9.5.0:
resolution: {integrity: sha512-4M1AyPZmIv41pym+K5+4wup3bKuYebbH8w8BROY1hmT7rIwcyS4tEL+UsGz0Hiu1FCOxcoBrwtAizc0YmBJjyQ==}
dev: false
/@vueuse/metadata/9.6.0:
resolution: {integrity: sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w==}
dev: false
/@vueuse/router/9.5.0_xsxatmlnmmg5bcuv3xdnj6fj7y:
resolution: {integrity: sha512-OtgV3iqC3CqbnXVy98Lv3PMf5exr/jntjxRDnAetsV+VAG9ipuzna3JW95KntW1m6I5EEsuQYdU3MXtCvvNlKQ==}
peerDependencies:
@ -3687,6 +3755,15 @@ packages:
- vue
dev: false
/@vueuse/shared/9.6.0_vue@3.2.45:
resolution: {integrity: sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==}
dependencies:
vue-demi: 0.12.1_vue@3.2.45
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/abbrev/1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: true
@ -7008,6 +7085,12 @@ packages:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
dev: true
/focus-trap/7.1.0:
resolution: {integrity: sha512-CuJvwUBfJCWcU6fc4xr3UwMF5vWnox4isXAixCwrPzCsPKOQjP9T+nTlYT2t+vOmQL8MOQ16eim99XhjQHAuiQ==}
dependencies:
tabbable: 6.0.1
dev: false
/folder-walker/3.2.0:
resolution: {integrity: sha512-VjAQdSLsl6AkpZNyrQJfO7BXLo4chnStqb055bumZMbRUPpVuPN3a4ktsnRCmrFZjtMlYLkyXiR5rAs4WOpC4Q==}
dependencies:
@ -12255,6 +12338,10 @@ packages:
resolution: {integrity: sha512-P3cgh2bpaPvAO2NE3uRp/n6hmk4xPX4DQf+UzTlCAycssKdqhp6hjw+ENWe+aUS7TogKRFtptMosTSFeC6R55g==}
dev: true
/tabbable/6.0.1:
resolution: {integrity: sha512-SYJSIgeyXW7EuX1ytdneO5e8jip42oHWg9xl/o3oTYhmXusZVgiA+VlPvjIN+kHii9v90AmzTZEBcsEvuAY+TA==}
dev: false
/tabtab/3.0.2:
resolution: {integrity: sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==}
dependencies:

View File

@ -9,6 +9,7 @@
{ 'has-overflow': overflow },
variant,
]"
ref="modal"
v-bind="$attrs"
>
<div
@ -69,8 +70,10 @@ export default {
</script>
<script lang="ts" setup>
import {watchEffect} from 'vue'
import {ref, watchEffect} from 'vue'
import {useScrollLock} from '@vueuse/core'
import {useFocusTrap} from '@vueuse/integrations/useFocusTrap'
import type {RouteLocationRaw} from 'vue-router'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import BaseButton from '@/components/base/BaseButton.vue'
@ -81,6 +84,7 @@ const props = withDefaults(defineProps<{
wide?: boolean,
transitionName?: 'modal' | 'fade',
variant?: 'default' | 'hint-modal' | 'scrolling',
closeRoute?: RouteLocationRaw,
}>(), {
enabled: true,
transitionName: 'modal',
@ -90,11 +94,20 @@ const props = withDefaults(defineProps<{
const emit = defineEmits(['close', 'submit'])
const scrollLock = useScrollLock(document.body)
watchEffect(() => {
scrollLock.value = props.enabled
})
const modal = ref(null)
const {activate, deactivate} = useFocusTrap(modal)
watchEffect(() => {
if (props.enabled) {
activate()
} else {
deactivate()
}
})
function onClose() {
emit('close')
}