fix(input): scroll assist works in with shadow-dom (#16206)

fixes #15888
fixes #15294
fixes #15895
This commit is contained in:
Manu MA
2018-11-03 01:35:38 +01:00
committed by GitHub
parent 0abf992a13
commit d817cc3b30
9 changed files with 47 additions and 100 deletions

View File

@ -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;
}

View File

@ -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>

View File

@ -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 = '';
}

View File

@ -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);
}

View File

@ -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(() => {

View File

@ -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;