Commit ba41d5d
authored
fix:
## 📜 Description
Fixed a problem, when re-focusing field (after dismiss via system
button) pushes the field significantly higher.
## 💡 Motivation and Context
The `onEnd` handler nulls `lastSelection.value` when the keyboard hides
(`e.height === 0`). This was added intentionally in #1234 to solve an
iOS 15 problem: without nulling, `onStart` couldn't distinguish a fresh
selection (that arrived before `onStart` in the current session) from a
stale selection (leftover from the previous session). On iOS 15,
selection sometimes arrives after `onStart`, so the
`pendingSelectionForFocus` mechanism was introduced to defer layout
setup until the selection arrives.
However, nulling `lastSelection` causes two regressions:
### 1️⃣ Android — refocus same input
Android doesn't re-emit `onSelectionChange` when refocusing the same
input at the same cursor position. After the `null`, `onStart` sees
l`astSelection.value?.target !== e.target` (because `null?.target` is
`undefined`), falls back to `layout.value = input.value` (full input
height, e.g. `180px` instead of caret height `43px`), and sets
`pendingSelectionForFocus = true`. But the selection event never arrives
- so `onMove` runs the entire animation with the **wrong** height.
### 2️⃣ iOS 15 — refocus same input with new cursor
Similar to Android - `lastSelection` is `null`, so `onStart` can't use
the stale selection as a reasonable fallback. It uses the full input
height instead.
The fix consist of several coordinated changes
### 1️⃣ Replace lastSelection.value = null with a flag
Instead of destroying selection data, introduce
`selectionUpdatedSinceHide`:
- Set to `false` in `onEnd` when keyboard **hides**
- Set to `true` in `onSelectionChange` when any selection arrives
- In `onStart`, the "fresh selection" check becomes:
```ts
lastSelection.value?.target === e.target && selectionUpdatedSinceHide.value
```
This preserves the iOS 15 detection (stale selection has
`selectionUpdatedSinceHide = false`, so `onStart` correctly enters the
pending path) while keeping the data available as a fallback.
### 2️⃣ Use stale selection as best-effort fallback in `onStart`
When the selection is stale (not fresh) but targets the same input, use
`updateLayoutFromSelection()` instead of falling back to `input.value`.
The stale caret position (e.g. `y=43`) is much closer to correct than
the full input height (e.g. `180`). If a fresh selection arrives later
(iOS 15), it will overwrite this — but on Android where it never
arrives, the stale value is already correct.
```ts
if (lastSelection.value?.target === e.target) {
updateLayoutFromSelection(); // stale but same target — use as fallback
} else if (input.value) {
layout.value = input.value; // different target or no selection at all
}
pendingSelectionForFocus.value = true;
```
### 3️⃣ Handle same-target refocus in onSelectionChange
Since `lastSelection` is no longer nulled, when iOS 15 refocuses the
same input, `onSelectionChange` arrives with `e.target === lastTarget`.
The existing check if (`e.target !== lastTarget`) won't enter the
deferred-setup block. Fix by also checking the pending flag:
```ts
if (e.target !== lastTarget || pendingSelectionForFocus.value) {
```
This ensures the deferred selection setup runs regardless of whether the
target changed, as long as `onStart` flagged it as pending.
### 4️⃣ Conditional cleanup of pendingSelectionForFocus in onEnd
To prevent the pending flag from leaking into the next focus session
(Android case where selection never arrives), clear it in `onEnd` - but
only when the keyboard was actually appearing
(`keyboardWillAppear.value`), not during a focus switch with the same
keyboard height. Otherwise, toolbar focus switching breaks: `onEnd`
fires immediately (no animation), clearing the flag before
`onSelectionChange` has a chance to process the deferred scroll.
```ts
if (e.height === 0) {
selectionUpdatedSinceHide.value = false;
} else if (keyboardWillAppear.value) {
pendingSelectionForFocus.value = false;
}
```
Closes
#1394
## 📢 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
- create separate `types.ts` file;
- created `KeyboardAwareScrollView` tests (70% test coverage, texting 5
most critical/hard to catch bugs);
- fixed a problem with re-focus on Android;
## 🤔 How Has This Been Tested?
Tested manually and via this PR.
## 📸 Screenshots (if appropriate):
|Before|After|
|-------|-----|
|<video
src="https://github.com/user-attachments/assets/c6eb29cf-6756-4bda-8f4f-ba7d83e15d4e">|<video
src="https://github.com/user-attachments/assets/7ecc2b46-77cc-4203-8264-63138d325ad5">|
|iOS 15|iOS 26|Android|
|------|-------|--------|
|<video
src="https://github.com/user-attachments/assets/7f867783-ab5d-4a8e-9232-a7a7ad68cdaf">|<video
src="https://github.com/user-attachments/assets/f054efa7-9774-450c-93ab-7a4d90e6c66b">|<video
src="https://github.com/user-attachments/assets/b49bbb15-63f7-4143-ba5c-dcd159159723">|
## 📝 Checklist
- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changedKeyboardAwareScrollView re-focus after hardware keyboard dismissal (#1403)1 parent af5da35 commit ba41d5d
10 files changed
Lines changed: 582 additions & 31 deletions
File tree
- src/components
- KeyboardAwareScrollView
- __fixtures__
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
Lines changed: 113 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
Lines changed: 151 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
Lines changed: 114 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
0 commit comments