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:
4
core/src/components.d.ts
vendored
4
core/src/components.d.ts
vendored
@ -1700,7 +1700,7 @@ export namespace Components {
|
||||
*/
|
||||
'autocomplete': 'on' | 'off';
|
||||
/**
|
||||
* Whether autocorrection should be enabled when the user is entering/editing the text value.
|
||||
* Whether auto correction should be enabled when the user is entering/editing the text value.
|
||||
*/
|
||||
'autocorrect': 'on' | 'off';
|
||||
/**
|
||||
@ -1818,7 +1818,7 @@ export namespace Components {
|
||||
*/
|
||||
'autocomplete'?: 'on' | 'off';
|
||||
/**
|
||||
* Whether autocorrection should be enabled when the user is entering/editing the text value.
|
||||
* Whether auto correction should be enabled when the user is entering/editing the text value.
|
||||
*/
|
||||
'autocorrect'?: 'on' | 'off';
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export class Input implements ComponentInterface {
|
||||
@Prop() autocomplete: 'on' | 'off' = 'off';
|
||||
|
||||
/**
|
||||
* Whether autocorrection should be enabled when the user is entering/editing the text value.
|
||||
* Whether auto correction should be enabled when the user is entering/editing the text value.
|
||||
*/
|
||||
@Prop() autocorrect: 'on' | 'off' = 'off';
|
||||
|
||||
|
@ -15,7 +15,7 @@ It is meant for text `type` inputs only, such as `"text"`, `"password"`, `"email
|
||||
| `accept` | `accept` | If the value of the type attribute is `"file"`, then this attribute will indicate the types of files that the server accepts, otherwise it will be ignored. The value must be a comma-separated list of unique content type specifiers. | `string \| undefined` | `undefined` |
|
||||
| `autocapitalize` | `autocapitalize` | Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. | `"characters" \| "off" \| "on" \| "words"` | `'off'` |
|
||||
| `autocomplete` | `autocomplete` | Indicates whether the value of the control can be automatically completed by the browser. | `"off" \| "on"` | `'off'` |
|
||||
| `autocorrect` | `autocorrect` | Whether autocorrection should be enabled when the user is entering/editing the text value. | `"off" \| "on"` | `'off'` |
|
||||
| `autocorrect` | `autocorrect` | Whether auto correction should be enabled when the user is entering/editing the text value. | `"off" \| "on"` | `'off'` |
|
||||
| `autofocus` | `autofocus` | This Boolean attribute lets you specify that a form control should have input focus when the page loads. | `boolean` | `false` |
|
||||
| `clearInput` | `clear-input` | If `true`, a clear icon will appear in the input when there is a value. Clicking it clears the input. | `boolean` | `false` |
|
||||
| `clearOnEdit` | `clear-on-edit` | If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types. | `boolean \| undefined` | `undefined` |
|
||||
|
@ -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,89 +6,52 @@ 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) {
|
||||
// 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
|
||||
// up the app's layout, and does not allow the native browser
|
||||
// to attempt to scroll the input into place (messing up headers/footers)
|
||||
// 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);
|
||||
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';
|
||||
addClone(componentEl, inputEl, inputRelativeY);
|
||||
} else {
|
||||
removeClone(componentEl, inputEl);
|
||||
}
|
||||
(componentEl as any)[RELOCATED_KEY] = shouldRelocate;
|
||||
}
|
||||
|
||||
export function isFocused(input: HTMLInputElement): boolean {
|
||||
return input === input.ownerDocument!.activeElement;
|
||||
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
|
||||
// up the app's layout, and does not allow the native browser
|
||||
// to attempt to scroll the input into place (messing up headers/footers)
|
||||
// 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) 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;
|
||||
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());
|
||||
|
||||
componentEl.style.pointerEvents = '';
|
||||
const clone = cloneMap.get(componentEl);
|
||||
if (clone) {
|
||||
cloneMap.delete(componentEl);
|
||||
clone.remove();
|
||||
}
|
||||
(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)';
|
||||
componentEl.style.pointerEvents = '';
|
||||
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