@@ -35,6 +35,15 @@ export const enableScrollAssist = (
35
35
const addScrollPadding =
36
36
enableScrollPadding && ( keyboardResize === undefined || keyboardResize . mode === KeyboardResize . None ) ;
37
37
38
+ /**
39
+ * This tracks whether or not the keyboard has been
40
+ * presented for a single focused text field. Note
41
+ * that it does not track if the keyboard is open
42
+ * in general such as if the keyboard is open for
43
+ * a different focused text field.
44
+ */
45
+ let hasKeyboardBeenPresentedForTextField = false ;
46
+
38
47
/**
39
48
* When adding scroll padding we need to know
40
49
* how much of the viewport the keyboard obscures.
@@ -50,6 +59,74 @@ export const enableScrollAssist = (
50
59
*/
51
60
const platformHeight = win !== undefined ? win . innerHeight : 0 ;
52
61
62
+ /**
63
+ * Scroll assist is run when a text field
64
+ * is focused. However, it may need to
65
+ * re-run when the keyboard size changes
66
+ * such that the text field is now hidden
67
+ * underneath the keyboard.
68
+ * This function re-runs scroll assist
69
+ * when that happens.
70
+ *
71
+ * One limitation of this is on a web browser
72
+ * where native keyboard APIs do not have cross-browser
73
+ * support. `ionKeyboardDidShow` relies on the Visual Viewport API.
74
+ * This means that if the keyboard changes but does not change
75
+ * geometry, then scroll assist will not re-run even if
76
+ * the user has scrolled the text field under the keyboard.
77
+ * This is not a problem when running in Cordova/Capacitor
78
+ * because `ionKeyboardDidShow` uses the native events
79
+ * which fire every time the keyboard changes.
80
+ */
81
+ const keyboardShow = ( ev : CustomEvent < { keyboardHeight : number } > ) => {
82
+ /**
83
+ * If the keyboard has not yet been presented
84
+ * for this text field then the text field has just
85
+ * received focus. In that case, the focusin listener
86
+ * will run scroll assist.
87
+ */
88
+ if ( hasKeyboardBeenPresentedForTextField === false ) {
89
+ hasKeyboardBeenPresentedForTextField = true ;
90
+ return ;
91
+ }
92
+
93
+ /**
94
+ * Otherwise, the keyboard has already been presented
95
+ * for the focused text field.
96
+ * This means that the keyboard likely changed
97
+ * geometry, and we need to re-run scroll assist.
98
+ * This can happen when the user rotates their device
99
+ * or when they switch keyboards.
100
+ *
101
+ * Make sure we pass in the computed keyboard height
102
+ * rather than the estimated keyboard height.
103
+ *
104
+ * Since the keyboard is already open then we do not
105
+ * need to wait for the webview to resize, so we pass
106
+ * "waitForResize: false".
107
+ */
108
+ jsSetFocus (
109
+ componentEl ,
110
+ inputEl ,
111
+ contentEl ,
112
+ footerEl ,
113
+ ev . detail . keyboardHeight ,
114
+ addScrollPadding ,
115
+ disableClonedInput ,
116
+ platformHeight ,
117
+ false
118
+ ) ;
119
+ } ;
120
+
121
+ /**
122
+ * Reset the internal state when the text field loses focus.
123
+ */
124
+ const focusOut = ( ) => {
125
+ hasKeyboardBeenPresentedForTextField = false ;
126
+ win ?. removeEventListener ( 'ionKeyboardDidShow' , keyboardShow ) ;
127
+ componentEl . removeEventListener ( 'focusout' , focusOut , true ) ;
128
+ } ;
129
+
53
130
/**
54
131
* When the input is about to receive
55
132
* focus, we need to move it to prevent
@@ -76,11 +153,17 @@ export const enableScrollAssist = (
76
153
disableClonedInput ,
77
154
platformHeight
78
155
) ;
156
+
157
+ win ?. addEventListener ( 'ionKeyboardDidShow' , keyboardShow ) ;
158
+ componentEl . addEventListener ( 'focusout' , focusOut , true ) ;
79
159
} ;
160
+
80
161
componentEl . addEventListener ( 'focusin' , focusIn , true ) ;
81
162
82
163
return ( ) => {
83
164
componentEl . removeEventListener ( 'focusin' , focusIn , true ) ;
165
+ win ?. removeEventListener ( 'ionKeyboardDidShow' , keyboardShow ) ;
166
+ componentEl . removeEventListener ( 'focusout' , focusOut , true ) ;
84
167
} ;
85
168
} ;
86
169
@@ -110,7 +193,8 @@ const jsSetFocus = async (
110
193
keyboardHeight : number ,
111
194
enableScrollPadding : boolean ,
112
195
disableClonedInput = false ,
113
- platformHeight = 0
196
+ platformHeight = 0 ,
197
+ waitForResize = true
114
198
) => {
115
199
if ( ! contentEl && ! footerEl ) {
116
200
return ;
@@ -217,7 +301,7 @@ const jsSetFocus = async (
217
301
* bandwidth to become available.
218
302
*/
219
303
const totalScrollAmount = scrollEl . scrollHeight - scrollEl . clientHeight ;
220
- if ( scrollData . scrollAmount > totalScrollAmount - scrollEl . scrollTop ) {
304
+ if ( waitForResize && scrollData . scrollAmount > totalScrollAmount - scrollEl . scrollTop ) {
221
305
/**
222
306
* On iOS devices, the system will show a "Passwords" bar above the keyboard
223
307
* after the initial keyboard is shown. This prevents the webview from resizing
0 commit comments