import { onBeforeRouteLeave, onBeforeRouteUpdate, onBeforeUnmount } from '#imports'; import { computed, toValue, type MaybeRefOrGetter, type Ref } from 'vue'; import { useDialog } from '~/composables/useDialog'; type DirtyCloseGuardOptions = { dirty: MaybeRefOrGetter; title?: string; message?: string; confirmText?: string; cancelText?: string; confirmColor?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'; onDiscard?: () => void | Promise; }; export const useDirtyCloseGuard = (open: Ref, options: DirtyCloseGuardOptions) => { const dialog = useDialog(); let pendingCloseRequest: Promise | null = null; const isDirty = computed(() => Boolean(toValue(options.dirty))); const shouldGuard = computed(() => Boolean(open.value) && true === isDirty.value); const confirmClose = async (): Promise => { if (false === isDirty.value) { open.value = false; return true; } const { status } = await dialog.confirmDialog({ title: options.title ?? 'Discard changes?', message: options.message ?? 'You have unsaved changes. Do you want to discard them?', confirmText: options.confirmText ?? 'Discard changes', cancelText: options.cancelText ?? 'Keep editing', confirmColor: options.confirmColor ?? 'warning', }); if (true !== status) { return false; } await options.onDiscard?.(); open.value = false; return true; }; const requestClose = async (): Promise => { if (pendingCloseRequest) { return pendingCloseRequest; } pendingCloseRequest = confirmClose().finally(() => { pendingCloseRequest = null; }); return pendingCloseRequest; }; const handleOpenChange = async (value: boolean): Promise => { if (true === value) { open.value = true; return; } await requestClose(); }; onBeforeRouteLeave(async () => { if (false === shouldGuard.value) { return true; } return await requestClose(); }); onBeforeRouteUpdate(async () => { if (false === shouldGuard.value) { return true; } return await requestClose(); }); if ('undefined' !== typeof window) { const handleBeforeUnload = (event: BeforeUnloadEvent): void => { if (false === shouldGuard.value) { return; } event.preventDefault(); event.returnValue = ''; }; window.addEventListener('beforeunload', handleBeforeUnload); onBeforeUnmount(() => { window.removeEventListener('beforeunload', handleBeforeUnload); }); } return { isDirty, requestClose, handleOpenChange, }; };