fix: add automaticOffset prop for correct KAV positioning in modals#1346
Conversation
KeyboardAvoidingView uses onLayout to get the view's frame, but onLayout returns parent-relative coordinates. In pageSheet modals, the parent's y=0 doesn't correspond to screen y=0, causing the keyboard avoidance calculation to use incorrect absolute positions. This commit fixes two issues: 1. Use measureInWindow instead of onLayout to get absolute screen coordinates. This ensures frame.y reflects the actual screen position, not the parent-relative position. 2. Compensate for the gap below pageSheet modals. In pageSheet presentation, the modal doesn't extend to the screen bottom, but the keyboard covers this gap. The overlap calculation now treats the view as extending to the screen bottom when it detects a modal-like configuration (gap > 0, gap < keyboard height, view > 50% of screen). A combinedRef is used to maintain both the internal ref (needed for measureInWindow) and the forwarded ref from the consumer. Fixes kirillzyusko#867 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
33bcd74 to
9436c60
Compare
Type useRef as View | null to avoid the MutableRefObject cast. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9436c60 to
faf5e6f
Compare
measureInWindow already provides absolute screen coordinates, making the modal gap compensation unnecessary. The heuristic was compensating for onLayout returning parent-relative y=0 in modals, but that's now fixed at the source. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📊 Package size report
|
|
Thank you @thomasttvo for your PR Unfortunately I can not merge it as is. Try to open example app and check the difference between package/RN implementations. You will see that package implementation will push the content significantly higher than it's expected to do. Ideally RN and package implementation should produce identical visual result. This is a breaking change and we should understand what's causing this and fix it before a merge 🤞 You can also download e2e artifacts to see what exactly doesn't work... |
|
hey @kirillzyusko sorry about that! I meant to put this PR back in a WIP state since there's some significant changes to it since it's opened. Let me check what's going on with these issues and get back to you. Thank you for your patience! |
…ation With measureInWindow, frame.y is already in absolute screen coordinates, making keyboardVerticalOffset redundant in the formula. Removing it fixes over-compensation on regular screens while maintaining correct behavior in modals. Also adds modal test case to the example app and uses the existing useCombinedRef hook instead of a manual ref callback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With measureInWindow, the view's absolute position is already captured in frame.y. keyboardVerticalOffset is now purely additive extra offset rather than compensation for unknown view position. Update example to use offset=0 since nav bar is handled automatically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cycles through 0, 50, 100 to demonstrate the additive offset behavior. Shows "+N" in the header bar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With measureInWindow, navigation headers and modals are handled automatically. keyboardVerticalOffset is now purely additive extra offset — no need to pass useHeaderHeight() or compensate manually. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@kirillzyusko ready for review! I tested everything manually and verified it works locally, but I may have missed a few details. Let me know if you spot anything or have any feedback on the changes. |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
measureInWindow can return stale y=0 when onLayout fires during a modal's slide-up animation. This caused height and position modes to under/over-compensate in modals. Two fixes: 1. onLayoutWorklet for height mode now allows position (x, y) updates while preserving the original height — prevents stale y without reintroducing the height feedback loop. 2. Re-measure absolute position on keyboard start via useKeyboardHandler. By the time the user focuses an input, the modal animation is complete and measureInWindow returns the correct coordinates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
useKeyboardAnimation and useTranslateAnimation already call useKeyboardHandler (which includes useResizeMode). The reMeasure handler only needs event subscription, not the resize mode side effect. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hey @thomasttvo I reviewed your PR and I think it has breaking changes. Since we start to calculate I tend to think this is a breaking change and I don't want to created a release V2 just because of these changes. Do you think we can hide new functionality under feature flag/new prop? For example Again, my concern is that |
|
makes sense @kirillzyusko let me see what I can do |
The measureInWindow change was a breaking change for users who set keyboardVerticalOffset to their header height. This gates it behind an `automaticOffset` prop (default false) so existing behavior is preserved. When enabled, the view auto-detects its screen position and keyboardVerticalOffset becomes purely additive. Also moves example app settings from nav header to screen content area, updates E2E tests for both modes, and updates docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
You can ignore them for now. Argos job is flacky a little bit and needs to be fixed (I'll handle it separately) |
- Remove reMeasure + useGenericKeyboardHandler (unnecessary: keyboard open triggers onLayout which corrects stale measureInWindow values) - Remove runOnJS and useGenericKeyboardHandler imports - Simplify onLayout to single if/else without nested null checks - Scope onLayoutWorklet else branch to automaticOffset only (non-automatic mode uses parent-relative coords that are always correct) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove relativeKeyboardHeight comment (duplicates automaticOffset JSDoc) - Remove onLayout ref existence comment (self-evident from optional chain) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite the automaticOffset else-branch comment to explain the full chain: why the if-branch skips updates, why automaticOffset needs the correction, and why height is preserved. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d96427c to
ed904e4
Compare
Split the single block comment into two focused comments — one per branch — so readers understand each path independently. Add ref safety comment on measureInWindow call. Remove debug console.log. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ed904e4 to
11ea978
Compare
Position mode's outer view doesn't resize when the keyboard opens
(only the inner view shifts via { bottom }), so onLayout won't
re-fire to correct stale measureInWindow values from modal animation.
Add useGenericKeyboardHandler to re-measure on keyboard start.
Also: split onLayoutWorklet comments, add ref safety comment,
simplify else-branch with spread, remove debug console.log.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverts example app and e2e test modifications to keep this PR focused on the core automaticOffset implementation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New dedicated example screen showcasing automaticOffset in KeyboardAvoidingView, including a page-sheet Modal to test all three behavior modes (padding, height, position). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Testing confirmed measureInWindow returns correct coordinates at onLayout time in modals, making the useGenericKeyboardHandler re-measurement unnecessary. Also adds Auto/Manual toggle to the example screen and removes debug code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The else-if branch for automaticOffset in height mode was not needed — the stale y=0 from measureInWindow gets corrected on the next onLayout while the keyboard is still closed. Height mode's shrunk-view bug exists regardless of this branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@kirillzyusko 🤦 , this is very strange. I can see the |
kirillzyusko
left a comment
There was a problem hiding this comment.
Looks good! Thank you for your contribution ❤️
|
FYI @thomasttvo it seems like it doesn't work with new architecture (FabricExample) + Modal:
Paper architecture works well. Is it a bug in |
|
@kirillzyusko on it! should have checked that we've been testing against Paper all this time. |
|
@thomasttvo the problem seems to be in react-native and how |
|
I opened a PR here: facebook/react-native#56062 |
) ## 📜 Description Automatically detect top border of `KeyboardAwareScrollView`. ## 💡 Motivation and Context Continue the epic with better discovery of component location on the screen and logical continuation of #1346 In this PR I started to detect relative position of `ScrollView`, so that I better understand if caret is not visible because it obscured by other elements (header etc.) It's still not perfectly implemented and there is still a "blind"/"dead" zone where text is already hidden but scroll doesn't happen. I'll fix it in following PRs but for now I just want to bring these changes to upcoming `1.21.0` release 🤞 We also can't use `measureInWindow` because it produces incorrect measurements, so we need to use our custom implementation that has been added in #1355 Significantly improves UI for behavior described in #1341 ## 📢 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 - auto detect position of `KeyboardAwareScrollView` on the screen to better understand top border of the component relative to screen; ## 🤔 How Has This Been Tested? Tested manually on iPhone 17 Pro (iOS 26.2, simulator). ## 📸 Screenshots (if appropriate): https://github.com/user-attachments/assets/43fe233f-9c83-40c9-9642-a68e2d8e8e7c ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
## 📜 Description Silent `could not fing view for tag` warning for `KeyboardAwareScrollView` (similarly how it works for `KeyboardAvoidingView` now). ## 💡 Motivation and Context This issue got introduced after #1352 (which was based on #1346) In certain cases `onLayout` may be triggered but view may be not laid out yet (if you use it with `react-native-pager` lazy). Since changes in #1352 were purely additional (we try to understand the position of element on the screen, but before these changes we always assumed we were in the top of the screen) I decided to wrap it for now in `try/catch` block. Silenting error is not a correct fix, but this is the best what we can do now - we will not spam logs when devs update the package (in #1346 we also used `catch` block), but yes, in certain cases detection of top border will not work (and it didn't work before, so it's fair enough to silent the error). Closes #1375 ## 📢 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 - handle rejected promises within `try/catch` block; ## 🤔 How Has This Been Tested? Tested via e2e tests, I didn't test changes manually because I don't have a repro. ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed

Problem
KeyboardAvoidingViewdoesn't work correctly inpageSheetmodals on iOS becauseonLayoutreturns parent-relative coordinates. In a modal, the parent'sy=0doesn't correspond to screeny=0, so the keyboard overlap calculation uses incorrect positions — resulting in the keyboard covering the input or the view being offset incorrectly.This is an iOS-specific issue. On Android, modals use a separate
Dialogwindow, butmeasureInWindow(backed bygetLocationOnScreen()) already returns correct absolute screen coordinates regardless of which window the view is in.Solution
Add an opt-in
automaticOffsetprop (defaultfalse) that gates themeasureInWindowbehavior. This preserves backward compatibility — existing users who setkeyboardVerticalOffsetto their header height continue to work unchanged.When
automaticOffset={true}:measureInWindowinstead ofonLayoutto get absolute screen coordinates, so the view's position is correctly detected in modals, behind navigation headers, etc.keyboardVerticalOffsetbecomes purely additive extra space rather than compensation for unknown positioning — the default of0works correctly out of the box.When
automaticOffset={false}(default):onLayoutparent-relative coordinates, andkeyboardVerticalOffsetmust be set to the header height manually.Uses the existing
useCombinedRefhook to maintain both the internal ref (needed formeasureInWindow) and the forwarded ref from the consumer.Example app
Added a new "KeyboardAvoidingView Automatic" example screen (
KeyboardAvoidingViewAutomatic) to showcaseautomaticOffsetbehavior:automaticOffset={true}vsautomaticOffset={false}pageSheetModal to test the modal positioning fixkeyboardVerticalOffsettoggle (+0, +50, +100)KAVContentcomponent shared between regular screen and modalNo changes to the existing KAV example screen or E2E tests.
Test Plan
iOS (iPhone 16 simulator),
automaticOffset={true},keyboardVerticalOffset={0}:automaticOffset={false}(default),keyboardVerticalOffset={100}:Fixes #867