From 91d34e1105f14a160f0ab897a237eed78eda56ad Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Fri, 17 Apr 2026 12:41:22 -0700 Subject: [PATCH] Re-emit keyboardDidShow on IME height change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Follow-up to D100437445, which replaced the legacy height-based IME detector in `ReactRootView.CustomGlobalLayoutListener` with a visibility-only detector (`WindowInsetsCompat.Type.ime()`). The legacy code also re-emitted `keyboardDidShow` whenever the IME *height* changed while still visible (e.g., user toggles the emoji panel or predictive bar). Removing that left these JS consumers reading stale `endCoordinates`: - `KeyboardAvoidingView` — caches `_keyboardEvent`, no longer resizes on IME height changes. - `ScrollView` — caches `_keyboardMetrics`; focused `TextInput` auto-scroll uses stale `screenY`/`height`. - `Keyboard.metrics()` — public API returning the cached payload from the last `keyboardDidShow`. Fix: track the last reported IME height and re-emit `keyboardDidShow` when the keyboard is visible AND (it just appeared OR the height changed), restoring pre-D100437445 semantics. The hide branch is gated on `mKeyboardIsVisible` so it still fires exactly once per cycle. Changelog: [Android][Fixed] KeyboardAvoidingView and TextInput auto-scroll not responding to IME height changes (e.g., when toggling emoji panel or predictive bar) Differential Revision: D101385688 --- .../com/facebook/react/ReactRootView.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 2577fdf57d0a..8fcd2812a286 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -931,6 +931,7 @@ private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLay private final Rect mVisibleViewArea; private boolean mKeyboardIsVisible = false; + private int mKeyboardHeight = 0; private int mDeviceRotation = 0; /* package */ CustomGlobalLayoutListener() { @@ -957,13 +958,17 @@ private void checkForKeyboardEvents() { } boolean keyboardIsVisible = rootInsets.isVisible(WindowInsetsCompat.Type.ime()); - if (keyboardIsVisible != mKeyboardIsVisible) { - mKeyboardIsVisible = keyboardIsVisible; - Insets barInsets = rootInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + Insets barInsets = rootInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - if (keyboardIsVisible) { - Insets imeInsets = rootInsets.getInsets(WindowInsetsCompat.Type.ime()); - int height = imeInsets.bottom - barInsets.bottom; + if (keyboardIsVisible) { + Insets imeInsets = rootInsets.getInsets(WindowInsetsCompat.Type.ime()); + int height = imeInsets.bottom - barInsets.bottom; + + // Re-emit on height change while keyboard stays visible (e.g., emoji + // panel toggle); JS consumers cache endCoordinates from keyboardDidShow. + if (!mKeyboardIsVisible || height != mKeyboardHeight) { + mKeyboardIsVisible = true; + mKeyboardHeight = height; ViewGroup.LayoutParams rootLayoutParams = getRootView().getLayoutParams(); Assertions.assertCondition(rootLayoutParams instanceof WindowManager.LayoutParams); @@ -981,15 +986,18 @@ private void checkForKeyboardEvents() { PixelUtil.toDIPFromPixel(mVisibleViewArea.left), PixelUtil.toDIPFromPixel(mVisibleViewArea.width()), PixelUtil.toDIPFromPixel(height))); - } else { - sendEvent( - "keyboardDidHide", - createKeyboardEventPayload( - PixelUtil.toDIPFromPixel(mVisibleViewArea.bottom + barInsets.bottom), - 0, - PixelUtil.toDIPFromPixel(mVisibleViewArea.width()), - 0)); } + } else if (mKeyboardIsVisible) { + mKeyboardIsVisible = false; + mKeyboardHeight = 0; + + sendEvent( + "keyboardDidHide", + createKeyboardEventPayload( + PixelUtil.toDIPFromPixel(mVisibleViewArea.bottom + barInsets.bottom), + 0, + PixelUtil.toDIPFromPixel(mVisibleViewArea.width()), + 0)); } }