Repository navigation
Add Mob.Keyboard.dismiss/1 and the dismiss_keyboard NIF - #173
Merged
Merged
Conversation
GenericJam
force-pushed
the
feat/keyboard-dismiss
branch
from
October 24, 2026 02:53
422b39d to
b9415d7
Compare
Owner
|
Thanks @tmepple, this is a clean addition and the write-up made the review easy. I rebased onto master (Zig 0.17.0) and added one commit on top. Summary: Changed
Verified
|
Verified: static (format, credo --strict, compile --warnings-as-errors, erlfmt, clang-format), host (mix test), and the iOS 26.5 simulator over dist: type_text succeeded before the call and returned no_first_responder after it with the field still in the tree; a value assigned in the same handler reached the field; on_blur arrives without a re-render and is dropped with one (decisions/2026-09-24-keyboard-dismiss-is-a-device-api.md). Android: host tests only. The bridge method is optional and no generated template defines it yet, so Android is a documented no-op until mob_new gains MobBridge.dismissKeyboard().
…blur iOS dismiss_keyboard now waits (IO_BOUND, 2 s bound) for the main thread to resign the first responder. Queued async, the handler's re-render reached a field SwiftUI still had focused, and a value: assigned next to the dismiss was dropped after an edit in nearly every run on the iPhone Air simulator; 22 of 22 landed with the wait. Android mob_send_blur resolves by identity, like change events and the iOS final-text blur: clearFocus() fires after the BEAM committed the handler's new tap table, and the strict check dropped 6 of 8 such blurs on a Moto G Power 2021 (10 of 10 arrive now). Docs, guide, decision record and CHANGELOG describe the paired mob_new MobBridge.dismissKeyboard() (hide IME + clear focus).
GenericJam
force-pushed
the
feat/keyboard-dismiss
branch
from
October 24, 2026 03:12
63eb0c1 to
cdb71ae
Compare
GenericJam
added a commit
to GenericJam/mob_new
that referenced
this pull request
Oct 24, 2026
* Android MobBridge.dismissKeyboard(): the Android half of Mob.Keyboard.dismiss/1 Hides the soft keyboard (InputMethodManager.hideSoftInputFromWindow) and clears focus on the UI thread, so the field's on_blur fires as on iOS, where dismissing resigns the first responder. mob's dismiss_keyboard NIF looks it up with cacheOptional by name and ()V (GenericJam/mob#173). Verified on a Moto G Power 2021: the decimal keyboard goes down (dumpsys input_method mInputShown=false), a value assigned in the same handler reaches the field, on_blur arrives, and the next tap focuses the field again. * dismissKeyboard targets the focused field's own window (sheets) A field in a Mob.UI.sheet renders in ModalBottomSheet's separate window, so activity.currentFocus carried the wrong window token: the IME ignored the hide and the field kept focus (verified on a Moto G Power 2021). MobTextField now records its LocalView on focus and dismissKeyboard targets it. * Test the hostView wiring; name the hostView line in the upgrade note
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.
What
Mob.Keyboard.dismiss/1and adismiss_keyboard/0NIF: take the on-screen keyboard down from a screen's own handler.delete_backward/key_pressuse). Fire-and-forget —dispatch_async, returnsokat once — shaped likehaptic/1/torch/1, not like the observing harness NIFs;key_press(:escape)remains the observing form.MobBridge.dismissKeyboard()(cached withcacheOptional); a bridge without it returns{:error, :not_loaded}, whichdismiss/1swallows. No generated template defines it yet. The pairedmob_newchange isInputMethodManager.hideSoftInputFromWindowon the activity's focused view, a dozen lines next tohaptic— happy to open that PR as well.dismiss/1is guarded on the host (unlikeMob.Haptic) so a screen that chains it stays testable underMob.ScreenCase.guides/device_capabilities.md→ Keyboard, CHANGELOG[Unreleased], anddecisions/2026-09-24-keyboard-dismiss-is-a-device-api.md. No version bump — per RELEASE.md that's yours to call.Why
A screen has no way to dismiss the keyboard from its own handler — its own Done or Save button, a tap on blank space, a committed value — short of removing the field from the tree. The built-in exits are the return key (which the
decimal/numberpads don't have, soon_submitnever fires for them), another field taking focus, and the iOS keyboard-toolbar Done. Hit in a downstream app (Alctrak) with a weight field on a decimal pad.Verification
mix format --check-formatted,mix credo --strict,mix compile --warnings-as-errors,mix erlfmt --check src/,xcrun clang-format --dry-run -Werror ios/mob_nif.m android/jni/mob_beam.h.mix test, 1812 passed.nif_scheduling_completeness_testclassifies the NIF as returns-promptly.Mob.Test.type_text/2succeeded before the call and returned{:error, :no_first_responder}after it, with the field still in the tree; full-screensimctlcaptures agree. Avalue:assigned in the same handler as the dismiss reached the field, so SwiftUI's focus state had already flipped whenMobTextField'sinitialTextsync ran.zig ast-checkpasses on the NIF, but it was not compiled or run (no Android toolchain here, and CI doesn't build the Android NIF) — please build it once before merging. The only Android path reachable today is the missing-method branch ({:error, :not_loaded}), the same shape asnif_clear_text; the JNI call itself can't execute until the template defines the method.One thing worth your eye
The field's
on_blurarrives when the NIF runs with no re-render, and viakey_press(:escape), but did not arrive when the dismissing handler also re-rendered — consistent with the blur firing after the new render generation committed and being dropped as stale. Documented as "act in the dismissing handler, not inon_blur". If you'd rather the resign go throughMob.Senderso the blur lands in the new generation, I can do that instead.No Linear issue yet — happy to rename the branch and title once one is assigned.
🤖 Generated with Claude Code