useModal Composable
Reusable modal management with keyboard navigation and backdrop click handling
Code
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
import { useFocusTrap } from './useFocusTrap'; // Reuse focus trap composable
export function useModal(
modalRef: Ref<HTMLElement | null>,
backdropRef: Ref<HTMLElement | null> = ref(null)
) {
const isOpen = ref(false);
const { activate: activateFocusTrap, deactivate: deactivateFocusTrap } = useFocusTrap(modalRef, isOpen);
// Open modal
const open = () => {
__TOKEN_36__ (isOpen.value) return;
isOpen.value = true;
document.body.style.overflow = 'hidden'; // Prevent body scroll
setTimeout(() => activateFocusTrap(), 0); // Wait for modal to render
};
// Close modal
const close = () => {
__TOKEN_39__ (!isOpen.value) return;
isOpen.value = false;
document.body.style.overflow = ''; // Restore body scroll
deactivateFocusTrap();
};
// Toggle modal
const toggle = () => {
isOpen.value ? close() : open();
};
// Handle escape key
const handleEscapeKey = (e: KeyboardEvent) => {
__TOKEN_43__ (e.key === 'Escape' && isOpen.value) {
close();
}
};
// Handle backdrop click
const handleBackdropClick = (e: MouseEvent) => {
__TOKEN_45__ (backdropRef.value && e.target === backdropRef.value && isOpen.value) {
close();
}
};
onMounted(() => {
document.addEventListener('keydown', handleEscapeKey);
__TOKEN_46__ (backdropRef.value) {
backdropRef.value.addEventListener('click', handleBackdropClick);
}
});
onUnmounted(() => {
document.removeEventListener('keydown', handleEscapeKey);
__TOKEN_47__ (backdropRef.value) {
backdropRef.value.removeEventListener('click', handleBackdropClick);
}
// Cleanup
__TOKEN_48__ (isOpen.value) {
document.body.style.overflow = '';
}
});
return {
isOpen,
open,
close,
toggle
};
}
// Usage example
// const modalRef = ref<HTMLElement | null>(null);
// const backdropRef = ref<HTMLElement | null>(null);
// const { isOpen, open, close } = useModal(modalRef, backdropRef);
// open(); // Open modal
// close(); // Close modal