mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
fix(input): scroll assist works in with shadow-dom (#16206)
fixes #15888 fixes #15294 fixes #15895
This commit is contained in:
@ -25,7 +25,7 @@
|
||||
--padding-bottom: 0;
|
||||
--padding-start: 0;
|
||||
--background: transparent;
|
||||
--color: inherit;
|
||||
--color: initial;
|
||||
|
||||
display: flex;
|
||||
position: relative;
|
||||
@ -108,17 +108,11 @@
|
||||
// This will only show when the scroll assist is configured
|
||||
// otherwise the .input-cover will not be rendered at all
|
||||
// The input cover is not clickable when the input is disabled
|
||||
|
||||
.input-cover {
|
||||
.cloned-input {
|
||||
@include position(0, null, null, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host([disabled]) .input-cover {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@ -151,10 +145,6 @@
|
||||
// --------------------------------------------------
|
||||
// When the input has focus, then the input cover should be hidden
|
||||
|
||||
:host(.has-focus) .input-cover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host(.has-focus) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@
|
||||
<ion-input clear-input value="reallylonglonglonginputtoseetheedgesreallylonglonglonginputtoseetheedges"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-item color="dark">
|
||||
<ion-label position="floating">Floating</ion-label>
|
||||
<ion-input checked></ion-input>
|
||||
</ion-item>
|
||||
|
@ -1,4 +1,4 @@
|
||||
const RELOCATED_KEY = '$ionRelocated';
|
||||
const cloneMap = new WeakMap<HTMLElement, HTMLElement>();
|
||||
|
||||
export function relocateInput(
|
||||
componentEl: HTMLElement,
|
||||
@ -6,11 +6,22 @@ export function relocateInput(
|
||||
shouldRelocate: boolean,
|
||||
inputRelativeY = 0
|
||||
) {
|
||||
if ((componentEl as any)[RELOCATED_KEY] === shouldRelocate) {
|
||||
if (cloneMap.has(componentEl) === shouldRelocate) {
|
||||
return;
|
||||
}
|
||||
console.debug(`native-input, hideCaret, shouldHideCaret: ${shouldRelocate}, input value: ${inputEl.value}`);
|
||||
if (shouldRelocate) {
|
||||
addClone(componentEl, inputEl, inputRelativeY);
|
||||
} else {
|
||||
removeClone(componentEl, inputEl);
|
||||
}
|
||||
}
|
||||
|
||||
export function isFocused(input: HTMLInputElement): boolean {
|
||||
return input === (input as any).getRootNode().activeElement;
|
||||
}
|
||||
|
||||
function addClone(componentEl: HTMLElement, inputEl: HTMLInputElement, inputRelativeY: number) {
|
||||
// this allows for the actual input to receive the focus from
|
||||
// the user's touch event, but before it receives focus, it
|
||||
// moves the actual input to a location that will not screw
|
||||
@ -19,76 +30,28 @@ export function relocateInput(
|
||||
// the cloned input fills the area of where native input should be
|
||||
// while the native input fakes out the browser by relocating itself
|
||||
// before it receives the actual focus event
|
||||
// We hide the focused input (with the visible caret) invisiable by making it scale(0),
|
||||
cloneInputComponent(componentEl, inputEl);
|
||||
// We hide the focused input (with the visible caret) invisible by making it scale(0),
|
||||
const parentEl = inputEl.parentNode!;
|
||||
|
||||
// DOM WRITES
|
||||
const clonedEl = inputEl.cloneNode(false) as HTMLInputElement;
|
||||
clonedEl.classList.add('cloned-input');
|
||||
clonedEl.tabIndex = -1;
|
||||
parentEl.appendChild(clonedEl);
|
||||
cloneMap.set(componentEl, clonedEl);
|
||||
|
||||
const doc = componentEl.ownerDocument!;
|
||||
const tx = doc.dir === 'rtl' ? 9999 : -9999;
|
||||
inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0)`;
|
||||
// TODO
|
||||
// inputEle.style.opacity = '0';
|
||||
} else {
|
||||
removeClone(componentEl, inputEl);
|
||||
}
|
||||
(componentEl as any)[RELOCATED_KEY] = shouldRelocate;
|
||||
}
|
||||
|
||||
export function isFocused(input: HTMLInputElement): boolean {
|
||||
return input === input.ownerDocument!.activeElement;
|
||||
componentEl.style.pointerEvents = 'none';
|
||||
inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0) scale(0)`;
|
||||
}
|
||||
|
||||
function removeClone(componentEl: HTMLElement, inputEl: HTMLElement) {
|
||||
if (componentEl && componentEl.parentElement) {
|
||||
Array.from(componentEl.parentElement.querySelectorAll('.cloned-input'))
|
||||
.forEach(clon => clon.remove());
|
||||
|
||||
const clone = cloneMap.get(componentEl);
|
||||
if (clone) {
|
||||
cloneMap.delete(componentEl);
|
||||
clone.remove();
|
||||
}
|
||||
componentEl.style.pointerEvents = '';
|
||||
}
|
||||
(inputEl.style as any)['transform'] = '';
|
||||
inputEl.style.opacity = '';
|
||||
}
|
||||
|
||||
function cloneInputComponent(componentEl: HTMLElement, inputEl: HTMLInputElement) {
|
||||
// Make sure we kill all the clones before creating new ones
|
||||
// It is a defensive, removeClone() should do nothing
|
||||
// removeClone(plt, srcComponentEle, srcNativeInputEle);
|
||||
// given a native <input> or <textarea> element
|
||||
// find its parent wrapping component like <ion-input> or <ion-textarea>
|
||||
// then clone the entire component
|
||||
const parentElement = componentEl.parentElement;
|
||||
const doc = componentEl.ownerDocument!;
|
||||
if (componentEl && parentElement) {
|
||||
// DOM READ
|
||||
const srcTop = componentEl.offsetTop;
|
||||
const srcLeft = componentEl.offsetLeft;
|
||||
const srcWidth = componentEl.offsetWidth;
|
||||
const srcHeight = componentEl.offsetHeight;
|
||||
|
||||
// DOM WRITE
|
||||
// not using deep clone so we don't pull in unnecessary nodes
|
||||
const clonedComponentEle = doc.createElement('div');
|
||||
const clonedStyle = clonedComponentEle.style;
|
||||
clonedComponentEle.classList.add(...Array.from(componentEl.classList));
|
||||
clonedComponentEle.classList.add('cloned-input');
|
||||
clonedComponentEle.setAttribute('aria-hidden', 'true');
|
||||
clonedStyle.pointerEvents = 'none';
|
||||
clonedStyle.position = 'absolute';
|
||||
clonedStyle.top = srcTop + 'px';
|
||||
clonedStyle.left = srcLeft + 'px';
|
||||
clonedStyle.width = srcWidth + 'px';
|
||||
clonedStyle.height = srcHeight + 'px';
|
||||
|
||||
const clonedInputEl = doc.createElement('input');
|
||||
clonedInputEl.classList.add(...Array.from(inputEl.classList));
|
||||
clonedInputEl.value = inputEl.value;
|
||||
clonedInputEl.type = inputEl.type;
|
||||
clonedInputEl.placeholder = inputEl.placeholder;
|
||||
|
||||
clonedInputEl.tabIndex = -1;
|
||||
|
||||
clonedComponentEle.appendChild(clonedInputEl);
|
||||
parentElement.appendChild(clonedComponentEle);
|
||||
|
||||
componentEl.style.pointerEvents = 'none';
|
||||
}
|
||||
inputEl.style.transform = 'scale(0)';
|
||||
inputEl.style.transform = '';
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ export function enableHideCaretOnScroll(componentEl: HTMLElement, inputEl: HTMLI
|
||||
console.debug('Input: enableHideCaretOnScroll');
|
||||
|
||||
const scrollHideCaret = (shouldHideCaret: boolean) => {
|
||||
// console.log('scrollHideCaret', shouldHideCaret)
|
||||
if (isFocused(inputEl)) {
|
||||
relocateInput(componentEl, inputEl, shouldHideCaret);
|
||||
}
|
||||
|
@ -40,11 +40,6 @@ export function enableInputBlurring(doc: Document) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip if div is a cover
|
||||
if (tapped.classList.contains('input-cover')) {
|
||||
return;
|
||||
}
|
||||
|
||||
focused = false;
|
||||
// TODO: find a better way, why 50ms?
|
||||
setTimeout(() => {
|
||||
|
@ -22,7 +22,7 @@ function calcScrollData(
|
||||
inputRect: ClientRect,
|
||||
contentRect: ClientRect,
|
||||
keyboardHeight: number,
|
||||
plaformHeight: number
|
||||
platformHeight: number
|
||||
): ScrollData {
|
||||
// compute input's Y values relative to the body
|
||||
const inputTop = inputRect.top;
|
||||
@ -30,13 +30,13 @@ function calcScrollData(
|
||||
|
||||
// compute visible area
|
||||
const visibleAreaTop = contentRect.top;
|
||||
const visibleAreaBottom = Math.min(contentRect.bottom, plaformHeight - keyboardHeight);
|
||||
const visibleAreaBottom = Math.min(contentRect.bottom, platformHeight - keyboardHeight);
|
||||
|
||||
// compute safe area
|
||||
const safeAreaTop = visibleAreaTop + 10;
|
||||
const safeAreaBottom = visibleAreaBottom / 2.0;
|
||||
const safeAreaTop = visibleAreaTop + 15;
|
||||
const safeAreaBottom = visibleAreaBottom * 0.5;
|
||||
|
||||
// figure out if each edge of teh input is within the safe area
|
||||
// figure out if each edge of the input is within the safe area
|
||||
const distanceToBottom = safeAreaBottom - inputBottom;
|
||||
const distanceToTop = safeAreaTop - inputTop;
|
||||
|
||||
|
Reference in New Issue
Block a user