fix(Modal): keep close confirmation armed after an intermediate form submit - #2779
Merged
Merged
Conversation
Form.handleSubmit called modalController.confirmClose() on every successful submit, which set closeIsConfirmed = true. That flag was only cleared once a close actually went through, so a submit that did not close the Modal left behind a permanently valid "close without asking" grant. Wizards that advance a step via SubmitButton were unprotected from then on: Escape and backdrop click closed the Modal silently while the Form was still dirty. Replace the unbounded latch with a grant that is scoped to the operation that may legitimately close the Modal: - OverlayController gains grantCloseWithoutConfirmation(), returning a disposer. setOpen() honours an active grant the same way it honours bypassConfirmation. - Form wraps the submit flow and – separately – the after-submit callback run from onAfterSuccessFeedback, releasing each grant as soon as that operation has settled (synchronously, or via .finally() for an awaited onSubmit). - closeIsConfirmed is now consumed on every close attempt instead of only on a successful close, so it cannot survive an aborted close either. bypassConfirmation and the Action closeModal behaviour are unchanged. Fixes #2775 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lisa18289
requested review from
Jan-Eimertenbrink,
ins0,
maaaathis and
mfal
as code owners
August 5, 2026 06:17
Lisa18289
marked this pull request as draft
August 5, 2026 06:17
Contributor
Coverage Report for ./packages/components/
File CoverageNo changed files found. |
Contributor
🚀 Preview DeploymentPreview environments are ready:
Images:
|
…nfirmation-bug-2798f5 # Conflicts: # apps/docs/src/content/04-components/overlays/modal/overview.mdx # packages/components/src/integrations/react-hook-form/components/Form/Form.tsx
mfal
approved these changes
Aug 7, 2026
Jan-Eimertenbrink
added a commit
that referenced
this pull request
Aug 10, 2026
Resolve the one modify/delete conflict on apps/docs/src/content/04-components/overlays/modal/overview.mdx: this branch consolidated the modal tab files into a single index.mdx, while main updated the confirmOnClose explanation (#2779). Keep the deletion of overview.mdx and fold main's revised wording — the reset-only phrasing plus the new submit-closes vs. modal-stays-open paragraph — into modal/index.mdx. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2775
Problem
Form.handleSubmitcalledmodalController.confirmClose()on every successful submit, settingcloseIsConfirmed = true. That flag was only cleared once a close actually went through, so a submit that did not close the Modal left behind a permanently valid "close without asking" grant. A wizard advancing a step viaSubmitButtonwas unprotected from then on:Escapeand backdrop click closed the Modal silently, even though theFormwas still mounted and stillisDirty.The grant was correct in intent — after saving, "discard unsaved changes?" would be nonsense — but unbounded in scope, while what it needs to cover is exactly one close: the one caused by that submit.
Solution
Scope the grant to the operation that may legitimately close the Modal, as sketched under "Proposed direction" in the issue (the explicit-lifetime alternative).
OverlayController.grantCloseWithoutConfirmation()returns a disposer.setOpen()honours an active grant the same way it honoursbypassConfirmation.Formwraps the submit flow and — separately — the after-submit callback invoked fromonAfterSuccessFeedback, releasing each grant as soon as that operation has settled (synchronously, or via.finally()for an awaitedonSubmit). Two independent grants avoid waiting on a callback that may never fire, e.g. when the form is submitted via Enter instead of aSubmitButton.closeIsConfirmedis now consumed on every close attempt instead of only on a successful close, so it cannot survive a close aborted by anonClosehandler either — the same latch shape, one level down.bypassConfirmationand theAction closeModalbehaviour are unchanged.Acceptance criteria
Modal keeps requiring confirmation after a submit that does not close itModal closed by its own Form submit needs no confirmation, covering all four paths: synchronously inonSubmit, after an awaited asynconSubmit, and in the after-submit callback with and withoutSubmitButtonsuccess feedbackbypassConfirmationandAction closeModalunchanged — covered by the existing suiteModal.browser.test.tsxalongside the existing confirmation testsBoth tests were verified to actually bite: each of the three grant sites was neutered individually, and in every case exactly the matching variant failed.
Docs
overview.mdxclaimed the confirmation is gone "nach einem erfolgreichen Submit" — a description of the bug. It now states that a submit which closes the Modal needs no prompt, while a Modal that stays open after a submit remains protected.Note on
OverlayControllerOverlayControlleris exported wholesale from the.entry, sograntCloseWithoutConfirmation()becomes public API. Additive and non-breaking; happy to move it behind an internal surface if you would rather not expose it.Verification
componentsbrowser tests (222), unit (156),test:compile,pnpm lint(0 errors), andpnpm buildwith no generated-code drift — all green.The local
test:visualrun forremote-react-componentsshows 26 failures on macOS, but that set is byte-identical with and without this change (verified over repeated runs with baseline cleanup in between) — pre-existing darwin baseline drift, unrelated. This change alters no rendered output, so no visual test was added; adding therun-visual-testslabel to confirm against the Linux baselines would still be worthwhile.Out of scope
The issue's "Related observation 1" —
Formoverruling<Modal confirmOnClose>when the global flag is on — appears already resolved:confirmOnCloseSourcesis keyed per source andconfirmOnCloseEnabledcombines them with.some(Boolean), i.e. the OR semantic the issue asks for. The existing test for it only runs with the flag off, though, so a flag-on case would be cheap insurance. Observation 2 (per-step<Form>losing dirty state) is untouched.🤖 Generated with Claude Code