mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 20:33:32 +08:00
refactor(input): break apart input source files
This commit is contained in:
538
ionic/components/input/input-base.ts
Normal file
538
ionic/components/input/input-base.ts
Normal file
@ -0,0 +1,538 @@
|
||||
import {Directive, Input, Output, EventEmitter, HostListener, ViewChild, ElementRef} from 'angular2/core';
|
||||
import {NgControl} from 'angular2/common';
|
||||
|
||||
import {Config} from '../../config/config';
|
||||
import {Content} from '../content/content';
|
||||
import {Form} from '../../util/form';
|
||||
import {Item} from '../item/item';
|
||||
import {IonicApp} from '../app/app';
|
||||
import {Label} from '../label/label';
|
||||
import {pointerCoord, hasPointerMoved, closest} from '../../util/dom';
|
||||
import {NavController} from '../nav/nav-controller';
|
||||
import {NativeInput} from './native-input';
|
||||
import {Platform} from '../../platform/platform';
|
||||
|
||||
|
||||
export class InputBase {
|
||||
protected _coord;
|
||||
protected _deregScroll;
|
||||
protected _keyboardHeight;
|
||||
protected _scrollMove: EventListener;
|
||||
protected _type: string = 'text';
|
||||
protected _useAssist: boolean = true;
|
||||
protected _value = '';
|
||||
protected _isTouch: boolean;
|
||||
|
||||
inputControl: NgControl;
|
||||
|
||||
@Input() clearInput;
|
||||
@Input() placeholder: string = '';
|
||||
@ViewChild(NativeInput) protected _native: NativeInput;
|
||||
|
||||
constructor(
|
||||
config: Config,
|
||||
protected _form: Form,
|
||||
protected _item: Item,
|
||||
protected _app: IonicApp,
|
||||
protected _platform: Platform,
|
||||
protected _elementRef: ElementRef,
|
||||
protected _scrollView: Content,
|
||||
protected _nav: NavController,
|
||||
ngControl: NgControl
|
||||
) {
|
||||
this._useAssist = true;// config.get('scrollAssist');
|
||||
this._keyboardHeight = config.get('keyboardHeight');
|
||||
|
||||
if (ngControl) {
|
||||
ngControl.valueAccessor = this;
|
||||
}
|
||||
|
||||
_form.register(this);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this._item) {
|
||||
this._item.setCssClass('item-input', true);
|
||||
this._item.registerInput(this._type);
|
||||
}
|
||||
|
||||
let clearInput = this.clearInput;
|
||||
if (typeof clearInput === 'string') {
|
||||
this.clearInput = (clearInput === '' || clearInput === 'true');
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
let self = this;
|
||||
|
||||
self._scrollMove = function(ev: UIEvent) {
|
||||
// scroll move event listener this instance can reuse
|
||||
if (!(self._nav && self._nav.isTransitioning())) {
|
||||
self.deregScrollMove();
|
||||
|
||||
if (self.hasFocus()) {
|
||||
self._native.hideFocus(true);
|
||||
|
||||
self._scrollView.onScrollEnd(function() {
|
||||
self._native.hideFocus(false);
|
||||
|
||||
if (self.hasFocus()) {
|
||||
// if it still has focus then keep listening
|
||||
self.regScrollMove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.setItemControlCss();
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
this.setItemControlCss();
|
||||
}
|
||||
|
||||
private setItemControlCss() {
|
||||
let item = this._item;
|
||||
let nativeControl = this._native && this._native.ngControl;
|
||||
|
||||
if (item && nativeControl) {
|
||||
item.setCssClass('ng-untouched', nativeControl.untouched);
|
||||
item.setCssClass('ng-touched', nativeControl.touched);
|
||||
item.setCssClass('ng-pristine', nativeControl.pristine);
|
||||
item.setCssClass('ng-dirty', nativeControl.dirty);
|
||||
item.setCssClass('ng-valid', nativeControl.valid);
|
||||
item.setCssClass('ng-invalid', !nativeControl.valid);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._form.deregister(this);
|
||||
}
|
||||
|
||||
@Input()
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(val) {
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
@Input()
|
||||
get type() {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
set type(val) {
|
||||
this._type = 'text';
|
||||
|
||||
if (val) {
|
||||
val = val.toLowerCase();
|
||||
|
||||
if (/password|email|number|search|tel|url|date|datetime|datetime-local|month/.test(val)) {
|
||||
this._type = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ViewChild(NativeInput)
|
||||
private set _nativeInput(nativeInput: NativeInput) {
|
||||
this._native = nativeInput;
|
||||
|
||||
if (this._item && this._item.labelId !== null) {
|
||||
nativeInput.labelledBy(this._item.labelId);
|
||||
}
|
||||
|
||||
nativeInput.valueChange.subscribe(inputValue => {
|
||||
this.onChange(inputValue);
|
||||
});
|
||||
|
||||
this.focusChange(this.hasFocus());
|
||||
nativeInput.focusChange.subscribe(textInputHasFocus => {
|
||||
this.focusChange(textInputHasFocus);
|
||||
if (!textInputHasFocus) {
|
||||
this.onTouched(textInputHasFocus);
|
||||
}
|
||||
});
|
||||
|
||||
this.checkHasValue(nativeInput.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ViewChild(NextInput)
|
||||
private set _nextInput(nextInput: NextInput) {
|
||||
nextInput.focused.subscribe(() => {
|
||||
this._form.tabFocus(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Angular2 Forms API method called by the model (Control) on change to update
|
||||
* the checked value.
|
||||
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34
|
||||
*/
|
||||
writeValue(value) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onChange(val) {
|
||||
this.checkHasValue(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onTouched(val) {}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
hasFocus(): boolean {
|
||||
// check if an input has focus or not
|
||||
return this._native.hasFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
checkHasValue(inputValue) {
|
||||
if (this._item) {
|
||||
this._item.setCssClass('input-has-value', !!(inputValue && inputValue !== ''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
focusChange(inputHasFocus: boolean) {
|
||||
if (this._item) {
|
||||
this._item.setCssClass('input-has-focus', inputHasFocus);
|
||||
}
|
||||
if (!inputHasFocus) {
|
||||
this.deregScrollMove();
|
||||
}
|
||||
}
|
||||
|
||||
private pointerStart(ev) {
|
||||
// input cover touchstart
|
||||
console.debug('scroll assist pointerStart', ev.type);
|
||||
|
||||
if (ev.type === 'touchstart') {
|
||||
this._isTouch = true;
|
||||
}
|
||||
|
||||
if ((this._isTouch || (!this._isTouch && ev.type === 'mousedown')) && this._app.isEnabled()) {
|
||||
// remember where the touchstart/mousedown started
|
||||
this._coord = pointerCoord(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private pointerEnd(ev) {
|
||||
// input cover touchend/mouseup
|
||||
console.debug('scroll assist pointerEnd', ev.type);
|
||||
|
||||
if ((this._isTouch && ev.type === 'mouseup') || !this._app.isEnabled()) {
|
||||
// the app is actively doing something right now
|
||||
// don't try to scroll in the input
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
} else if (this._coord) {
|
||||
// get where the touchend/mouseup ended
|
||||
let endCoord = pointerCoord(ev);
|
||||
|
||||
// focus this input if the pointer hasn't moved XX pixels
|
||||
// and the input doesn't already have focus
|
||||
if (!hasPointerMoved(8, this._coord, endCoord) && !this.hasFocus()) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// begin the input focus process
|
||||
console.debug('initFocus', ev.type);
|
||||
this.initFocus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._coord = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initFocus() {
|
||||
// begin the process of setting focus to the inner input element
|
||||
let scrollView = this._scrollView;
|
||||
|
||||
if (scrollView) {
|
||||
// this input is inside of a scroll view
|
||||
|
||||
// find out if text input should be manually scrolled into view
|
||||
let ele = this._elementRef.nativeElement;
|
||||
let itemEle = closest(ele, 'ion-item');
|
||||
if (itemEle) {
|
||||
ele = itemEle;
|
||||
}
|
||||
|
||||
let scrollData = InputBase.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getContentDimensions(), this._keyboardHeight, this._platform.height());
|
||||
if (scrollData.scrollAmount > -3 && scrollData.scrollAmount < 3) {
|
||||
// the text input is in a safe position that doesn't
|
||||
// require it to be scrolled into view, just set focus now
|
||||
this.setFocus();
|
||||
this.regScrollMove();
|
||||
return;
|
||||
}
|
||||
|
||||
// add padding to the bottom of the scroll view (if needed)
|
||||
scrollView.addScrollPadding(scrollData.scrollPadding);
|
||||
|
||||
// manually scroll the text input to the top
|
||||
// do not allow any clicks while it's scrolling
|
||||
let scrollDuration = getScrollAssistDuration(scrollData.scrollAmount);
|
||||
this._app.setEnabled(false, scrollDuration);
|
||||
this._nav && this._nav.setTransitioning(true, scrollDuration);
|
||||
|
||||
// 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
|
||||
// at this point the native text input still does not have focus
|
||||
this._native.relocate(true, scrollData.inputSafeY);
|
||||
|
||||
// scroll the input into place
|
||||
scrollView.scrollTo(0, scrollData.scrollTo, scrollDuration).then(() => {
|
||||
// the scroll view is in the correct position now
|
||||
// give the native text input focus
|
||||
this._native.relocate(false, 0);
|
||||
|
||||
this.setFocus();
|
||||
|
||||
// all good, allow clicks again
|
||||
this._app.setEnabled(true);
|
||||
this._nav && this._nav.setTransitioning(false);
|
||||
this.regScrollMove();
|
||||
});
|
||||
|
||||
} else {
|
||||
// not inside of a scroll view, just focus it
|
||||
this.setFocus();
|
||||
this.regScrollMove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
clearTextInput() {
|
||||
console.log("Should clear input");
|
||||
//console.log(this.textInput.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private setFocus() {
|
||||
// immediately set focus
|
||||
this._form.setAsFocused(this);
|
||||
|
||||
// set focus on the actual input element
|
||||
this._native.setFocus();
|
||||
|
||||
// ensure the body hasn't scrolled down
|
||||
document.body.scrollTop = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Angular2 Forms API method called by the view (NgControl) to register the
|
||||
* onChange event handler that updates the model (Control).
|
||||
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L27
|
||||
* @param {Function} fn the onChange event handler.
|
||||
*/
|
||||
registerOnChange(fn) { this.onChange = fn; }
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Angular2 Forms API method called by the the view (NgControl) to register
|
||||
* the onTouched event handler that marks model (Control) as touched.
|
||||
* @param {Function} fn onTouched event handler.
|
||||
*/
|
||||
registerOnTouched(fn) { this.onTouched = fn; }
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private regScrollMove() {
|
||||
// register scroll move listener
|
||||
if (this._useAssist && this._scrollView) {
|
||||
setTimeout(() => {
|
||||
this.deregScrollMove();
|
||||
this._deregScroll = this._scrollView.addScrollEventListener(this._scrollMove);
|
||||
}, 80);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private deregScrollMove() {
|
||||
// deregister the scroll move listener
|
||||
this._deregScroll && this._deregScroll();
|
||||
}
|
||||
|
||||
focusNext() {
|
||||
this._form.tabFocus(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, plaformHeight) {
|
||||
// compute input's Y values relative to the body
|
||||
let inputTop = (inputOffsetTop + scrollViewDimensions.contentTop - scrollViewDimensions.scrollTop);
|
||||
let inputBottom = (inputTop + inputOffsetHeight);
|
||||
|
||||
// compute the safe area which is the viewable content area when the soft keyboard is up
|
||||
let safeAreaTop = scrollViewDimensions.contentTop;
|
||||
let safeAreaHeight = plaformHeight - keyboardHeight - safeAreaTop;
|
||||
safeAreaHeight /= 2;
|
||||
let safeAreaBottom = safeAreaTop + safeAreaHeight;
|
||||
|
||||
let inputTopWithinSafeArea = (inputTop >= safeAreaTop && inputTop <= safeAreaBottom);
|
||||
let inputTopAboveSafeArea = (inputTop < safeAreaTop);
|
||||
let inputTopBelowSafeArea = (inputTop > safeAreaBottom);
|
||||
let inputBottomWithinSafeArea = (inputBottom >= safeAreaTop && inputBottom <= safeAreaBottom);
|
||||
let inputBottomBelowSafeArea = (inputBottom > safeAreaBottom);
|
||||
|
||||
/*
|
||||
Text Input Scroll To Scenarios
|
||||
---------------------------------------
|
||||
1) Input top within safe area, bottom within safe area
|
||||
2) Input top within safe area, bottom below safe area, room to scroll
|
||||
3) Input top above safe area, bottom within safe area, room to scroll
|
||||
4) Input top below safe area, no room to scroll, input smaller than safe area
|
||||
5) Input top within safe area, bottom below safe area, no room to scroll, input smaller than safe area
|
||||
6) Input top within safe area, bottom 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 = {
|
||||
scrollAmount: 0,
|
||||
scrollTo: 0,
|
||||
scrollPadding: 0,
|
||||
inputSafeY: 0
|
||||
};
|
||||
|
||||
if (inputTopWithinSafeArea && inputBottomWithinSafeArea) {
|
||||
// Input top within safe area, bottom within safe area
|
||||
// no need to scroll to a position, it's good as-is
|
||||
return scrollData;
|
||||
}
|
||||
|
||||
// looks like we'll have to do some auto-scrolling
|
||||
if (inputTopBelowSafeArea || inputBottomBelowSafeArea) {
|
||||
// Input top and bottom below safe area
|
||||
// auto scroll the input up so at least the top of it shows
|
||||
|
||||
if (safeAreaHeight > inputOffsetHeight) {
|
||||
// safe area height is taller than the input height, so we
|
||||
// can bring it up the input just enough to show the input bottom
|
||||
scrollData.scrollAmount = Math.round(safeAreaBottom - inputBottom);
|
||||
|
||||
} else {
|
||||
// safe area height is smaller than the input height, so we can
|
||||
// only scroll it up so the input top is at the top of the safe area
|
||||
// however the input bottom will be below the safe area
|
||||
scrollData.scrollAmount = Math.round(safeAreaTop - inputTop);
|
||||
}
|
||||
|
||||
scrollData.inputSafeY = -(inputTop - safeAreaTop) + 4;
|
||||
|
||||
} else if (inputTopAboveSafeArea) {
|
||||
// Input top above safe area
|
||||
// auto scroll the input down so at least the top of it shows
|
||||
scrollData.scrollAmount = Math.round(safeAreaTop - inputTop);
|
||||
|
||||
scrollData.inputSafeY = (safeAreaTop - inputTop) + 4;
|
||||
}
|
||||
|
||||
// figure out where it should scroll to for the best position to the input
|
||||
scrollData.scrollTo = (scrollViewDimensions.scrollTop - scrollData.scrollAmount);
|
||||
|
||||
if (scrollData.scrollAmount < 0) {
|
||||
// when auto-scrolling up, there also needs to be enough
|
||||
// content padding at the bottom of the scroll view
|
||||
// manually add it if there isn't enough scrollable area
|
||||
|
||||
// figure out how many scrollable area is left to scroll up
|
||||
let availablePadding = (scrollViewDimensions.scrollHeight - scrollViewDimensions.scrollTop) - scrollViewDimensions.contentHeight;
|
||||
|
||||
let paddingSpace = availablePadding + scrollData.scrollAmount;
|
||||
if (paddingSpace < 0) {
|
||||
// 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';
|
||||
// window.safeAreaEle.style.height = safeAreaHeight + 'px';
|
||||
// window.safeAreaEle.innerHTML = `
|
||||
// <div>scrollTo: ${scrollData.scrollTo}</div>
|
||||
// <div>scrollAmount: ${scrollData.scrollAmount}</div>
|
||||
// <div>scrollPadding: ${scrollData.scrollPadding}</div>
|
||||
// <div>inputSafeY: ${scrollData.inputSafeY}</div>
|
||||
// <div>scrollHeight: ${scrollViewDimensions.scrollHeight}</div>
|
||||
// <div>scrollTop: ${scrollViewDimensions.scrollTop}</div>
|
||||
// <div>contentHeight: ${scrollViewDimensions.contentHeight}</div>
|
||||
// `;
|
||||
|
||||
return scrollData;
|
||||
}
|
||||
}
|
||||
|
||||
const SCROLL_ASSIST_SPEED = 0.3;
|
||||
|
||||
function getScrollAssistDuration(distanceToScroll) {
|
||||
//return 3000;
|
||||
distanceToScroll = Math.abs(distanceToScroll);
|
||||
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
|
||||
return Math.min(400, Math.max(150, duration));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[next-input]'
|
||||
})
|
||||
export class NextInput {
|
||||
@Output() focused: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
@HostListener('focus')
|
||||
receivedFocus() {
|
||||
this.focused.emit(true);
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +1,32 @@
|
||||
import {Component, Directive, Attribute, forwardRef, QueryList, Host, Optional, ElementRef, Renderer, Input, Output, EventEmitter, ViewChild, ViewChildren, ContentChild, ContentChildren, HostListener} from 'angular2/core';
|
||||
import {Component, Optional, ElementRef} from 'angular2/core';
|
||||
import {NgIf, NgControl} from 'angular2/common';
|
||||
|
||||
import {NavController} from '../nav/nav-controller';
|
||||
import {Button} from '../button/button';
|
||||
import {Config} from '../../config/config';
|
||||
import {Content} from '../content/content';
|
||||
import {Form} from '../../util/form';
|
||||
import {InputBase, NextInput} from './input-base';
|
||||
import {IonicApp} from '../app/app';
|
||||
import {Item} from '../item/item';
|
||||
import {Label} from '../label/label';
|
||||
import {IonicApp} from '../app/app';
|
||||
import {Content} from '../content/content';
|
||||
import {pointerCoord, hasPointerMoved, closest} from '../../util/dom';
|
||||
import {Platform} from '../../platform/platform';
|
||||
import {Button} from '../button/button';
|
||||
import {Icon} from '../icon/icon';
|
||||
import {NativeInput} from './native-input';
|
||||
import {NavController} from '../nav/nav-controller';
|
||||
import {Platform} from '../../platform/platform';
|
||||
|
||||
|
||||
/**
|
||||
* @name Input
|
||||
* @module ionic
|
||||
* @description
|
||||
*
|
||||
* `ion-input` is a wrapper for text inputs and textareas. An
|
||||
* `ion-input` is for text input types only, such as `text`,
|
||||
* `password`, `email`, `number`, `search`, `tel`, `url` and `textarea`.
|
||||
|
||||
* `ion-input` is meant for text type inputs only, such as `text`,
|
||||
* `password`, `email`, `number`, `search`, `tel`, and `url`. Ionic
|
||||
* still uses an actual `<input type="text">` HTML element within the
|
||||
* component, however, with Ionic wrapping the native HTML input
|
||||
* element it's able to better handle the user experience and
|
||||
* interactivity.
|
||||
*
|
||||
* Similarily, `<ion-textarea>` should be used in place of `<textarea>`.
|
||||
*
|
||||
* An `ion-input` is **not** used for non-text type inputs, such as a
|
||||
* `checkbox`, `radio`, `toggle`, `range`, `select`, etc.
|
||||
*
|
||||
@ -58,522 +61,6 @@ import {NativeInput} from './native-input';
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[next-input]'
|
||||
})
|
||||
class NextInput {
|
||||
|
||||
@Output() focused: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
@HostListener('focus')
|
||||
receivedFocus() {
|
||||
this.focused.emit(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class TextInputBase {
|
||||
protected _coord;
|
||||
protected _deregScroll;
|
||||
protected _keyboardHeight;
|
||||
protected _scrollMove: EventListener;
|
||||
protected _type: string = 'text';
|
||||
protected _useAssist: boolean = true;
|
||||
protected _value = '';
|
||||
protected _isTouch: boolean;
|
||||
|
||||
inputControl: NgControl;
|
||||
|
||||
@Input() clearInput;
|
||||
@Input() placeholder: string = '';
|
||||
@ViewChild(NativeInput) protected _native: NativeInput;
|
||||
|
||||
constructor(
|
||||
config: Config,
|
||||
protected _form: Form,
|
||||
protected _item: Item,
|
||||
protected _app: IonicApp,
|
||||
protected _platform: Platform,
|
||||
protected _elementRef: ElementRef,
|
||||
protected _scrollView: Content,
|
||||
protected _nav: NavController,
|
||||
ngControl: NgControl
|
||||
) {
|
||||
this._useAssist = true;// config.get('scrollAssist');
|
||||
this._keyboardHeight = config.get('keyboardHeight');
|
||||
|
||||
if (ngControl) {
|
||||
ngControl.valueAccessor = this;
|
||||
}
|
||||
|
||||
_form.register(this);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this._item) {
|
||||
this._item.setCssClass('item-input', true);
|
||||
this._item.registerInput(this._type);
|
||||
}
|
||||
|
||||
let clearInput = this.clearInput;
|
||||
if (typeof clearInput === 'string') {
|
||||
this.clearInput = (clearInput === '' || clearInput === 'true');
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
let self = this;
|
||||
|
||||
self._scrollMove = function(ev: UIEvent) {
|
||||
// scroll move event listener this instance can reuse
|
||||
if (!(self._nav && self._nav.isTransitioning())) {
|
||||
self.deregScrollMove();
|
||||
|
||||
if (self.hasFocus()) {
|
||||
self._native.hideFocus(true);
|
||||
|
||||
self._scrollView.onScrollEnd(function() {
|
||||
self._native.hideFocus(false);
|
||||
|
||||
if (self.hasFocus()) {
|
||||
// if it still has focus then keep listening
|
||||
self.regScrollMove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.setItemControlCss();
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
this.setItemControlCss();
|
||||
}
|
||||
|
||||
private setItemControlCss() {
|
||||
let item = this._item;
|
||||
let nativeControl = this._native && this._native.ngControl;
|
||||
|
||||
if (item && nativeControl) {
|
||||
item.setCssClass('ng-untouched', nativeControl.untouched);
|
||||
item.setCssClass('ng-touched', nativeControl.touched);
|
||||
item.setCssClass('ng-pristine', nativeControl.pristine);
|
||||
item.setCssClass('ng-dirty', nativeControl.dirty);
|
||||
item.setCssClass('ng-valid', nativeControl.valid);
|
||||
item.setCssClass('ng-invalid', !nativeControl.valid);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._form.deregister(this);
|
||||
}
|
||||
|
||||
@Input()
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(val) {
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
@Input()
|
||||
get type() {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
set type(val) {
|
||||
this._type = 'text';
|
||||
|
||||
if (val) {
|
||||
val = val.toLowerCase();
|
||||
|
||||
if (/password|email|number|search|tel|url|date|datetime|datetime-local|month/.test(val)) {
|
||||
this._type = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ViewChild(NativeInput)
|
||||
private set _nativeInput(nativeInput: NativeInput) {
|
||||
this._native = nativeInput;
|
||||
|
||||
if (this._item && this._item.labelId !== null) {
|
||||
nativeInput.labelledBy(this._item.labelId);
|
||||
}
|
||||
|
||||
nativeInput.valueChange.subscribe(inputValue => {
|
||||
this.onChange(inputValue);
|
||||
});
|
||||
|
||||
this.focusChange(this.hasFocus());
|
||||
nativeInput.focusChange.subscribe(textInputHasFocus => {
|
||||
this.focusChange(textInputHasFocus);
|
||||
if (!textInputHasFocus) {
|
||||
this.onTouched(textInputHasFocus);
|
||||
}
|
||||
});
|
||||
|
||||
this.checkHasValue(nativeInput.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ViewChild(NextInput)
|
||||
private set _nextInput(nextInput: NextInput) {
|
||||
nextInput.focused.subscribe(() => {
|
||||
this._form.tabFocus(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Angular2 Forms API method called by the model (Control) on change to update
|
||||
* the checked value.
|
||||
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34
|
||||
*/
|
||||
writeValue(value) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onChange(val) {
|
||||
this.checkHasValue(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onTouched(val) {}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
hasFocus(): boolean {
|
||||
// check if an input has focus or not
|
||||
return this._native.hasFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
checkHasValue(inputValue) {
|
||||
if (this._item) {
|
||||
this._item.setCssClass('input-has-value', !!(inputValue && inputValue !== ''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
focusChange(inputHasFocus: boolean) {
|
||||
if (this._item) {
|
||||
this._item.setCssClass('input-has-focus', inputHasFocus);
|
||||
}
|
||||
if (!inputHasFocus) {
|
||||
this.deregScrollMove();
|
||||
}
|
||||
}
|
||||
|
||||
private pointerStart(ev) {
|
||||
// input cover touchstart
|
||||
console.debug('scroll assist pointerStart', ev.type);
|
||||
|
||||
if (ev.type === 'touchstart') {
|
||||
this._isTouch = true;
|
||||
}
|
||||
|
||||
if ((this._isTouch || (!this._isTouch && ev.type === 'mousedown')) && this._app.isEnabled()) {
|
||||
// remember where the touchstart/mousedown started
|
||||
this._coord = pointerCoord(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private pointerEnd(ev) {
|
||||
// input cover touchend/mouseup
|
||||
console.debug('scroll assist pointerEnd', ev.type);
|
||||
|
||||
if ((this._isTouch && ev.type === 'mouseup') || !this._app.isEnabled()) {
|
||||
// the app is actively doing something right now
|
||||
// don't try to scroll in the input
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
} else if (this._coord) {
|
||||
// get where the touchend/mouseup ended
|
||||
let endCoord = pointerCoord(ev);
|
||||
|
||||
// focus this input if the pointer hasn't moved XX pixels
|
||||
// and the input doesn't already have focus
|
||||
if (!hasPointerMoved(8, this._coord, endCoord) && !this.hasFocus()) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// begin the input focus process
|
||||
console.debug('initFocus', ev.type);
|
||||
this.initFocus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._coord = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initFocus() {
|
||||
// begin the process of setting focus to the inner input element
|
||||
let scrollView = this._scrollView;
|
||||
|
||||
if (scrollView) {
|
||||
// this input is inside of a scroll view
|
||||
|
||||
// find out if text input should be manually scrolled into view
|
||||
let ele = this._elementRef.nativeElement;
|
||||
let itemEle = closest(ele, 'ion-item');
|
||||
if (itemEle) {
|
||||
ele = itemEle;
|
||||
}
|
||||
|
||||
let scrollData = TextInput.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getContentDimensions(), this._keyboardHeight, this._platform.height());
|
||||
if (scrollData.scrollAmount > -3 && scrollData.scrollAmount < 3) {
|
||||
// the text input is in a safe position that doesn't
|
||||
// require it to be scrolled into view, just set focus now
|
||||
this.setFocus();
|
||||
this.regScrollMove();
|
||||
return;
|
||||
}
|
||||
|
||||
// add padding to the bottom of the scroll view (if needed)
|
||||
scrollView.addScrollPadding(scrollData.scrollPadding);
|
||||
|
||||
// manually scroll the text input to the top
|
||||
// do not allow any clicks while it's scrolling
|
||||
let scrollDuration = getScrollAssistDuration(scrollData.scrollAmount);
|
||||
this._app.setEnabled(false, scrollDuration);
|
||||
this._nav && this._nav.setTransitioning(true, scrollDuration);
|
||||
|
||||
// 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
|
||||
// at this point the native text input still does not have focus
|
||||
this._native.relocate(true, scrollData.inputSafeY);
|
||||
|
||||
// scroll the input into place
|
||||
scrollView.scrollTo(0, scrollData.scrollTo, scrollDuration).then(() => {
|
||||
// the scroll view is in the correct position now
|
||||
// give the native text input focus
|
||||
this._native.relocate(false, 0);
|
||||
|
||||
this.setFocus();
|
||||
|
||||
// all good, allow clicks again
|
||||
this._app.setEnabled(true);
|
||||
this._nav && this._nav.setTransitioning(false);
|
||||
this.regScrollMove();
|
||||
});
|
||||
|
||||
} else {
|
||||
// not inside of a scroll view, just focus it
|
||||
this.setFocus();
|
||||
this.regScrollMove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
clearTextInput() {
|
||||
console.log("Should clear input");
|
||||
//console.log(this.textInput.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private setFocus() {
|
||||
// immediately set focus
|
||||
this._form.setAsFocused(this);
|
||||
|
||||
// set focus on the actual input element
|
||||
this._native.setFocus();
|
||||
|
||||
// ensure the body hasn't scrolled down
|
||||
document.body.scrollTop = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Angular2 Forms API method called by the view (NgControl) to register the
|
||||
* onChange event handler that updates the model (Control).
|
||||
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L27
|
||||
* @param {Function} fn the onChange event handler.
|
||||
*/
|
||||
registerOnChange(fn) { this.onChange = fn; }
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Angular2 Forms API method called by the the view (NgControl) to register
|
||||
* the onTouched event handler that marks model (Control) as touched.
|
||||
* @param {Function} fn onTouched event handler.
|
||||
*/
|
||||
registerOnTouched(fn) { this.onTouched = fn; }
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private regScrollMove() {
|
||||
// register scroll move listener
|
||||
if (this._useAssist && this._scrollView) {
|
||||
setTimeout(() => {
|
||||
this.deregScrollMove();
|
||||
this._deregScroll = this._scrollView.addScrollEventListener(this._scrollMove);
|
||||
}, 80);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private deregScrollMove() {
|
||||
// deregister the scroll move listener
|
||||
this._deregScroll && this._deregScroll();
|
||||
}
|
||||
|
||||
focusNext() {
|
||||
this._form.tabFocus(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
static getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, plaformHeight) {
|
||||
// compute input's Y values relative to the body
|
||||
let inputTop = (inputOffsetTop + scrollViewDimensions.contentTop - scrollViewDimensions.scrollTop);
|
||||
let inputBottom = (inputTop + inputOffsetHeight);
|
||||
|
||||
// compute the safe area which is the viewable content area when the soft keyboard is up
|
||||
let safeAreaTop = scrollViewDimensions.contentTop;
|
||||
let safeAreaHeight = plaformHeight - keyboardHeight - safeAreaTop;
|
||||
safeAreaHeight /= 2;
|
||||
let safeAreaBottom = safeAreaTop + safeAreaHeight;
|
||||
|
||||
let inputTopWithinSafeArea = (inputTop >= safeAreaTop && inputTop <= safeAreaBottom);
|
||||
let inputTopAboveSafeArea = (inputTop < safeAreaTop);
|
||||
let inputTopBelowSafeArea = (inputTop > safeAreaBottom);
|
||||
let inputBottomWithinSafeArea = (inputBottom >= safeAreaTop && inputBottom <= safeAreaBottom);
|
||||
let inputBottomBelowSafeArea = (inputBottom > safeAreaBottom);
|
||||
|
||||
/*
|
||||
Text Input Scroll To Scenarios
|
||||
---------------------------------------
|
||||
1) Input top within safe area, bottom within safe area
|
||||
2) Input top within safe area, bottom below safe area, room to scroll
|
||||
3) Input top above safe area, bottom within safe area, room to scroll
|
||||
4) Input top below safe area, no room to scroll, input smaller than safe area
|
||||
5) Input top within safe area, bottom below safe area, no room to scroll, input smaller than safe area
|
||||
6) Input top within safe area, bottom 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 = {
|
||||
scrollAmount: 0,
|
||||
scrollTo: 0,
|
||||
scrollPadding: 0,
|
||||
inputSafeY: 0
|
||||
};
|
||||
|
||||
if (inputTopWithinSafeArea && inputBottomWithinSafeArea) {
|
||||
// Input top within safe area, bottom within safe area
|
||||
// no need to scroll to a position, it's good as-is
|
||||
return scrollData;
|
||||
}
|
||||
|
||||
// looks like we'll have to do some auto-scrolling
|
||||
if (inputTopBelowSafeArea || inputBottomBelowSafeArea) {
|
||||
// Input top and bottom below safe area
|
||||
// auto scroll the input up so at least the top of it shows
|
||||
|
||||
if (safeAreaHeight > inputOffsetHeight) {
|
||||
// safe area height is taller than the input height, so we
|
||||
// can bring it up the input just enough to show the input bottom
|
||||
scrollData.scrollAmount = Math.round(safeAreaBottom - inputBottom);
|
||||
|
||||
} else {
|
||||
// safe area height is smaller than the input height, so we can
|
||||
// only scroll it up so the input top is at the top of the safe area
|
||||
// however the input bottom will be below the safe area
|
||||
scrollData.scrollAmount = Math.round(safeAreaTop - inputTop);
|
||||
}
|
||||
|
||||
scrollData.inputSafeY = -(inputTop - safeAreaTop) + 4;
|
||||
|
||||
} else if (inputTopAboveSafeArea) {
|
||||
// Input top above safe area
|
||||
// auto scroll the input down so at least the top of it shows
|
||||
scrollData.scrollAmount = Math.round(safeAreaTop - inputTop);
|
||||
|
||||
scrollData.inputSafeY = (safeAreaTop - inputTop) + 4;
|
||||
}
|
||||
|
||||
// figure out where it should scroll to for the best position to the input
|
||||
scrollData.scrollTo = (scrollViewDimensions.scrollTop - scrollData.scrollAmount);
|
||||
|
||||
if (scrollData.scrollAmount < 0) {
|
||||
// when auto-scrolling up, there also needs to be enough
|
||||
// content padding at the bottom of the scroll view
|
||||
// manually add it if there isn't enough scrollable area
|
||||
|
||||
// figure out how many scrollable area is left to scroll up
|
||||
let availablePadding = (scrollViewDimensions.scrollHeight - scrollViewDimensions.scrollTop) - scrollViewDimensions.contentHeight;
|
||||
|
||||
let paddingSpace = availablePadding + scrollData.scrollAmount;
|
||||
if (paddingSpace < 0) {
|
||||
// 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';
|
||||
// window.safeAreaEle.style.height = safeAreaHeight + 'px';
|
||||
// window.safeAreaEle.innerHTML = `
|
||||
// <div>scrollTo: ${scrollData.scrollTo}</div>
|
||||
// <div>scrollAmount: ${scrollData.scrollAmount}</div>
|
||||
// <div>scrollPadding: ${scrollData.scrollPadding}</div>
|
||||
// <div>inputSafeY: ${scrollData.inputSafeY}</div>
|
||||
// <div>scrollHeight: ${scrollViewDimensions.scrollHeight}</div>
|
||||
// <div>scrollTop: ${scrollViewDimensions.scrollTop}</div>
|
||||
// <div>contentHeight: ${scrollViewDimensions.contentHeight}</div>
|
||||
// `;
|
||||
|
||||
return scrollData;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ion-input',
|
||||
template:
|
||||
@ -583,12 +70,12 @@ export class TextInputBase {
|
||||
'<div (touchstart)="pointerStart($event)" (touchend)="pointerEnd($event)" (mousedown)="pointerStart($event)" (mouseup)="pointerEnd($event)" class="input-cover" *ngIf="_useAssist"></div>',
|
||||
directives: [
|
||||
NgIf,
|
||||
forwardRef(() => NextInput),
|
||||
NextInput,
|
||||
NativeInput,
|
||||
Button
|
||||
]
|
||||
})
|
||||
export class TextInput extends TextInputBase {
|
||||
export class TextInput extends InputBase {
|
||||
constructor(
|
||||
config: Config,
|
||||
form: Form,
|
||||
@ -605,6 +92,45 @@ export class TextInput extends TextInputBase {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @name TextArea
|
||||
* @description
|
||||
*
|
||||
* `ion-textarea` is is used for multi-line text inputs. Ionic still
|
||||
* uses an actual `<textarea>` HTML element within the component,
|
||||
* however, with Ionic wrapping the native HTML textarea element then
|
||||
* Ionic is able to better handle the user experience and interactivity.
|
||||
*
|
||||
* Not that `<ion-textarea>` must load its value from the `value` or
|
||||
* `[(ngModel)]` attribute. Unlike the native `<textarea>` element,
|
||||
* `<ion-textarea>` does not support loading its value from the
|
||||
* textarea's inner content.
|
||||
*
|
||||
* When requiring only a single-line text input it's recommended
|
||||
* to use `<ion-input>` instead.
|
||||
*
|
||||
* @property [inset] - The textarea will be inset
|
||||
*
|
||||
* @usage
|
||||
* ```html
|
||||
* <ion-item>
|
||||
* <ion-label>Comments</ion-label>
|
||||
* <ion-textarea></ion-textarea>
|
||||
* </ion-item>
|
||||
*
|
||||
* <ion-item>
|
||||
* <ion-labe stacked>Message</ion-label>
|
||||
* <ion-textarea [(ngModel)]="msg"></ion-textarea>
|
||||
* </ion-item>
|
||||
*
|
||||
* <ion-item>
|
||||
* <ion-labe floating>Description</ion-label>
|
||||
* <ion-textarea></ion-textarea>
|
||||
* </ion-item>
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ion-textarea',
|
||||
template:
|
||||
@ -613,11 +139,11 @@ export class TextInput extends TextInputBase {
|
||||
'<div (touchstart)="pointerStart($event)" (touchend)="pointerEnd($event)" (mousedown)="pointerStart($event)" (mouseup)="pointerEnd($event)" class="input-cover" *ngIf="_useAssist"></div>',
|
||||
directives: [
|
||||
NgIf,
|
||||
forwardRef(() => NextInput),
|
||||
NextInput,
|
||||
NativeInput
|
||||
]
|
||||
})
|
||||
export class TextArea extends TextInputBase {
|
||||
export class TextArea extends InputBase {
|
||||
constructor(
|
||||
config: Config,
|
||||
form: Form,
|
||||
@ -639,12 +165,3 @@ export class TextArea extends TextInputBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SCROLL_ASSIST_SPEED = 0.3;
|
||||
|
||||
function getScrollAssistDuration(distanceToScroll) {
|
||||
//return 3000;
|
||||
distanceToScroll = Math.abs(distanceToScroll);
|
||||
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
|
||||
return Math.min(400, Math.max(150, duration));
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ export class NativeInput {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
element(): HTMLElement {
|
||||
private element(): HTMLInputElement {
|
||||
return this._elementRef.nativeElement;
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,9 @@
|
||||
<ion-list inset>
|
||||
|
||||
<ion-item>
|
||||
<ion-label floating>Floating Label 1</ion-label>
|
||||
<ion-label floating>Floating Label 1: {{ myValues.value1 }}</ion-label>
|
||||
<ion-input [(ngModel)]='myValues.value1'></ion-input>
|
||||
</ion-item>
|
||||
Value: {{ myValues.value1 }}
|
||||
|
||||
<ion-item>
|
||||
<ion-label floating>Floating Label 2</ion-label>
|
||||
@ -23,10 +22,9 @@
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label floating primary>Floating Label 4</ion-label>
|
||||
<ion-label floating primary>Floating Label 4: {{ myValues.value2 }}</ion-label>
|
||||
<ion-textarea [(ngModel)]='myValues.value2'></ion-textarea>
|
||||
</ion-item>
|
||||
Value: {{ myValues.value2 }}
|
||||
|
||||
<ion-item>
|
||||
<ion-label floating secondary>Floating Label 5</ion-label>
|
||||
|
Reference in New Issue
Block a user