diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index 2ae7b96f27..c49083c4f5 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -25,7 +25,12 @@ export class App implements ComponentInterface { import('../../utils/status-tap').then((module) => module.startStatusTap()); } if (config.getBoolean('inputShims', needInputShims())) { - import('../../utils/input-shims/input-shims').then((module) => module.startInputShims(config)); + /** + * needInputShims() ensures that only iOS and Android + * platforms proceed into this block. + */ + const platform = isPlatform(window, 'ios') ? 'ios' : 'android'; + import('../../utils/input-shims/input-shims').then((module) => module.startInputShims(config, platform)); } const hardwareBackButtonModule = await import('../../utils/hardware-back-button'); if (config.getBoolean('hardwareBackButton', isHybrid)) { @@ -73,7 +78,25 @@ export class App implements ComponentInterface { } const needInputShims = () => { - return isPlatform(window, 'ios') && isPlatform(window, 'mobile'); + /** + * iOS always needs input shims + */ + const needsShimsIOS = isPlatform(window, 'ios') && isPlatform(window, 'mobile'); + if (needsShimsIOS) { + return true; + } + + /** + * Android only needs input shims when running + * in the browser and only if the browser is using the + * new Chrome 108+ resize behavior: https://developer.chrome.com/blog/viewport-resize-behavior/ + */ + const isAndroidMobileWeb = isPlatform(window, 'android') && isPlatform(window, 'mobileweb'); + if (isAndroidMobileWeb) { + return true; + } + + return false; }; const rIC = (callback: () => void) => { diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index b08914f1b1..bc9363b921 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -105,12 +105,10 @@ } } -.native-input[disabled] { +.native-input[disabled]:not(.cloned-input) { opacity: .4; } - - // Input Cover: Unfocused // -------------------------------------------------- // The input cover is the div that actually receives the @@ -127,6 +125,15 @@ pointer-events: none; } +/** + * The cloned input needs to be disabled on + * Android otherwise the viewport will still + * shift when running scroll assist. + */ +.cloned-input:disabled { + opacity: 1; +} + // Clear Input Icon // -------------------------------------------------- diff --git a/core/src/components/textarea/textarea.scss b/core/src/components/textarea/textarea.scss index 45c9f481cc..0b9a29a4fc 100644 --- a/core/src/components/textarea/textarea.scss +++ b/core/src/components/textarea/textarea.scss @@ -139,7 +139,7 @@ } } -.native-textarea[disabled] { +.native-textarea[disabled]:not(.cloned-input) { opacity: 0.4; } @@ -159,13 +159,22 @@ pointer-events: none; } +/** + * The cloned input needs to be disabled on + * Android otherwise the viewport will still + * shift when running scroll assist. + */ +.cloned-input:disabled { + opacity: 1; +} + :host([auto-grow]) .cloned-input { // Workaround for webkit rendering issue with scroll assist. // When cloning the textarea and scrolling into view, // a white box is rendered from the difference in height // from the auto grow container. // This change forces the cloned input to match the true - // height of the textarea container. + // height of the textarea container. height: 100%; } diff --git a/core/src/utils/input-shims/hacks/common.ts b/core/src/utils/input-shims/hacks/common.ts index 0ab0463de4..8da035ff98 100644 --- a/core/src/utils/input-shims/hacks/common.ts +++ b/core/src/utils/input-shims/hacks/common.ts @@ -4,14 +4,15 @@ export const relocateInput = ( componentEl: HTMLElement, inputEl: HTMLInputElement | HTMLTextAreaElement, shouldRelocate: boolean, - inputRelativeY = 0 + inputRelativeY = 0, + disabledClonedInput = false ) => { if (cloneMap.has(componentEl) === shouldRelocate) { return; } if (shouldRelocate) { - addClone(componentEl, inputEl, inputRelativeY); + addClone(componentEl, inputEl, inputRelativeY, disabledClonedInput); } else { removeClone(componentEl, inputEl); } @@ -24,7 +25,8 @@ export const isFocused = (input: HTMLInputElement | HTMLTextAreaElement): boolea const addClone = ( componentEl: HTMLElement, inputEl: HTMLInputElement | HTMLTextAreaElement, - inputRelativeY: number + inputRelativeY: number, + disabledClonedInput = false ) => { // this allows for the actual input to receive the focus from // the user's touch event, but before it receives focus, it @@ -38,9 +40,25 @@ const addClone = ( const parentEl = inputEl.parentNode!; // DOM WRITES - const clonedEl = inputEl.cloneNode(false) as HTMLElement; + const clonedEl = inputEl.cloneNode(false) as HTMLInputElement | HTMLTextAreaElement; clonedEl.classList.add('cloned-input'); clonedEl.tabIndex = -1; + + /** + * Making the cloned input disabled prevents + * Chrome for Android from still scrolling + * the entire page since this cloned input + * will briefly be hidden by the keyboard + * even though it is not focused. + * + * This is not needed on iOS. While this + * does not cause functional issues on iOS, + * the input still appears slightly dimmed even + * if we set opacity: 1. + */ + if (disabledClonedInput) { + clonedEl.disabled = true; + } parentEl.appendChild(clonedEl); cloneMap.set(componentEl, clonedEl); diff --git a/core/src/utils/input-shims/hacks/scroll-assist.ts b/core/src/utils/input-shims/hacks/scroll-assist.ts index b58cb76585..a163cf75a8 100644 --- a/core/src/utils/input-shims/hacks/scroll-assist.ts +++ b/core/src/utils/input-shims/hacks/scroll-assist.ts @@ -9,7 +9,8 @@ export const enableScrollAssist = ( inputEl: HTMLInputElement | HTMLTextAreaElement, contentEl: HTMLElement | null, footerEl: HTMLIonFooterElement | null, - keyboardHeight: number + keyboardHeight: number, + disableClonedInput = false ) => { let coord: any; const touchStart = (ev: Event) => { @@ -28,7 +29,7 @@ export const enableScrollAssist = ( // and the input doesn't already have focus if (!hasPointerMoved(6, coord, endCoord) && !isFocused(inputEl)) { // begin the input focus process - jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight); + jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight, disableClonedInput); } }; componentEl.addEventListener('touchstart', touchStart, { capture: true, passive: true }); @@ -45,7 +46,8 @@ const jsSetFocus = async ( inputEl: HTMLInputElement | HTMLTextAreaElement, contentEl: HTMLElement | null, footerEl: HTMLIonFooterElement | null, - keyboardHeight: number + keyboardHeight: number, + disableClonedInput = false ) => { if (!contentEl && !footerEl) { return; @@ -62,7 +64,7 @@ const jsSetFocus = async ( // temporarily move the focus to the focus holder so the browser // doesn't freak out while it's trying to get the input in place // at this point the native text input still does not have focus - relocateInput(componentEl, inputEl, true, scrollData.inputSafeY); + relocateInput(componentEl, inputEl, true, scrollData.inputSafeY, disableClonedInput); inputEl.focus(); /** diff --git a/core/src/utils/input-shims/input-shims.ts b/core/src/utils/input-shims/input-shims.ts index a83f4776fd..e8c44a7786 100644 --- a/core/src/utils/input-shims/input-shims.ts +++ b/core/src/utils/input-shims/input-shims.ts @@ -12,12 +12,20 @@ const SCROLL_ASSIST = true; const SCROLL_PADDING = true; const HIDE_CARET = true; -export const startInputShims = (config: Config) => { +export const startInputShims = (config: Config, platform: 'ios' | 'android') => { const doc = document; + const isIOS = platform === 'ios'; + const isAndroid = platform === 'android'; + + /** + * Hide Caret and Input Blurring are needed on iOS. + * Scroll Assist and Scroll Padding are needed on iOS and Android + * with Chrome web browser (not Chrome webview). + */ const keyboardHeight = config.getNumber('keyboardHeight', 290); const scrollAssist = config.getBoolean('scrollAssist', true); - const hideCaret = config.getBoolean('hideCaretOnScroll', true); - const inputBlurring = config.getBoolean('inputBlurring', true); + const hideCaret = config.getBoolean('hideCaretOnScroll', isIOS); + const inputBlurring = config.getBoolean('inputBlurring', isIOS); const scrollPadding = config.getBoolean('scrollPadding', true); const inputs = Array.from(doc.querySelectorAll('ion-input, ion-textarea')) as HTMLElement[]; @@ -55,7 +63,7 @@ export const startInputShims = (config: Config) => { scrollAssist && !scrollAssistMap.has(componentEl) ) { - const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight); + const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight, isAndroid); scrollAssistMap.set(componentEl, rmFn); } };