feat: use contentInset for KeyboardAwareScrollView#797
Merged
kirillzyusko merged 8 commits intoFeb 1, 2026
Conversation
Contributor
📊 Package size report
|
2 tasks
kirillzyusko
added a commit
that referenced
this pull request
Jan 28, 2026
## 📜 Description
Added `ClippingScrollView` component (on Android only) that is supposed
to act as a polyfill for `contentInset: {bottom}` property of
`ScrollView`.
## 💡 Motivation and Context
Those changes are based on my PR from
react/react-native#49145
The big problem with original PR is that it wasn't working correctly, if
we specify all insets/paddings simultaneously. But! It worked well for
`bottom` inset property (keyboard-controller case, because keyboard
appears from the bottom of the screen). I think it's risky to ship a
code in this state into facebook codebase, so I decided to add those
changes into `react-native-keyboard-controller` first. It's good
because:
- I have a full ownership of the code and I can fix bugs quickly
(without waiting for a new RN release);
- we don't ship buggy code in react-native repository;
- we don't depend on react-native version and we don't need to write a
conditional code, like "if prop is supported, then use new approach and
if not, then fallback to old implementation".
The approach that I've choose is based on "decorator" approach - this
approach has been used in many other libs, such as
`advanced-input-mask`, `live-markdown` etc. The idea is that we create
our custom view that wraps our target view and then we access a target
view as a children (and we can modify behavior/props of this view).
I'm going to use this component in `KeyboardAwareScrollView` and in new
`ChatKit` component. From lessons learned from the previous experience I
can confidently say, that additional view near children will cause
issues and this polyfill for `contentInset: {bottom}` should hopefully
solve all the issues that we had. I'll continue experiments with this
view in
#797
## 📢 Changelog
<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->
### JS
- added codegen component;
- added new types props for new component;
- added jsdoc for new component;
### Android
- added `ClippingScrollView`;
## 🤔 How Has This Been Tested?
Tested in example app in `KeyboardAwareScrollView` screen and in new
component that is currently under active development 😊 Everywhere works
stable on both architectures 🤞
## 📸 Screenshots (if appropriate):
|KeyboardAwareScollView|Non inverted chat list|
|--------------------------|---------------------|
|<video
src="https://github.com/user-attachments/assets/0f7efef7-ec15-4fe6-96cb-40334f0c91ad">|<video
src="https://github.com/user-attachments/assets/92093e87-434a-4c82-91d5-1c7e9706e5f0">|
## 📝 Checklist
- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
29efbf3 to
f11412f
Compare
9e74ef9 to
17bc9d4
Compare
kirillzyusko
commented
Feb 1, 2026
Owner
Author
There was a problem hiding this comment.
Caught a regression on iOS 26 + Xcode 16 👍
kirillzyusko
added a commit
that referenced
this pull request
Feb 1, 2026
## 📜 Description Fixed iOS e2e tests. ## 💡 Motivation and Context The pipeline got broken here: #797 iOS 18 tests were failing because of 1 asset <- just updated one screenshot iOS 26 + XCode 16 were failing because of 2 assets <- things went harder here On `iOS 26 + XCode 16` I used old iOS 26.0 version, which resulted in a wrong behavior. It was hard to fix it in the lib codebase, so I just decided to switch to a new iOS version. For that I had to update all snapshots. ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### E2E - updated iOS e2e assets; - use iOS 26.2 instead of iOS 26.0. ## 🤔 How Has This Been Tested? Tested manually via e2e run on a failed exam. ## 📸 Screenshots (if appropriate): <img width="858" height="395" alt="image" src="https://github.com/user-attachments/assets/58850c15-2bc4-4d6a-9159-97eafce64a25" /> ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
2 tasks
kirillzyusko
added a commit
that referenced
this pull request
Mar 4, 2026
## 📜 Description Make `scrollRectToVisible` of `ReactScrollView` no-op. ## 💡 Motivation and Context This issue became visible after #797 Before we were modifying `height` of fake view. And `scrollRectToVisible` is not sensitive to this: ```objc - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated { // Limiting scroll area to an area where we actually have content. CGSize contentSize = self.contentSize; UIEdgeInsets contentInset = self.contentInset; CGSize fullSize = CGSizeMake( contentSize.width + contentInset.left + contentInset.right, contentSize.height + contentInset.top + contentInset.bottom); rect = CGRectIntersection((CGRect){CGPointZero, fullSize}, rect); if (CGRectIsNull(rect)) { return; } [super scrollRectToVisible:rect animated:animated]; } ``` But since we started to modify `contentInset` this function now can also "scroll" and this causes strange effect such as #1331 This is a `ReactScrollView` built-in solution for "avoiding" `TextInput`. Logs: ``` 1, 298.31225727257987, 116, 718.6666666666666 'syncKeyboardFrame', 308 14 116, 718.6666666666666, 834.6666666666666 1, 298.66666666666663, 116, 718.6666666666666 [scrollRectToVisible] rect=(16.0, 713.7, 10.3, 24.7) animated=1 contentOffset=(0.0, 298.7) bounds=(402.0 x 758.0) contentSize=(402.0, 1084.7) contentInset=(t=0.0 l=0.0 b=339.0 r=0.0) fullSize=(402.0, 1423.7) visibleY=[298.7, 1056.7] alreadyVisible=1 safeAreaInsets=(t=16.0 l=0.0 b=34.0 r=0.0) adjustedContentInset=(t=0.0 l=0.0 b=339.0 r=0.0) -> CALLING [super scrollRectToVisible] 'syncKeyboardFrame', 308 308 keyboard height + 1 static pixel + 30 extraKeyboardHeight = 339 ``` Which we can evaluate as: ``` effective visible height = bounds.height - contentInset.bottom = 758 - 339 = 419 visible range = [contentOffset.y, contentOffset.y + 419] = [298.7, 717.7] rect bottom = 713.7 + 24.7 = 738.4 738.4 > 717.7 → NOT visible from UIKit's perspective! ``` So UIKit scrolls by 738.4 - 717.7 ≈ 20.7px 😡 I think this effect is totally undesirable for `KeyboardAwareScrollVIew`/`ClippingScrollView` since we control scroll position on our own, so via swizzling (this is the only one option at the moment, correct option would be to add a new prop to `ScrollView` in `react-native`) I make this method a no-op. I checked and functionality like "tap-status-bar-to-scroll" works well. So I think it's safe to have this fix 🤞 Closes #1331 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - make `ClippingScrollView` real view on iOS; ### iOS - add `ClippingScrollView` shadow node; - replace `scrollRectToVisible` to no-op; ### Android - add `ClippingScrollView` shadow node; ## 🤔 How Has This Been Tested? Tested manually on iPhone 17 Pro (iOS 26.2). ## 📸 Screenshots (if appropriate): https://github.com/user-attachments/assets/bb24aa16-661a-41af-b7d1-37fbbd9f4353 ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
2 tasks
kirillzyusko
added a commit
that referenced
this pull request
Mar 19, 2026
## 📜 Description Don't change keyboard padding in `KeyboardAwareScrollView` each frame as keyboard moves and do it only in the beginning of the animation (when keyboard appears) or in the end of the animation (when keyboard closed). ## 💡 Motivation and Context After #797 introduced removal of `ghost padding` we don't need to change `inset` every frame. Instead we can use an optimized approach and: - add full keyboard frame padding only in the beginning of the animation (when keyboard appears) - remove full keyboard frame padding when keyboard fully closed. Roughly it gives us `2x` less load on UI thread because now we only need to adjust scroll position inside `onMove` handler. A similar approach is already used in `KeyboardChatScrollView` (we also change padding only one time there). So these changes just makes components consistent. ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - don't change `currentKeyboardFrame` each frame; ## 🤔 How Has This Been Tested? Tested manually on iPhone 17 Pro (iOS 26.2, simulator), Pixel 7 Pro (API 36, real device). ## 📸 Screenshots (if appropriate): |iOS|Android| |---|--------| |<video src="https://github.com/user-attachments/assets/9e5f72cc-b6c7-4ea5-999f-d5c56c046809">|<video src="https://github.com/user-attachments/assets/31f5a675-2de8-4136-91fe-7390364bbb97">| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
2 tasks
kirillzyusko
added a commit
that referenced
this pull request
Mar 23, 2026
## 📜 Description Removed a fix with increased padding by `+1`. ## 💡 Motivation and Context This code has been added in #342 But these changes became irrelevant after #1381 Since we started to change frame to full-height frame once per the animation we no longer have a condition described in #342 Additionally #332 is no longer reproducible, because in #797 we already were fighting with "ghost view" issue and fix for this problem also fixes #332 (just in a different way). So to sum it up: - we no longer need to adjust spacer each frame; - we no longer need a fix with artificial spacer increasing 🤞 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - don't apply `+1` to keyboard spacer size in `KeyboardAwareScrollView`; ## 🤔 How Has This Been Tested? Tested manually on Redmi Note 5 Pro (Android 9). ## 📸 Screenshots (if appropriate): |Current code|Latest main (before PR changes)|1.20.7 release| |-------------|---------------------------------|-------------| |<video src="https://github.com/user-attachments/assets/7ab3477c-eedd-42da-9af9-5bdb0c842284">|<video src="https://github.com/user-attachments/assets/8697e5e6-84d3-49c5-adb6-2d286cd9ccaa">|<video src="https://github.com/user-attachments/assets/37c9b30c-8aa8-470a-9255-8c4a3d752a95">| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
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.
📜 Description
Use
ScrollViewWithBottomPaddingcomponent forKeyboardAwareScrollViewto optimize performance and fix other issues that were caused by the fact of usage additional view insideScrollView.💡 Motivation and Context
In this PR I'm moving away from the idea of having a fake view in the end of the
ScrollViewand instead start to useScrollViewWithBottomPaddingcomponent to achieve a desired visual effect.The additional view causes many issues such as:
flex: 1style is used;gap/justifyContent: "space-between"and other properties.In this PR I'm switching to the component that has been added in #1294 With its new power I can achieve cross-platform behavior and:
This PR has been opened for a long time, but finally can be merged because I got working version on Android.
Closes #794 #645 #929 #168
Potentially: #748 software-mansion/react-native-reanimated#5567 #719
Unlocks one item from #883
📢 Changelog
JS
useScrollStatehook;ScrollViewWithBottomPaddingcomponent inKeyboardAwareScrollView;removeGhostPaddinginKeyboardAwareScrollViewand use it there.E2E
KeyboardToolbarClosedtest-case.🤔 How Has This Been Tested?
Tested manually on:
📸 Screenshots (if appropriate):
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-02-01.at.16.46.14.mov
Screen.Recording.2026-02-01.at.16.49.23.mov
📝 Checklist