Symmetric keyboard dismissal with keep-focus-on-submit as the escape hatch - #353
Open
gwleuverink wants to merge 1 commit into
Open
Symmetric keyboard dismissal with keep-focus-on-submit as the escape hatch#353gwleuverink wants to merge 1 commit into
gwleuverink wants to merge 1 commit into
Conversation
gwleuverink
marked this pull request as ready for review
August 18, 2026 10:16
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.
Fixes #335. Companion PR: NativePHP/mobile-ui#53 carries the input-renderer half. They need to land together.
The report was a chat composer, a bare-text-input and a send pressable in one row. On iOS, tapping send fires @press and dismisses the keyboard in the same tap, so the user re-focuses the field after every message. keep-focus-on-submit didn't help, it only covered the return key. Reproduced exactly as filed.
While verifying I mapped the dismissal behaviour on both platforms, and they disagreed on nearly everything. Tapping a button while a field was focused dismissed on iOS but not on Android. Submit dismissed on iOS but not on Android, even though the docs describe dismiss as the default. And keep-focus-on-submit worked on iOS but was silently ignored on Android, the renderer never parsed the prop.
The fix rests on three decisions. The platforms should behave identically, and the web-like default wins: the keyboard drops on submit and on any tap outside the field, which is also the right default for regular forms, where tapping Submit should close the keyboard. Only Android's defaults change, iOS already behaved this way and its defaults are untouched. And keep-focus-on-submit becomes the one escape hatch that genuinely works on both platforms, keeping the field focused through the return key and through button sends alike. Tapping empty space always dismisses, even with the attribute, same as iMessage and WhatsApp transcripts.
Mechanically, the screen-level dismiss gesture is now a regular gesture instead of a simultaneousGesture, so it only fires for taps nothing else claimed, the plain-area path. Taps on interactive elements dismiss through a small KeyboardFocusPolicy that press dispatch consults, which is what lets the focused field's attribute exempt them. Android mirrors the same shape, its existing root clickable stays the plain-area path and the press modifiers route through the policy.
While testing on iOS we also caught a subtle one, worth spelling out because it shaped how press dispatch works. iOS commits a pending autocorrect suggestion when you tap elsewhere, and with the keyboard no longer resigning on a send tap, that commit now lands after the press has already been dispatched. The sequence was: tap send, PHP receives the press and sends the draft as typed, iOS then applies the correction to the field, and the late change event re-fills the server-side draft that send() just cleared. The field stays filled with the corrected word, and tapping send again produces a ghost second message.
Press dispatch on iOS now does two things about that. It flushes the focused field's pending change first, and it defers the press event by one runloop turn, so the correction's change event reaches PHP before the press does. A send with a pending suggestion now sends the corrected text, clears the field, and adds exactly one message. Verified on the simulator. Android turned out not to have the race at all, Gboard only suggests passively and never applies a correction on an unrelated tap, also verified on the emulator.
The behavioural change is Android-only and lives mostly in the companion PR: return and button taps start dropping the keyboard by default there, like iOS always did. No API changes, existing code runs as is. Android chat screens that relied on the keyboard staying up add keep-focus-on-submit to the input.
Verified on device, Android emulator (dumpsys mInputShown plus screenshots) and iOS simulator (manual):
Also verified on both platforms: the multiline composer from the issue (return inserts a newline, the send button sends the whole draft, the keyboard never moves) and the autocorrect scenario above.
One known limitation, pre-existing: with the attribute on a single-line field, the return key still shows a brief keyboard bounce on iOS (resign then refocus). That's an acknowledged SwiftUI platform bug, Apple's own focus sample reproduces it, and the in-code notes in NativeUITextInputCore already point at a UIKit-backed field as the eventual fix if it ever becomes worth it. It doesn't affect button sends or multiline composers, which is where the attribute matters.