147 lines
2.9 KiB
Vue
147 lines
2.9 KiB
Vue
<template>
|
|
<teleport to="body">
|
|
<transition name="fade">
|
|
<div
|
|
v-if="show"
|
|
class="full-screen-modal-overlay"
|
|
@click.self="close"
|
|
>
|
|
<transition name="scale">
|
|
<div
|
|
v-if="show"
|
|
ref="modalRef"
|
|
class="full-screen-modal-content"
|
|
tabindex="-1"
|
|
>
|
|
<div class="full-screen-modal-body">
|
|
<slot />
|
|
<span @click="close" class="dashicons dashicons-dismiss full-screen-modal-close"></span>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</transition>
|
|
</teleport>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
|
|
|
const props = defineProps({
|
|
show: Boolean
|
|
})
|
|
const emit = defineEmits(['close'])
|
|
|
|
const modalRef = ref(null)
|
|
|
|
function close() {
|
|
emit('close')
|
|
}
|
|
|
|
// ESC-Key & Focus-Trap
|
|
function handleKeyDown(e) {
|
|
if (e.key === 'Escape') {
|
|
close()
|
|
}
|
|
if (e.key === 'Tab' && modalRef.value) {
|
|
const focusable = modalRef.value.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
)
|
|
if (focusable.length === 0) return
|
|
|
|
const first = focusable[0]
|
|
const last = focusable[focusable.length - 1]
|
|
|
|
if (e.shiftKey && document.activeElement === first) {
|
|
e.preventDefault()
|
|
last.focus()
|
|
} else if (!e.shiftKey && document.activeElement === last) {
|
|
e.preventDefault()
|
|
first.focus()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Body-Scroll sperren
|
|
watch(
|
|
() => props.show,
|
|
async (newVal) => {
|
|
if (newVal) {
|
|
document.body.style.overflow = 'hidden'
|
|
await nextTick()
|
|
modalRef.value?.focus()
|
|
} else {
|
|
document.body.style.overflow = ''
|
|
}
|
|
}
|
|
)
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('keydown', handleKeyDown)
|
|
})
|
|
onUnmounted(() => {
|
|
window.removeEventListener('keydown', handleKeyDown)
|
|
document.body.style.overflow = ''
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.full-screen-modal-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 9999;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.full-screen-modal-content {
|
|
background: white;
|
|
border-radius: 12px;
|
|
position: absolute;
|
|
top: 30px;
|
|
bottom: 30px;
|
|
left: 30px;
|
|
right: 30px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
outline: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.full-screen-modal-body {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 20px;
|
|
position: relative;
|
|
}
|
|
|
|
.full-screen-modal-close {
|
|
position: absolute;
|
|
top: 15px;
|
|
right: 15px;
|
|
cursor: pointer;
|
|
font-size: 22px;
|
|
}
|
|
|
|
/* Animation */
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
.scale-enter-active,
|
|
.scale-leave-active {
|
|
transition: transform 0.25s ease, opacity 0.25s ease;
|
|
}
|
|
.scale-enter-from,
|
|
.scale-leave-to {
|
|
transform: scale(0.98);
|
|
opacity: 0;
|
|
}
|
|
</style>
|