mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 04:53:58 +08:00
@ -4,8 +4,8 @@ import { NgControl } from '@angular/forms';
|
|||||||
import { App } from '../app/app';
|
import { App } from '../app/app';
|
||||||
import { copyInputAttributes, PointerCoordinates, hasPointerMoved, pointerCoord } from '../../util/dom';
|
import { copyInputAttributes, PointerCoordinates, hasPointerMoved, pointerCoord } from '../../util/dom';
|
||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
import { Content } from '../content/content';
|
import { Content, ContentDimensions } from '../content/content';
|
||||||
import { Form } from '../../util/form';
|
import { Form, IonicFormInput } from '../../util/form';
|
||||||
import { Ion } from '../ion';
|
import { Ion } from '../ion';
|
||||||
import { isTrueProperty } from '../../util/util';
|
import { isTrueProperty } from '../../util/util';
|
||||||
import { Item } from '../item/item';
|
import { Item } from '../item/item';
|
||||||
@ -15,7 +15,11 @@ import { NavControllerBase } from '../../navigation/nav-controller-base';
|
|||||||
import { Platform } from '../../platform/platform';
|
import { Platform } from '../../platform/platform';
|
||||||
|
|
||||||
|
|
||||||
export class InputBase extends Ion {
|
/**
|
||||||
|
* @private
|
||||||
|
* Hopefully someday a majority of the auto-scrolling tricks can get removed.
|
||||||
|
*/
|
||||||
|
export class InputBase extends Ion implements IonicFormInput {
|
||||||
_coord: PointerCoordinates;
|
_coord: PointerCoordinates;
|
||||||
_deregScroll: Function;
|
_deregScroll: Function;
|
||||||
_disabled: boolean = false;
|
_disabled: boolean = false;
|
||||||
@ -72,6 +76,8 @@ export class InputBase extends Ion {
|
|||||||
|
|
||||||
scrollMove(ev: UIEvent) {
|
scrollMove(ev: UIEvent) {
|
||||||
// scroll move event listener this instance can reuse
|
// scroll move event listener this instance can reuse
|
||||||
|
console.debug(`input-base, scrollMove`);
|
||||||
|
|
||||||
if (!(this._nav && this._nav.isTransitioning())) {
|
if (!(this._nav && this._nav.isTransitioning())) {
|
||||||
this.deregScrollMove();
|
this.deregScrollMove();
|
||||||
|
|
||||||
@ -313,11 +319,11 @@ export class InputBase extends Ion {
|
|||||||
*/
|
*/
|
||||||
focusChange(inputHasFocus: boolean) {
|
focusChange(inputHasFocus: boolean) {
|
||||||
if (this._item) {
|
if (this._item) {
|
||||||
|
console.debug(`input-base, focusChange, inputHasFocus: ${inputHasFocus}, ${this._item.getNativeElement().nodeName}.${this._item.getNativeElement().className}`);
|
||||||
this._item.setElementClass('input-has-focus', inputHasFocus);
|
this._item.setElementClass('input-has-focus', inputHasFocus);
|
||||||
}
|
}
|
||||||
if (!inputHasFocus) {
|
if (!inputHasFocus) {
|
||||||
this.deregScrollMove();
|
this.deregScrollMove();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If clearOnEdit is enabled and the input blurred but has a value, set a flag
|
// If clearOnEdit is enabled and the input blurred but has a value, set a flag
|
||||||
@ -328,8 +334,6 @@ export class InputBase extends Ion {
|
|||||||
|
|
||||||
pointerStart(ev: any) {
|
pointerStart(ev: any) {
|
||||||
// input cover touchstart
|
// input cover touchstart
|
||||||
console.debug('scroll assist pointerStart', ev.type);
|
|
||||||
|
|
||||||
if (ev.type === 'touchstart') {
|
if (ev.type === 'touchstart') {
|
||||||
this._isTouch = true;
|
this._isTouch = true;
|
||||||
}
|
}
|
||||||
@ -338,11 +342,13 @@ export class InputBase extends Ion {
|
|||||||
// remember where the touchstart/mousedown started
|
// remember where the touchstart/mousedown started
|
||||||
this._coord = pointerCoord(ev);
|
this._coord = pointerCoord(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.debug(`input-base, pointerStart, type: ${ev.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
pointerEnd(ev: any) {
|
pointerEnd(ev: any) {
|
||||||
// input cover touchend/mouseup
|
// input cover touchend/mouseup
|
||||||
console.debug('scroll assist pointerEnd', ev.type);
|
console.debug(`input-base, pointerEnd, type: ${ev.type}`);
|
||||||
|
|
||||||
if ((this._isTouch && ev.type === 'mouseup') || !this._app.isEnabled()) {
|
if ((this._isTouch && ev.type === 'mouseup') || !this._app.isEnabled()) {
|
||||||
// the app is actively doing something right now
|
// the app is actively doing something right now
|
||||||
@ -361,10 +367,8 @@ export class InputBase extends Ion {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
// begin the input focus process
|
// begin the input focus process
|
||||||
console.debug('initFocus', ev.type);
|
|
||||||
this.initFocus();
|
this.initFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._coord = null;
|
this._coord = null;
|
||||||
@ -375,22 +379,32 @@ export class InputBase extends Ion {
|
|||||||
*/
|
*/
|
||||||
initFocus() {
|
initFocus() {
|
||||||
// begin the process of setting focus to the inner input element
|
// begin the process of setting focus to the inner input element
|
||||||
var scrollView = this._scrollView;
|
const scrollView = this._scrollView;
|
||||||
|
|
||||||
|
console.debug(`input-base, initFocus(), scrollView: ${!!scrollView}`);
|
||||||
|
|
||||||
if (scrollView) {
|
if (scrollView) {
|
||||||
// this input is inside of a scroll view
|
// this input is inside of a scroll view
|
||||||
// find out if text input should be manually scrolled into view
|
// find out if text input should be manually scrolled into view
|
||||||
|
|
||||||
// get container of this input, probably an ion-item a few nodes up
|
// get container of this input, probably an ion-item a few nodes up
|
||||||
var ele: HTMLElement = this._elementRef.nativeElement;
|
let ele: HTMLElement = this._elementRef.nativeElement;
|
||||||
ele = <HTMLElement>ele.closest('ion-item,[ion-item]') || ele;
|
ele = <HTMLElement>ele.closest('ion-item,[ion-item]') || ele;
|
||||||
|
|
||||||
var scrollData = InputBase.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getContentDimensions(), this._keyboardHeight, this._platform.height());
|
const scrollData = getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getContentDimensions(), this._keyboardHeight, this._platform.height());
|
||||||
if (scrollData.scrollAmount > -3 && scrollData.scrollAmount < 3) {
|
if (Math.abs(scrollData.scrollAmount) < 4) {
|
||||||
// the text input is in a safe position that doesn't
|
// the text input is in a safe position that doesn't
|
||||||
// require it to be scrolled into view, just set focus now
|
// require it to be scrolled into view, just set focus now
|
||||||
this.setFocus();
|
this.setFocus();
|
||||||
|
|
||||||
|
// all good, allow clicks again
|
||||||
|
this._app.setEnabled(true);
|
||||||
|
this._nav && this._nav.setTransitioning(false);
|
||||||
this.regScrollMove();
|
this.regScrollMove();
|
||||||
|
|
||||||
|
if (this._usePadding) {
|
||||||
|
this._scrollView.clearScrollPaddingFocusOut();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +415,7 @@ export class InputBase extends Ion {
|
|||||||
|
|
||||||
// manually scroll the text input to the top
|
// manually scroll the text input to the top
|
||||||
// do not allow any clicks while it's scrolling
|
// do not allow any clicks while it's scrolling
|
||||||
var scrollDuration = getScrollAssistDuration(scrollData.scrollAmount);
|
const scrollDuration = getScrollAssistDuration(scrollData.scrollAmount);
|
||||||
this._app.setEnabled(false, scrollDuration);
|
this._app.setEnabled(false, scrollDuration);
|
||||||
this._nav && this._nav.setTransitioning(true);
|
this._nav && this._nav.setTransitioning(true);
|
||||||
|
|
||||||
@ -411,7 +425,8 @@ export class InputBase extends Ion {
|
|||||||
this._native.beginFocus(true, scrollData.inputSafeY);
|
this._native.beginFocus(true, scrollData.inputSafeY);
|
||||||
|
|
||||||
// scroll the input into place
|
// scroll the input into place
|
||||||
scrollView.scrollTo(0, scrollData.scrollTo, scrollDuration).then(() => {
|
scrollView.scrollTo(0, scrollData.scrollTo, scrollDuration, () => {
|
||||||
|
console.debug(`input-base, scrollTo completed, scrollTo: ${scrollData.scrollTo}, scrollDuration: ${scrollDuration}`);
|
||||||
// the scroll view is in the correct position now
|
// the scroll view is in the correct position now
|
||||||
// give the native text input focus
|
// give the native text input focus
|
||||||
this._native.beginFocus(false, 0);
|
this._native.beginFocus(false, 0);
|
||||||
@ -491,21 +506,24 @@ export class InputBase extends Ion {
|
|||||||
focusNext() {
|
focusNext() {
|
||||||
this._form.tabFocus(this);
|
this._form.tabFocus(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
static getScrollData(inputOffsetTop: number, inputOffsetHeight: number, scrollViewDimensions: any, keyboardHeight: number, plaformHeight: number) {
|
export function getScrollData(inputOffsetTop: number, inputOffsetHeight: number, scrollViewDimensions: ContentDimensions, keyboardHeight: number, plaformHeight: number) {
|
||||||
// compute input's Y values relative to the body
|
// compute input's Y values relative to the body
|
||||||
let inputTop = (inputOffsetTop + scrollViewDimensions.contentTop - scrollViewDimensions.scrollTop);
|
let inputTop = (inputOffsetTop + scrollViewDimensions.contentTop - scrollViewDimensions.scrollTop);
|
||||||
let inputBottom = (inputTop + inputOffsetHeight);
|
let inputBottom = (inputTop + inputOffsetHeight);
|
||||||
|
|
||||||
// compute the safe area which is the viewable content area when the soft keyboard is up
|
// compute the safe area which is the viewable content area when the soft keyboard is up
|
||||||
let safeAreaTop = scrollViewDimensions.contentTop;
|
let safeAreaTop = scrollViewDimensions.contentTop;
|
||||||
let safeAreaHeight = plaformHeight - keyboardHeight - safeAreaTop;
|
let safeAreaHeight = (plaformHeight - keyboardHeight - safeAreaTop) / 2;
|
||||||
safeAreaHeight /= 2;
|
|
||||||
let safeAreaBottom = safeAreaTop + safeAreaHeight;
|
let safeAreaBottom = safeAreaTop + safeAreaHeight;
|
||||||
|
|
||||||
|
// figure out if each edge of teh input is within the safe area
|
||||||
let inputTopWithinSafeArea = (inputTop >= safeAreaTop && inputTop <= safeAreaBottom);
|
let inputTopWithinSafeArea = (inputTop >= safeAreaTop && inputTop <= safeAreaBottom);
|
||||||
let inputTopAboveSafeArea = (inputTop < safeAreaTop);
|
let inputTopAboveSafeArea = (inputTop < safeAreaTop);
|
||||||
let inputTopBelowSafeArea = (inputTop > safeAreaBottom);
|
let inputTopBelowSafeArea = (inputTop > safeAreaBottom);
|
||||||
@ -524,7 +542,7 @@ export class InputBase extends Ion {
|
|||||||
7) Input top below safe area, no room to scroll, input larger than safe area
|
7) Input top below safe area, no room to scroll, input larger than safe area
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let scrollData = {
|
const scrollData: ScrollData = {
|
||||||
scrollAmount: 0,
|
scrollAmount: 0,
|
||||||
scrollTo: 0,
|
scrollTo: 0,
|
||||||
scrollPadding: 0,
|
scrollPadding: 0,
|
||||||
@ -538,13 +556,13 @@ export class InputBase extends Ion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// looks like we'll have to do some auto-scrolling
|
// looks like we'll have to do some auto-scrolling
|
||||||
if (inputTopBelowSafeArea || inputBottomBelowSafeArea) {
|
if (inputTopBelowSafeArea || inputBottomBelowSafeArea || inputTopAboveSafeArea) {
|
||||||
// Input top and bottom below safe area
|
// Input top or bottom below safe area
|
||||||
// auto scroll the input up so at least the top of it shows
|
// auto scroll the input up so at least the top of it shows
|
||||||
|
|
||||||
if (safeAreaHeight > inputOffsetHeight) {
|
if (safeAreaHeight > inputOffsetHeight) {
|
||||||
// safe area height is taller than the input height, so we
|
// safe area height is taller than the input height, so we
|
||||||
// can bring it up the input just enough to show the input bottom
|
// can bring up the input just enough to show the input bottom
|
||||||
scrollData.scrollAmount = Math.round(safeAreaBottom - inputBottom);
|
scrollData.scrollAmount = Math.round(safeAreaBottom - inputBottom);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -556,47 +574,34 @@ export class InputBase extends Ion {
|
|||||||
|
|
||||||
scrollData.inputSafeY = -(inputTop - safeAreaTop) + 4;
|
scrollData.inputSafeY = -(inputTop - safeAreaTop) + 4;
|
||||||
|
|
||||||
} else if (inputTopAboveSafeArea) {
|
if (inputTopAboveSafeArea && scrollData.scrollAmount > inputOffsetHeight) {
|
||||||
// Input top above safe area
|
// the input top is above the safe area and we're already scrolling it into place
|
||||||
// auto scroll the input down so at least the top of it shows
|
// don't let it scroll more than the height of the input
|
||||||
scrollData.scrollAmount = Math.round(safeAreaTop - inputTop);
|
scrollData.scrollAmount = inputOffsetHeight;
|
||||||
|
}
|
||||||
scrollData.inputSafeY = (safeAreaTop - inputTop) + 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// figure out where it should scroll to for the best position to the input
|
// figure out where it should scroll to for the best position to the input
|
||||||
scrollData.scrollTo = (scrollViewDimensions.scrollTop - scrollData.scrollAmount);
|
scrollData.scrollTo = (scrollViewDimensions.scrollTop - scrollData.scrollAmount);
|
||||||
|
|
||||||
if (scrollData.scrollAmount < 0) {
|
// when auto-scrolling, there also needs to be enough
|
||||||
// when auto-scrolling up, there also needs to be enough
|
|
||||||
// content padding at the bottom of the scroll view
|
// content padding at the bottom of the scroll view
|
||||||
// manually add it if there isn't enough scrollable area
|
// always add scroll padding when a text input has focus
|
||||||
|
// this allows for the content to scroll above of the keyboard
|
||||||
|
// content behind the keyboard would be blank
|
||||||
|
// some cases may not need it, but when jumping around it's best
|
||||||
|
// to have the padding already rendered so there's no jank
|
||||||
|
scrollData.scrollPadding = keyboardHeight;
|
||||||
|
|
||||||
// figure out how many scrollable area is left to scroll up
|
// var safeAreaEle: HTMLElement = (<any>window).safeAreaEle;
|
||||||
let availablePadding = (scrollViewDimensions.scrollHeight - scrollViewDimensions.scrollTop) - scrollViewDimensions.contentHeight;
|
// if (!safeAreaEle) {
|
||||||
|
// safeAreaEle = (<any>window).safeAreaEle = document.createElement('div');
|
||||||
let paddingSpace = availablePadding + scrollData.scrollAmount;
|
// safeAreaEle.style.cssText = 'position:absolute; padding:1px 5px; left:0; right:0; font-weight:bold; font-size:10px; font-family:Courier; text-align:right; background:rgba(0, 128, 0, 0.8); text-shadow:1px 1px white; pointer-events:none;';
|
||||||
if (paddingSpace < 0) {
|
// document.body.appendChild(safeAreaEle);
|
||||||
// there's not enough scrollable area at the bottom, so manually add more
|
|
||||||
scrollData.scrollPadding = (scrollViewDimensions.contentHeight - safeAreaHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (!window.safeAreaEle) {
|
|
||||||
// window.safeAreaEle = document.createElement('div');
|
|
||||||
// window.safeAreaEle.style.position = 'absolute';
|
|
||||||
// window.safeAreaEle.style.background = 'rgba(0, 128, 0, 0.7)';
|
|
||||||
// window.safeAreaEle.style.padding = '2px 5px';
|
|
||||||
// window.safeAreaEle.style.textShadow = '1px 1px white';
|
|
||||||
// window.safeAreaEle.style.left = '0px';
|
|
||||||
// window.safeAreaEle.style.right = '0px';
|
|
||||||
// window.safeAreaEle.style.fontWeight = 'bold';
|
|
||||||
// window.safeAreaEle.style.pointerEvents = 'none';
|
|
||||||
// document.body.appendChild(window.safeAreaEle);
|
|
||||||
// }
|
// }
|
||||||
// window.safeAreaEle.style.top = safeAreaTop + 'px';
|
// safeAreaEle.style.top = safeAreaTop + 'px';
|
||||||
// window.safeAreaEle.style.height = safeAreaHeight + 'px';
|
// safeAreaEle.style.height = safeAreaHeight + 'px';
|
||||||
// window.safeAreaEle.innerHTML = `
|
// safeAreaEle.innerHTML = `
|
||||||
// <div>scrollTo: ${scrollData.scrollTo}</div>
|
// <div>scrollTo: ${scrollData.scrollTo}</div>
|
||||||
// <div>scrollAmount: ${scrollData.scrollAmount}</div>
|
// <div>scrollAmount: ${scrollData.scrollAmount}</div>
|
||||||
// <div>scrollPadding: ${scrollData.scrollPadding}</div>
|
// <div>scrollPadding: ${scrollData.scrollPadding}</div>
|
||||||
@ -604,11 +609,12 @@ export class InputBase extends Ion {
|
|||||||
// <div>scrollHeight: ${scrollViewDimensions.scrollHeight}</div>
|
// <div>scrollHeight: ${scrollViewDimensions.scrollHeight}</div>
|
||||||
// <div>scrollTop: ${scrollViewDimensions.scrollTop}</div>
|
// <div>scrollTop: ${scrollViewDimensions.scrollTop}</div>
|
||||||
// <div>contentHeight: ${scrollViewDimensions.contentHeight}</div>
|
// <div>contentHeight: ${scrollViewDimensions.contentHeight}</div>
|
||||||
|
// <div>plaformHeight: ${plaformHeight}</div>
|
||||||
// `;
|
// `;
|
||||||
|
|
||||||
return scrollData;
|
return scrollData;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const SCROLL_ASSIST_SPEED = 0.3;
|
const SCROLL_ASSIST_SPEED = 0.3;
|
||||||
|
|
||||||
@ -617,3 +623,10 @@ function getScrollAssistDuration(distanceToScroll: number) {
|
|||||||
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
|
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
|
||||||
return Math.min(400, Math.max(150, duration));
|
return Math.min(400, Math.max(150, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ScrollData {
|
||||||
|
scrollAmount: number;
|
||||||
|
scrollTo: number;
|
||||||
|
scrollPadding: number;
|
||||||
|
inputSafeY: number;
|
||||||
|
}
|
||||||
|
@ -146,11 +146,6 @@ $text-input-ios-highlight-color-invalid: $text-input-highlight-color-invalid !
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-label-floating .text-input-ios.cloned-input,
|
|
||||||
.item-label-stacked .text-input-ios.cloned-input {
|
|
||||||
top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// iOS Clear Input Icon
|
// iOS Clear Input Icon
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
@ -147,14 +147,6 @@ $text-input-md-highlight-color-invalid: $text-input-highlight-color-invalid
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-label-floating .text-input-md.cloned-input {
|
|
||||||
top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-label-stacked .text-input-md.cloned-input {
|
|
||||||
top: 27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Material Design Clear Input Icon
|
// Material Design Clear Input Icon
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
@ -119,7 +119,7 @@ input.text-input:-webkit-autofill {
|
|||||||
|
|
||||||
[next-input] {
|
[next-input] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 1px;
|
bottom: 20px;
|
||||||
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
@ -153,18 +153,3 @@ input.text-input:-webkit-autofill {
|
|||||||
.input-has-focus.input-has-value .text-input-clear-icon {
|
.input-has-focus.input-has-value .text-input-clear-icon {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Cloned Input
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
.text-input.cloned-input {
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-input:not(.item-label-floating) .text-input.cloned-active {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
@ -126,14 +126,6 @@ $text-input-wp-highlight-color-invalid: $text-input-highlight-color-invalid
|
|||||||
width: calc(100% - #{$text-input-wp-margin-right});
|
width: calc(100% - #{$text-input-wp-margin-right});
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-label-floating .text-input-wp.cloned-input {
|
|
||||||
top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-label-stacked .text-input-wp.cloned-input {
|
|
||||||
top: 27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-wp.item-label-stacked [item-right],
|
.item-wp.item-label-stacked [item-right],
|
||||||
.item-wp.item-label-floating [item-right] {
|
.item-wp.item-label-floating [item-right] {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
|
@ -62,11 +62,11 @@ export class NativeInput {
|
|||||||
// automatically blur input if:
|
// automatically blur input if:
|
||||||
// 1) this input has focus
|
// 1) this input has focus
|
||||||
// 2) the newly tapped document element is not an input
|
// 2) the newly tapped document element is not an input
|
||||||
console.debug('input blurring enabled');
|
console.debug(`native-input, blurring enabled`);
|
||||||
|
|
||||||
document.addEventListener('touchend', docTouchEnd, true);
|
document.addEventListener('touchend', docTouchEnd, true);
|
||||||
self._unrefBlur = function() {
|
self._unrefBlur = function() {
|
||||||
console.debug('input blurring disabled');
|
console.debug(`native-input, blurring disabled`);
|
||||||
document.removeEventListener('touchend', docTouchEnd, true);
|
document.removeEventListener('touchend', docTouchEnd, true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ export class NativeInput {
|
|||||||
|
|
||||||
beginFocus(shouldFocus: boolean, inputRelativeY: number) {
|
beginFocus(shouldFocus: boolean, inputRelativeY: number) {
|
||||||
if (this._relocated !== shouldFocus) {
|
if (this._relocated !== shouldFocus) {
|
||||||
var focusedInputEle = this.element();
|
const focusedInputEle = this.element();
|
||||||
if (shouldFocus) {
|
if (shouldFocus) {
|
||||||
// we should focus into this element
|
// we should focus into this element
|
||||||
|
|
||||||
@ -113,8 +113,7 @@ export class NativeInput {
|
|||||||
// the cloned input fills the area of where native input should be
|
// the cloned input fills the area of where native input should be
|
||||||
// while the native input fakes out the browser by relocating itself
|
// while the native input fakes out the browser by relocating itself
|
||||||
// before it receives the actual focus event
|
// before it receives the actual focus event
|
||||||
var clonedInputEle = cloneInput(focusedInputEle, 'cloned-focus');
|
cloneInputComponent(focusedInputEle);
|
||||||
focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle);
|
|
||||||
|
|
||||||
// move the native input to a location safe to receive focus
|
// move the native input to a location safe to receive focus
|
||||||
// according to the browser, the native input receives focus in an
|
// according to the browser, the native input receives focus in an
|
||||||
@ -128,18 +127,11 @@ export class NativeInput {
|
|||||||
// to scroll the input into view itself (screwing up headers/footers)
|
// to scroll the input into view itself (screwing up headers/footers)
|
||||||
this.setFocus();
|
this.setFocus();
|
||||||
|
|
||||||
if (this._clone) {
|
|
||||||
focusedInputEle.classList.add('cloned-active');
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// should remove the focus
|
// should remove the focus
|
||||||
if (this._clone) {
|
if (this._clone) {
|
||||||
// should remove the cloned node
|
// should remove the cloned node
|
||||||
focusedInputEle.classList.remove('cloned-active');
|
removeClone(focusedInputEle);
|
||||||
(<any>focusedInputEle.style)[CSS.transform] = '';
|
|
||||||
focusedInputEle.style.opacity = '';
|
|
||||||
removeClone(focusedInputEle, 'cloned-focus');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,17 +142,14 @@ export class NativeInput {
|
|||||||
hideFocus(shouldHideFocus: boolean) {
|
hideFocus(shouldHideFocus: boolean) {
|
||||||
let focusedInputEle = this.element();
|
let focusedInputEle = this.element();
|
||||||
|
|
||||||
console.debug(`native input hideFocus, shouldHideFocus: ${shouldHideFocus}, input value: ${focusedInputEle.value}`);
|
console.debug(`native-input, hideFocus, shouldHideFocus: ${shouldHideFocus}, input value: ${focusedInputEle.value}`);
|
||||||
|
|
||||||
if (shouldHideFocus) {
|
if (shouldHideFocus) {
|
||||||
let clonedInputEle = cloneInput(focusedInputEle, 'cloned-move');
|
cloneInputComponent(focusedInputEle);
|
||||||
|
focusedInputEle.style.transform = 'scale(0)';
|
||||||
focusedInputEle.classList.add('cloned-active');
|
|
||||||
focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
focusedInputEle.classList.remove('cloned-active');
|
removeClone(focusedInputEle);
|
||||||
removeClone(focusedInputEle, 'cloned-move');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,26 +175,56 @@ export class NativeInput {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneInput(focusedInputEle: any, addCssClass: string) {
|
function cloneInputComponent(srcNativeInputEle: HTMLInputElement) {
|
||||||
let clonedInputEle = focusedInputEle.cloneNode(true);
|
// given a native <input> or <textarea> element
|
||||||
clonedInputEle.classList.add('cloned-input');
|
// find its parent wrapping component like <ion-input> or <ion-textarea>
|
||||||
clonedInputEle.classList.add(addCssClass);
|
// then clone the entire component
|
||||||
clonedInputEle.setAttribute('aria-hidden', true);
|
const srcComponentEle = <HTMLElement>srcNativeInputEle.closest('ion-input,ion-textarea');
|
||||||
clonedInputEle.removeAttribute('aria-labelledby');
|
if (srcComponentEle) {
|
||||||
clonedInputEle.tabIndex = -1;
|
// DOM READ
|
||||||
clonedInputEle.style.width = (focusedInputEle.offsetWidth + 10) + 'px';
|
const srcTop = srcComponentEle.offsetTop;
|
||||||
clonedInputEle.style.height = focusedInputEle.offsetHeight + 'px';
|
const srcLeft = srcComponentEle.offsetLeft;
|
||||||
clonedInputEle.value = focusedInputEle.value;
|
const srcWidth = srcComponentEle.offsetWidth;
|
||||||
return clonedInputEle;
|
const srcHeight = srcComponentEle.offsetHeight;
|
||||||
|
|
||||||
|
// DOM WRITE
|
||||||
|
// not using deep clone so we don't pull in unnecessary nodes
|
||||||
|
const clonedComponentEle = <HTMLElement>srcComponentEle.cloneNode(false);
|
||||||
|
clonedComponentEle.classList.add('cloned-input');
|
||||||
|
clonedComponentEle.setAttribute('aria-hidden', 'true');
|
||||||
|
clonedComponentEle.style.pointerEvents = 'none';
|
||||||
|
clonedComponentEle.style.position = 'absolute';
|
||||||
|
clonedComponentEle.style.top = srcTop + 'px';
|
||||||
|
clonedComponentEle.style.left = srcLeft + 'px';
|
||||||
|
clonedComponentEle.style.width = srcWidth + 'px';
|
||||||
|
clonedComponentEle.style.height = srcHeight + 'px';
|
||||||
|
|
||||||
|
const clonedNativeInputEle = <HTMLInputElement>srcNativeInputEle.cloneNode(false);
|
||||||
|
clonedNativeInputEle.value = srcNativeInputEle.value;
|
||||||
|
clonedNativeInputEle.tabIndex = -1;
|
||||||
|
|
||||||
|
clonedComponentEle.appendChild(clonedNativeInputEle);
|
||||||
|
srcComponentEle.parentNode.appendChild(clonedComponentEle);
|
||||||
|
|
||||||
|
srcComponentEle.style.pointerEvents = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeClone(focusedInputEle: any, queryCssClass: string) {
|
srcNativeInputEle.style.transform = 'scale(0)';
|
||||||
let clonedInputEle = focusedInputEle.parentElement.querySelector('.' + queryCssClass);
|
|
||||||
if (clonedInputEle) {
|
|
||||||
clonedInputEle.parentNode.removeChild(clonedInputEle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeClone(srcNativeInputEle: HTMLElement) {
|
||||||
|
const srcComponentEle = <HTMLElement>srcNativeInputEle.closest('ion-input,ion-textarea');
|
||||||
|
if (srcComponentEle && srcComponentEle.parentElement) {
|
||||||
|
const clonedInputEles = srcComponentEle.parentElement.querySelectorAll('.cloned-input');
|
||||||
|
for (var i = 0; i < clonedInputEles.length; i++) {
|
||||||
|
clonedInputEles[i].parentNode.removeChild(clonedInputEles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
srcComponentEle.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
srcNativeInputEle.style.transform = '';
|
||||||
|
srcNativeInputEle.style.opacity = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,133 +1,181 @@
|
|||||||
import { TextInput } from '../input';
|
import { getScrollData } from '../input-base';
|
||||||
|
import { ContentDimensions } from '../../content/content';
|
||||||
|
|
||||||
|
|
||||||
|
describe('text input', () => {
|
||||||
|
|
||||||
|
describe('getScrollData', () => {
|
||||||
|
|
||||||
describe('input', () => {
|
|
||||||
it('should scroll, top and bottom below safe area, no room to scroll', () => {
|
it('should scroll, top and bottom below safe area, no room to scroll', () => {
|
||||||
let inputOffsetTop = 350;
|
let inputOffsetTop = 350;
|
||||||
let inputOffsetHeight = 35;
|
let inputOffsetHeight = 35;
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
contentHeight: 700,
|
contentHeight: 700,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 30,
|
scrollTop: 30,
|
||||||
scrollHeight: 700
|
scrollHeight: 700,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(-205);
|
expect(scrollData.scrollAmount).toBe(-205);
|
||||||
expect(scrollData.scrollTo).toBe(235);
|
expect(scrollData.scrollTo).toBe(235);
|
||||||
expect(scrollData.scrollPadding).toBe(550);
|
expect(scrollData.scrollPadding).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll, top and bottom below safe area, room to scroll', () => {
|
it('should scroll, top and bottom below safe area, room to scroll', () => {
|
||||||
let inputOffsetTop = 350;
|
let inputOffsetTop = 350;
|
||||||
let inputOffsetHeight = 35;
|
let inputOffsetHeight = 35;
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
contentHeight: 700,
|
contentHeight: 700,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 30,
|
scrollTop: 30,
|
||||||
scrollHeight: 1000
|
scrollHeight: 1000,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(-205);
|
expect(scrollData.scrollAmount).toBe(-205);
|
||||||
expect(scrollData.scrollTo).toBe(235);
|
expect(scrollData.scrollTo).toBe(235);
|
||||||
expect(scrollData.scrollPadding).toBe(0);
|
expect(scrollData.scrollPadding).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll, top above safe', () => {
|
it('should scroll, top above safe', () => {
|
||||||
// TextInput top within safe area, bottom below safe area, room to scroll
|
// TextInput top within safe area, bottom below safe area, room to scroll
|
||||||
let inputOffsetTop = 100;
|
let inputOffsetTop = 100;
|
||||||
let inputOffsetHeight = 33;
|
let inputOffsetHeight = 33;
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
contentHeight: 700,
|
contentHeight: 700,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 250,
|
scrollTop: 250,
|
||||||
scrollHeight: 700
|
scrollHeight: 700,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(150);
|
expect(scrollData.scrollAmount).toBe(33);
|
||||||
expect(scrollData.scrollTo).toBe(100);
|
expect(scrollData.scrollTo).toBe(217);
|
||||||
expect(scrollData.scrollPadding).toBe(0);
|
expect(scrollData.scrollPadding).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll, top in safe, bottom below safe, below more than top in, not enough padding', () => {
|
it('should scroll, top in safe, bottom below safe, below more than top in, not enough padding', () => {
|
||||||
// TextInput top within safe area, bottom below safe area, room to scroll
|
// TextInput top within safe area, bottom below safe area, room to scroll
|
||||||
let inputOffsetTop = 100;
|
let inputOffsetTop = 100;
|
||||||
let inputOffsetHeight = 320;
|
let inputOffsetHeight = 320;
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
contentHeight: 700,
|
contentHeight: 700,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 20,
|
scrollTop: 20,
|
||||||
scrollHeight: 700
|
scrollHeight: 700,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(-80);
|
expect(scrollData.scrollAmount).toBe(-80);
|
||||||
expect(scrollData.scrollTo).toBe(100);
|
expect(scrollData.scrollTo).toBe(100);
|
||||||
expect(scrollData.scrollPadding).toBe(550);
|
expect(scrollData.scrollPadding).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll, top in safe, bottom below safe, below more than top in, enough padding', () => {
|
it('should scroll, top in safe, bottom below safe, below more than top in, enough padding', () => {
|
||||||
// TextInput top within safe area, bottom below safe area, room to scroll
|
// TextInput top within safe area, bottom below safe area, room to scroll
|
||||||
let inputOffsetTop = 20;
|
let inputOffsetTop = 20;
|
||||||
let inputOffsetHeight = 330;
|
let inputOffsetHeight = 330;
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
|
contentHeight: 700,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
|
scrollHeight: 700,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(-20);
|
expect(scrollData.scrollAmount).toBe(-20);
|
||||||
expect(scrollData.scrollTo).toBe(20);
|
expect(scrollData.scrollTo).toBe(20);
|
||||||
expect(scrollData.scrollPadding).toBe(0);
|
expect(scrollData.scrollPadding).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll, top in safe, bottom below safe, below less than top in, enough padding', () => {
|
it('should scroll, top in safe, bottom below safe, below less than top in, enough padding', () => {
|
||||||
// TextInput top within safe area, bottom below safe area, room to scroll
|
// TextInput top within safe area, bottom below safe area, room to scroll
|
||||||
let inputOffsetTop = 250;
|
let inputOffsetTop = 250;
|
||||||
let inputOffsetHeight = 80; // goes 30px below safe area
|
let inputOffsetHeight = 80; // goes 30px below safe area
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
|
contentHeight: 700,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
|
scrollHeight: 700,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(-180);
|
expect(scrollData.scrollAmount).toBe(-180);
|
||||||
expect(scrollData.scrollTo).toBe(180);
|
expect(scrollData.scrollTo).toBe(180);
|
||||||
expect(scrollData.scrollPadding).toBe(0);
|
expect(scrollData.scrollPadding).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not scroll, top in safe, bottom in safe', () => {
|
it('should not scroll, top in safe, bottom in safe', () => {
|
||||||
// TextInput top within safe area, bottom within safe area
|
// TextInput top within safe area, bottom within safe area
|
||||||
let inputOffsetTop = 100;
|
let inputOffsetTop = 100;
|
||||||
let inputOffsetHeight = 50;
|
let inputOffsetHeight = 50;
|
||||||
let scrollViewDimensions = {
|
let scrollViewDimensions: ContentDimensions = {
|
||||||
contentTop: 100,
|
contentTop: 100,
|
||||||
|
contentBottom: 0,
|
||||||
|
contentHeight: 700,
|
||||||
|
contentWidth: 400,
|
||||||
|
contentLeft: 0,
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
keyboardTop: 400
|
scrollHeight: 700,
|
||||||
|
scrollWidth: 400,
|
||||||
|
scrollLeft: 0,
|
||||||
};
|
};
|
||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
expect(scrollData.scrollAmount).toBe(0);
|
expect(scrollData.scrollAmount).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -27,8 +27,8 @@ $label-ios-margin: $item-ios-padding-top ($item-ios-padding-right /
|
|||||||
color: $label-ios-text-color;
|
color: $label-ios-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-ios + ion-input .text-input,
|
.label-ios + .input .text-input,
|
||||||
.label-ios + ion-textarea .text-input {
|
.label-ios + .input + .cloned-input {
|
||||||
margin-left: $item-ios-padding-left;
|
margin-left: $item-ios-padding-left;
|
||||||
|
|
||||||
width: calc(100% - (#{$item-ios-padding-right} / 2) - #{$item-ios-padding-left});
|
width: calc(100% - (#{$item-ios-padding-right} / 2) - #{$item-ios-padding-left});
|
||||||
|
@ -76,6 +76,7 @@ export const PLATFORM_CONFIGS: {[key: string]: PlatformConfig} = {
|
|||||||
return 'ripple';
|
return 'ripple';
|
||||||
},
|
},
|
||||||
autoFocusAssist: 'immediate',
|
autoFocusAssist: 'immediate',
|
||||||
|
inputCloning: true,
|
||||||
scrollAssist: true,
|
scrollAssist: true,
|
||||||
hoverCSS: false,
|
hoverCSS: false,
|
||||||
keyboardHeight: 300,
|
keyboardHeight: 300,
|
||||||
|
4
src/themes/normalize.scss
vendored
4
src/themes/normalize.scss
vendored
@ -107,6 +107,10 @@ textarea {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea::placeholder {
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
form,
|
form,
|
||||||
input,
|
input,
|
||||||
optgroup,
|
optgroup,
|
||||||
|
Reference in New Issue
Block a user