mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
fix(android): account for chrome 108 resize (#26244)
This commit is contained in:
@ -25,7 +25,12 @@ export class App implements ComponentInterface {
|
|||||||
import('../../utils/status-tap').then((module) => module.startStatusTap());
|
import('../../utils/status-tap').then((module) => module.startStatusTap());
|
||||||
}
|
}
|
||||||
if (config.getBoolean('inputShims', needInputShims())) {
|
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');
|
const hardwareBackButtonModule = await import('../../utils/hardware-back-button');
|
||||||
if (config.getBoolean('hardwareBackButton', isHybrid)) {
|
if (config.getBoolean('hardwareBackButton', isHybrid)) {
|
||||||
@ -73,7 +78,25 @@ export class App implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const needInputShims = () => {
|
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) => {
|
const rIC = (callback: () => void) => {
|
||||||
|
|||||||
@ -105,12 +105,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.native-input[disabled] {
|
.native-input[disabled]:not(.cloned-input) {
|
||||||
opacity: .4;
|
opacity: .4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Input Cover: Unfocused
|
// Input Cover: Unfocused
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
// The input cover is the div that actually receives the
|
// The input cover is the div that actually receives the
|
||||||
@ -127,6 +125,15 @@
|
|||||||
pointer-events: none;
|
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
|
// Clear Input Icon
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|||||||
@ -139,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.native-textarea[disabled] {
|
.native-textarea[disabled]:not(.cloned-input) {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +159,15 @@
|
|||||||
pointer-events: none;
|
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 {
|
:host([auto-grow]) .cloned-input {
|
||||||
// Workaround for webkit rendering issue with scroll assist.
|
// Workaround for webkit rendering issue with scroll assist.
|
||||||
// When cloning the textarea and scrolling into view,
|
// When cloning the textarea and scrolling into view,
|
||||||
|
|||||||
@ -4,14 +4,15 @@ export const relocateInput = (
|
|||||||
componentEl: HTMLElement,
|
componentEl: HTMLElement,
|
||||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||||
shouldRelocate: boolean,
|
shouldRelocate: boolean,
|
||||||
inputRelativeY = 0
|
inputRelativeY = 0,
|
||||||
|
disabledClonedInput = false
|
||||||
) => {
|
) => {
|
||||||
if (cloneMap.has(componentEl) === shouldRelocate) {
|
if (cloneMap.has(componentEl) === shouldRelocate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldRelocate) {
|
if (shouldRelocate) {
|
||||||
addClone(componentEl, inputEl, inputRelativeY);
|
addClone(componentEl, inputEl, inputRelativeY, disabledClonedInput);
|
||||||
} else {
|
} else {
|
||||||
removeClone(componentEl, inputEl);
|
removeClone(componentEl, inputEl);
|
||||||
}
|
}
|
||||||
@ -24,7 +25,8 @@ export const isFocused = (input: HTMLInputElement | HTMLTextAreaElement): boolea
|
|||||||
const addClone = (
|
const addClone = (
|
||||||
componentEl: HTMLElement,
|
componentEl: HTMLElement,
|
||||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||||
inputRelativeY: number
|
inputRelativeY: number,
|
||||||
|
disabledClonedInput = false
|
||||||
) => {
|
) => {
|
||||||
// this allows for the actual input to receive the focus from
|
// this allows for the actual input to receive the focus from
|
||||||
// the user's touch event, but before it receives focus, it
|
// the user's touch event, but before it receives focus, it
|
||||||
@ -38,9 +40,25 @@ const addClone = (
|
|||||||
const parentEl = inputEl.parentNode!;
|
const parentEl = inputEl.parentNode!;
|
||||||
|
|
||||||
// DOM WRITES
|
// DOM WRITES
|
||||||
const clonedEl = inputEl.cloneNode(false) as HTMLElement;
|
const clonedEl = inputEl.cloneNode(false) as HTMLInputElement | HTMLTextAreaElement;
|
||||||
clonedEl.classList.add('cloned-input');
|
clonedEl.classList.add('cloned-input');
|
||||||
clonedEl.tabIndex = -1;
|
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);
|
parentEl.appendChild(clonedEl);
|
||||||
cloneMap.set(componentEl, clonedEl);
|
cloneMap.set(componentEl, clonedEl);
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,8 @@ export const enableScrollAssist = (
|
|||||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||||
contentEl: HTMLElement | null,
|
contentEl: HTMLElement | null,
|
||||||
footerEl: HTMLIonFooterElement | null,
|
footerEl: HTMLIonFooterElement | null,
|
||||||
keyboardHeight: number
|
keyboardHeight: number,
|
||||||
|
disableClonedInput = false
|
||||||
) => {
|
) => {
|
||||||
let coord: any;
|
let coord: any;
|
||||||
const touchStart = (ev: Event) => {
|
const touchStart = (ev: Event) => {
|
||||||
@ -28,7 +29,7 @@ export const enableScrollAssist = (
|
|||||||
// and the input doesn't already have focus
|
// and the input doesn't already have focus
|
||||||
if (!hasPointerMoved(6, coord, endCoord) && !isFocused(inputEl)) {
|
if (!hasPointerMoved(6, coord, endCoord) && !isFocused(inputEl)) {
|
||||||
// begin the input focus process
|
// 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 });
|
componentEl.addEventListener('touchstart', touchStart, { capture: true, passive: true });
|
||||||
@ -45,7 +46,8 @@ const jsSetFocus = async (
|
|||||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||||
contentEl: HTMLElement | null,
|
contentEl: HTMLElement | null,
|
||||||
footerEl: HTMLIonFooterElement | null,
|
footerEl: HTMLIonFooterElement | null,
|
||||||
keyboardHeight: number
|
keyboardHeight: number,
|
||||||
|
disableClonedInput = false
|
||||||
) => {
|
) => {
|
||||||
if (!contentEl && !footerEl) {
|
if (!contentEl && !footerEl) {
|
||||||
return;
|
return;
|
||||||
@ -62,7 +64,7 @@ const jsSetFocus = async (
|
|||||||
// temporarily move the focus to the focus holder so the browser
|
// 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
|
// 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
|
// 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();
|
inputEl.focus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -12,12 +12,20 @@ const SCROLL_ASSIST = true;
|
|||||||
const SCROLL_PADDING = true;
|
const SCROLL_PADDING = true;
|
||||||
const HIDE_CARET = true;
|
const HIDE_CARET = true;
|
||||||
|
|
||||||
export const startInputShims = (config: Config) => {
|
export const startInputShims = (config: Config, platform: 'ios' | 'android') => {
|
||||||
const doc = document;
|
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 keyboardHeight = config.getNumber('keyboardHeight', 290);
|
||||||
const scrollAssist = config.getBoolean('scrollAssist', true);
|
const scrollAssist = config.getBoolean('scrollAssist', true);
|
||||||
const hideCaret = config.getBoolean('hideCaretOnScroll', true);
|
const hideCaret = config.getBoolean('hideCaretOnScroll', isIOS);
|
||||||
const inputBlurring = config.getBoolean('inputBlurring', true);
|
const inputBlurring = config.getBoolean('inputBlurring', isIOS);
|
||||||
const scrollPadding = config.getBoolean('scrollPadding', true);
|
const scrollPadding = config.getBoolean('scrollPadding', true);
|
||||||
const inputs = Array.from(doc.querySelectorAll('ion-input, ion-textarea')) as HTMLElement[];
|
const inputs = Array.from(doc.querySelectorAll('ion-input, ion-textarea')) as HTMLElement[];
|
||||||
|
|
||||||
@ -55,7 +63,7 @@ export const startInputShims = (config: Config) => {
|
|||||||
scrollAssist &&
|
scrollAssist &&
|
||||||
!scrollAssistMap.has(componentEl)
|
!scrollAssistMap.has(componentEl)
|
||||||
) {
|
) {
|
||||||
const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight);
|
const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight, isAndroid);
|
||||||
scrollAssistMap.set(componentEl, rmFn);
|
scrollAssistMap.set(componentEl, rmFn);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user