mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
fix(scroll-assist): re-run when keyboard changes (#28174)
Issue number: resolves #22940 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Scroll assist does not run when changing keyboards. This means that inputs can be hidden under the keyboard if the new keyboard is larger than the previous keyboard. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - On Browsers/PWAs scroll assist will re-run when the keyboard geometry changes. We don't have a cross-browser way of detecting keyboard changes yet, so this is the best we have for now. - On Cordova/Capacitor scroll assist will re-run when the keyboard changes, even if the overall keyboard geometry does not change. In the example below, we are changing keyboards while an input is focused: | `main` | branch | | - | - | | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/715e176a-6724-4308-ae3e-15b5bea308ac"></video> | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/b9ccd482-720a-409b-a089-b3330c1e405c"></video> | Breakdown per-resize mode: | Native | None | Ionic | Body | | - | - | - | - | | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/b930ac5f-3398-4887-a8ca-a57708adc66d"></video> | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/68465854-94d0-4e00-940c-c4674a43b6a3"></video> | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/561f313a-9caf-4c9e-ab15-9c4383f0e3ee"></video> | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/300b8894-ad2a-43bc-8e82-ecd68afd407e"></video> | ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev build: `7.3.4-dev.11694706860.14b2710d` --------- Co-authored-by: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com>
This commit is contained in:
@ -20,6 +20,26 @@
|
|||||||
* Note: Code inside of this if-block will
|
* Note: Code inside of this if-block will
|
||||||
* not run in an SSR environment.
|
* not run in an SSR environment.
|
||||||
*/
|
*/
|
||||||
export const win: Window | undefined = typeof window !== 'undefined' ? window : undefined;
|
|
||||||
|
/**
|
||||||
|
* Event listeners on the window typically expect
|
||||||
|
* Event types for the listener parameter. If you want to listen
|
||||||
|
* on the window for certain CustomEvent types you can add that definition
|
||||||
|
* here as long as you are using the "win" utility below.
|
||||||
|
*/
|
||||||
|
type IonicWindow = Window & {
|
||||||
|
addEventListener(
|
||||||
|
type: 'ionKeyboardDidShow',
|
||||||
|
listener: (ev: CustomEvent<{ keyboardHeight: number }>) => void,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
|
): void;
|
||||||
|
removeEventListener(
|
||||||
|
type: 'ionKeyboardDidShow',
|
||||||
|
listener: (ev: CustomEvent<{ keyboardHeight: number }>) => void,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
|
): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const win: IonicWindow | undefined = typeof window !== 'undefined' ? window : undefined;
|
||||||
|
|
||||||
export const doc: Document | undefined = typeof document !== 'undefined' ? document : undefined;
|
export const doc: Document | undefined = typeof document !== 'undefined' ? document : undefined;
|
||||||
|
@ -35,6 +35,15 @@ export const enableScrollAssist = (
|
|||||||
const addScrollPadding =
|
const addScrollPadding =
|
||||||
enableScrollPadding && (keyboardResize === undefined || keyboardResize.mode === KeyboardResize.None);
|
enableScrollPadding && (keyboardResize === undefined || keyboardResize.mode === KeyboardResize.None);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tracks whether or not the keyboard has been
|
||||||
|
* presented for a single focused text field. Note
|
||||||
|
* that it does not track if the keyboard is open
|
||||||
|
* in general such as if the keyboard is open for
|
||||||
|
* a different focused text field.
|
||||||
|
*/
|
||||||
|
let hasKeyboardBeenPresentedForTextField = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When adding scroll padding we need to know
|
* When adding scroll padding we need to know
|
||||||
* how much of the viewport the keyboard obscures.
|
* how much of the viewport the keyboard obscures.
|
||||||
@ -50,6 +59,74 @@ export const enableScrollAssist = (
|
|||||||
*/
|
*/
|
||||||
const platformHeight = win !== undefined ? win.innerHeight : 0;
|
const platformHeight = win !== undefined ? win.innerHeight : 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll assist is run when a text field
|
||||||
|
* is focused. However, it may need to
|
||||||
|
* re-run when the keyboard size changes
|
||||||
|
* such that the text field is now hidden
|
||||||
|
* underneath the keyboard.
|
||||||
|
* This function re-runs scroll assist
|
||||||
|
* when that happens.
|
||||||
|
*
|
||||||
|
* One limitation of this is on a web browser
|
||||||
|
* where native keyboard APIs do not have cross-browser
|
||||||
|
* support. `ionKeyboardDidShow` relies on the Visual Viewport API.
|
||||||
|
* This means that if the keyboard changes but does not change
|
||||||
|
* geometry, then scroll assist will not re-run even if
|
||||||
|
* the user has scrolled the text field under the keyboard.
|
||||||
|
* This is not a problem when running in Cordova/Capacitor
|
||||||
|
* because `ionKeyboardDidShow` uses the native events
|
||||||
|
* which fire every time the keyboard changes.
|
||||||
|
*/
|
||||||
|
const keyboardShow = (ev: CustomEvent<{ keyboardHeight: number }>) => {
|
||||||
|
/**
|
||||||
|
* If the keyboard has not yet been presented
|
||||||
|
* for this text field then the text field has just
|
||||||
|
* received focus. In that case, the focusin listener
|
||||||
|
* will run scroll assist.
|
||||||
|
*/
|
||||||
|
if (hasKeyboardBeenPresentedForTextField === false) {
|
||||||
|
hasKeyboardBeenPresentedForTextField = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Otherwise, the keyboard has already been presented
|
||||||
|
* for the focused text field.
|
||||||
|
* This means that the keyboard likely changed
|
||||||
|
* geometry, and we need to re-run scroll assist.
|
||||||
|
* This can happen when the user rotates their device
|
||||||
|
* or when they switch keyboards.
|
||||||
|
*
|
||||||
|
* Make sure we pass in the computed keyboard height
|
||||||
|
* rather than the estimated keyboard height.
|
||||||
|
*
|
||||||
|
* Since the keyboard is already open then we do not
|
||||||
|
* need to wait for the webview to resize, so we pass
|
||||||
|
* "waitForResize: false".
|
||||||
|
*/
|
||||||
|
jsSetFocus(
|
||||||
|
componentEl,
|
||||||
|
inputEl,
|
||||||
|
contentEl,
|
||||||
|
footerEl,
|
||||||
|
ev.detail.keyboardHeight,
|
||||||
|
addScrollPadding,
|
||||||
|
disableClonedInput,
|
||||||
|
platformHeight,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the internal state when the text field loses focus.
|
||||||
|
*/
|
||||||
|
const focusOut = () => {
|
||||||
|
hasKeyboardBeenPresentedForTextField = false;
|
||||||
|
win?.removeEventListener('ionKeyboardDidShow', keyboardShow);
|
||||||
|
componentEl.removeEventListener('focusout', focusOut, true);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the input is about to receive
|
* When the input is about to receive
|
||||||
* focus, we need to move it to prevent
|
* focus, we need to move it to prevent
|
||||||
@ -76,11 +153,17 @@ export const enableScrollAssist = (
|
|||||||
disableClonedInput,
|
disableClonedInput,
|
||||||
platformHeight
|
platformHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
|
win?.addEventListener('ionKeyboardDidShow', keyboardShow);
|
||||||
|
componentEl.addEventListener('focusout', focusOut, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentEl.addEventListener('focusin', focusIn, true);
|
componentEl.addEventListener('focusin', focusIn, true);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
componentEl.removeEventListener('focusin', focusIn, true);
|
componentEl.removeEventListener('focusin', focusIn, true);
|
||||||
|
win?.removeEventListener('ionKeyboardDidShow', keyboardShow);
|
||||||
|
componentEl.removeEventListener('focusout', focusOut, true);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,7 +193,8 @@ const jsSetFocus = async (
|
|||||||
keyboardHeight: number,
|
keyboardHeight: number,
|
||||||
enableScrollPadding: boolean,
|
enableScrollPadding: boolean,
|
||||||
disableClonedInput = false,
|
disableClonedInput = false,
|
||||||
platformHeight = 0
|
platformHeight = 0,
|
||||||
|
waitForResize = true
|
||||||
) => {
|
) => {
|
||||||
if (!contentEl && !footerEl) {
|
if (!contentEl && !footerEl) {
|
||||||
return;
|
return;
|
||||||
@ -217,7 +301,7 @@ const jsSetFocus = async (
|
|||||||
* bandwidth to become available.
|
* bandwidth to become available.
|
||||||
*/
|
*/
|
||||||
const totalScrollAmount = scrollEl.scrollHeight - scrollEl.clientHeight;
|
const totalScrollAmount = scrollEl.scrollHeight - scrollEl.clientHeight;
|
||||||
if (scrollData.scrollAmount > totalScrollAmount - scrollEl.scrollTop) {
|
if (waitForResize && scrollData.scrollAmount > totalScrollAmount - scrollEl.scrollTop) {
|
||||||
/**
|
/**
|
||||||
* On iOS devices, the system will show a "Passwords" bar above the keyboard
|
* On iOS devices, the system will show a "Passwords" bar above the keyboard
|
||||||
* after the initial keyboard is shown. This prevents the webview from resizing
|
* after the initial keyboard is shown. This prevents the webview from resizing
|
||||||
|
Reference in New Issue
Block a user