From 3119e16ed8df0daec0e019ae2efb2176d7e9e953 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:02:45 +0200 Subject: [PATCH] feat: prompt browser confirmation on page exit with unsaved changes (#9503) Co-authored-by: Claude Fable 5 --- .../UnsavedConfirmationModal.svelte | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/frontend/src/lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte b/frontend/src/lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte index 1305d611c1..55a78245be 100644 --- a/frontend/src/lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte +++ b/frontend/src/lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte @@ -38,6 +38,26 @@ let open = $state(false) let goingTo: URL | undefined = $state(undefined) + // Mirrors the modal condition: dirty when values differ, or when either + // value is missing (e.g. a never-saved draft). Also refreshes + // savedValue/modifiedValue for the diff drawer. + function hasUnsavedChanges(): boolean { + const state = getInitialAndModifiedValues?.() + savedValue = state?.savedValue + modifiedValue = state?.modifiedValue + + if (savedValue && modifiedValue) { + const draftOrDeployed = cleanValueProperties((savedValue.draft || savedValue) ?? {}) + const current = cleanValueProperties(modifiedValue ?? {}) + + return ( + orderedJsonStringify(replaceFalseWithUndefined(draftOrDeployed)) !== + orderedJsonStringify(replaceFalseWithUndefined(current)) + ) + } + return true + } + beforeNavigate(async (newNavigationState) => { if ( !bypassBeforeNavigate && @@ -49,38 +69,32 @@ ) { goingTo = newNavigationState.to.url - const state = getInitialAndModifiedValues?.() - savedValue = state?.savedValue - modifiedValue = state?.modifiedValue - - async function openModal() { + if (hasUnsavedChanges()) { newNavigationState.cancel() open = true - } - if (savedValue && modifiedValue) { - const draftOrDeployed = cleanValueProperties((savedValue.draft || savedValue) ?? {}) - const current = cleanValueProperties(modifiedValue ?? {}) - - if ( - orderedJsonStringify(replaceFalseWithUndefined(draftOrDeployed)) === - orderedJsonStringify(replaceFalseWithUndefined(current)) - ) { - if (!tabMode) { - bypassBeforeNavigate = true - } - additionalExitAction?.() - } else { - await openModal() - } } else { - await openModal() + if (!tabMode) { + bypassBeforeNavigate = true + } + additionalExitAction?.() } } else if (bypassBeforeNavigate) { bypassBeforeNavigate = false } }) + + function onBeforeUnload(event: BeforeUnloadEvent) { + if (!bypassBeforeNavigate && getInitialAndModifiedValues && hasUnsavedChanges()) { + // Triggers the browser's native "leave site?" confirmation + event.preventDefault() + // Required by some browsers (legacy mechanism) + event.returnValue = true + } + } + + {#if open}