mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 04:53:58 +08:00
refactor(input): split TextInput and ItemInput components
Make it easier to start adding other inputs, like select
This commit is contained in:
@ -14,6 +14,7 @@
|
|||||||
"components/checkbox/checkbox.ios",
|
"components/checkbox/checkbox.ios",
|
||||||
"components/content/content.ios",
|
"components/content/content.ios",
|
||||||
"components/item/item.ios",
|
"components/item/item.ios",
|
||||||
|
"components/item-input/item-input.ios",
|
||||||
"components/label/label.ios",
|
"components/label/label.ios",
|
||||||
"components/list/list.ios",
|
"components/list/list.ios",
|
||||||
"components/menu/menu.ios",
|
"components/menu/menu.ios",
|
||||||
@ -22,7 +23,6 @@
|
|||||||
"components/searchbar/searchbar.ios",
|
"components/searchbar/searchbar.ios",
|
||||||
"components/segment/segment.ios",
|
"components/segment/segment.ios",
|
||||||
"components/tabs/tabs.ios",
|
"components/tabs/tabs.ios",
|
||||||
"components/text-input/text-input.ios",
|
|
||||||
"components/toggle/toggle.ios",
|
"components/toggle/toggle.ios",
|
||||||
"components/toolbar/toolbar.ios";
|
"components/toolbar/toolbar.ios";
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"components/checkbox/checkbox.md",
|
"components/checkbox/checkbox.md",
|
||||||
"components/content/content.md",
|
"components/content/content.md",
|
||||||
"components/item/item.md",
|
"components/item/item.md",
|
||||||
|
"components/item-input/item-input.md",
|
||||||
"components/label/label.md",
|
"components/label/label.md",
|
||||||
"components/list/list.md",
|
"components/list/list.md",
|
||||||
"components/menu/menu.md",
|
"components/menu/menu.md",
|
||||||
@ -23,6 +24,5 @@
|
|||||||
"components/searchbar/searchbar.md",
|
"components/searchbar/searchbar.md",
|
||||||
"components/segment/segment.md",
|
"components/segment/segment.md",
|
||||||
"components/tabs/tabs.md",
|
"components/tabs/tabs.md",
|
||||||
"components/text-input/text-input.md",
|
|
||||||
"components/toggle/toggle.md",
|
"components/toggle/toggle.md",
|
||||||
"components/toolbar/toolbar.md";
|
"components/toolbar/toolbar.md";
|
||||||
|
7
ionic/components/app/normalize.scss
vendored
7
ionic/components/app/normalize.scss
vendored
@ -94,6 +94,13 @@ textarea {
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: auto;
|
||||||
|
overflow: auto;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
form,
|
form,
|
||||||
input,
|
input,
|
||||||
optgroup,
|
optgroup,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@import "../../globals.ios";
|
@import "../../globals.ios";
|
||||||
@import "./text-input";
|
@import "./item-input";
|
||||||
|
|
||||||
// iOS Text Input
|
// iOS Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
$text-input-ios-background-color: $list-ios-background-color !default;
|
$text-input-ios-background-color: $list-ios-background-color !default;
|
@ -1,7 +1,7 @@
|
|||||||
@import "../../globals.md";
|
@import "../../globals.md";
|
||||||
@import "./text-input";
|
@import "./item-input";
|
||||||
|
|
||||||
// Material Design Text Input
|
// Material Design Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
$text-input-md-background-color: $list-md-background-color !default;
|
$text-input-md-background-color: $list-md-background-color !default;
|
@ -1,12 +1,6 @@
|
|||||||
@import "../../globals.core";
|
@import "../../globals.core";
|
||||||
|
|
||||||
// Text Input
|
// Input
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
$text-input-textarea-resize: none !default;
|
|
||||||
|
|
||||||
|
|
||||||
// All Inputs in ion-input
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
.item-input {
|
.item-input {
|
||||||
@ -21,6 +15,15 @@ input.item-input:-webkit-autofill {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
@include placeholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-mobile textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Scroll Assist
|
// Scroll Assist
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@ -43,25 +46,6 @@ input.item-input:-webkit-autofill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Input textarea
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
height: auto;
|
|
||||||
overflow: auto;
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform-mobile textarea {
|
|
||||||
resize: $text-input-textarea-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
textarea {
|
|
||||||
@include placeholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear Input Icon
|
// Clear Input Icon
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
534
ionic/components/item-input/item-input.ts
Normal file
534
ionic/components/item-input/item-input.ts
Normal file
@ -0,0 +1,534 @@
|
|||||||
|
import {Component, Directive, Attribute, forwardRef, Host, Optional, ElementRef, Renderer, Input, ContentChild, ContentChildren, HostListener} from 'angular2/core';
|
||||||
|
import {NgIf} from 'angular2/common';
|
||||||
|
|
||||||
|
import {NavController} from '../nav/nav-controller';
|
||||||
|
import {Config} from '../../config/config';
|
||||||
|
import {Form} from '../../util/form';
|
||||||
|
import {Label} from '../label/label';
|
||||||
|
import {TextInput} from '../text-input/text-input';
|
||||||
|
import {IonicApp} from '../app/app';
|
||||||
|
import {Content} from '../content/content';
|
||||||
|
import {pointerCoord, hasPointerMoved} from '../../util/dom';
|
||||||
|
import {Platform} from '../../platform/platform';
|
||||||
|
import {Button} from '../button/button';
|
||||||
|
import {Icon} from '../icon/icon';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Input
|
||||||
|
* @module ionic
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* `ion-input` is a generic wrapper for both inputs and textareas. You can give `ion-input` attributes to tell it how to handle a child `ion-label` component.
|
||||||
|
*
|
||||||
|
* @property [fixed-label] - a persistant label that sits next the the input
|
||||||
|
* @property [floating-label] - a label that will float about the input if the input is empty of looses focus
|
||||||
|
* @property [stacked-label] - A stacked label will always appear on top of the input
|
||||||
|
* @property [inset] - The input will be inset
|
||||||
|
* @property [clearInput] - A clear icon will appear in the input which clears it
|
||||||
|
*
|
||||||
|
* @usage
|
||||||
|
* ```html
|
||||||
|
* <ion-input>
|
||||||
|
* <ion-label>Username</ion-label>
|
||||||
|
* <input type="text" value="">
|
||||||
|
* </ion-input>
|
||||||
|
*
|
||||||
|
* <ion-input clearInput>
|
||||||
|
* <input type="text" placeholder="Username">
|
||||||
|
* </ion-input>
|
||||||
|
*
|
||||||
|
* <ion-input fixed-label>
|
||||||
|
* <ion-label>Username</ion-label>
|
||||||
|
* <input type="text" value="">
|
||||||
|
* </ion-input>
|
||||||
|
*
|
||||||
|
* <ion-input floating-label>
|
||||||
|
* <ion-label>Username</ion-label>
|
||||||
|
* <input type="text" value="">
|
||||||
|
* </ion-input>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ion-input',
|
||||||
|
host: {
|
||||||
|
'(touchstart)': 'pointerStart($event)',
|
||||||
|
'(touchend)': 'pointerEnd($event)',
|
||||||
|
'(mouseup)': 'pointerEnd($event)',
|
||||||
|
'class': 'item',
|
||||||
|
'[class.ng-untouched]': 'hasClass("ng-untouched")',
|
||||||
|
'[class.ng-touched]': 'hasClass("ng-touched")',
|
||||||
|
'[class.ng-pristine]': 'hasClass("ng-pristine")',
|
||||||
|
'[class.ng-dirty]': 'hasClass("ng-dirty")',
|
||||||
|
'[class.ng-valid]': 'hasClass("ng-valid")',
|
||||||
|
'[class.ng-invalid]': 'hasClass("ng-invalid")'
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
'<div class="item-inner">' +
|
||||||
|
'<ng-content></ng-content>' +
|
||||||
|
'<input [type]="type" aria-hidden="true" scroll-assist *ngIf="_assist">' +
|
||||||
|
'<button clear *ngIf="clearInput && value" class="text-input-clear-icon" (click)="clearTextInput()" (mousedown)="clearTextInput()"></button>' +
|
||||||
|
'</div>',
|
||||||
|
directives: [NgIf, forwardRef(() => InputScrollAssist), TextInput, Button]
|
||||||
|
})
|
||||||
|
export class ItemInput {
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@Input() clearInput: any;
|
||||||
|
value: string = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
config: Config,
|
||||||
|
private _form: Form,
|
||||||
|
private _renderer: Renderer,
|
||||||
|
private _elementRef: ElementRef,
|
||||||
|
private _app: IonicApp,
|
||||||
|
private _platform: Platform,
|
||||||
|
@Optional() @Host() private _scrollView: Content,
|
||||||
|
@Optional() private _nav: NavController,
|
||||||
|
@Attribute('floating-label') isFloating: string,
|
||||||
|
@Attribute('stacked-label') isStacked: string,
|
||||||
|
@Attribute('fixed-label') isFixed: string,
|
||||||
|
@Attribute('inset') isInset: string
|
||||||
|
) {
|
||||||
|
_form.register(this);
|
||||||
|
|
||||||
|
this.type = null;
|
||||||
|
this.lastTouch = 0;
|
||||||
|
|
||||||
|
// make more gud with pending @Attributes API
|
||||||
|
this.displayType = (isFloating === '' ? 'floating' : (isStacked === '' ? 'stacked' : (isFixed === '' ? 'fixed' : (isInset === '' ? 'inset' : null))));
|
||||||
|
|
||||||
|
this._assist = config.get('scrollAssist');
|
||||||
|
this.keyboardHeight = config.get('keyboardHeight');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@ContentChild(TextInput)
|
||||||
|
set _setInput(textInput) {
|
||||||
|
if (textInput) {
|
||||||
|
textInput.addClass('item-input');
|
||||||
|
if (this.displayType) {
|
||||||
|
textInput.addClass(this.displayType + '-input');
|
||||||
|
}
|
||||||
|
this.input = textInput;
|
||||||
|
this.type = textInput.type;
|
||||||
|
|
||||||
|
this.hasValue(this.input.value);
|
||||||
|
textInput.valueChange.subscribe(inputValue => {
|
||||||
|
this.hasValue(inputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.focusChange(this.hasFocus());
|
||||||
|
textInput.focusChange.subscribe(textInputHasFocus => {
|
||||||
|
this.focusChange(textInputHasFocus);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@ContentChild(Label)
|
||||||
|
set _setLabel(label) {
|
||||||
|
if (label && this.displayType) {
|
||||||
|
label.addClass(this.displayType + '-label');
|
||||||
|
}
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@ContentChildren(Button)
|
||||||
|
set _buttons(buttons) {
|
||||||
|
buttons.toArray().forEach(button => {
|
||||||
|
if (!button.isItem) {
|
||||||
|
button.addClass('item-button');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@ContentChildren(Icon)
|
||||||
|
set _icons(icons) {
|
||||||
|
icons.toArray().forEach(icon => {
|
||||||
|
icon.addClass('item-icon');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* On Initialization check for attributes
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
let clearInput = this.clearInput;
|
||||||
|
if (typeof clearInput === 'string') {
|
||||||
|
this.clearInput = (clearInput === '' || clearInput === 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ngAfterViewInit() {
|
||||||
|
let self = this;
|
||||||
|
if (self.input && self.label) {
|
||||||
|
// if there is an input and a label
|
||||||
|
// then give the label an ID
|
||||||
|
// and tell the input the ID of who it's labelled by
|
||||||
|
self.input.labelledBy(self.label.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scrollMove = function(ev) {
|
||||||
|
if (!(self._nav && self._nav.isTransitioning())) {
|
||||||
|
self.deregMove();
|
||||||
|
|
||||||
|
if (self.hasFocus()) {
|
||||||
|
self.input.hideFocus(true);
|
||||||
|
self._scrollView.onScrollEnd(function() {
|
||||||
|
self.input.hideFocus(false);
|
||||||
|
|
||||||
|
if (self.hasFocus()) {
|
||||||
|
self.regMove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
clearTextInput() {
|
||||||
|
console.log("Should clear input");
|
||||||
|
//console.log(this.textInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
pointerStart(ev) {
|
||||||
|
if (this._assist && this._app.isEnabled()) {
|
||||||
|
// remember where the touchstart/mousedown started
|
||||||
|
this.startCoord = pointerCoord(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
pointerEnd(ev) {
|
||||||
|
if (!this._app.isEnabled()) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
} else if (this._assist && ev.type === 'touchend') {
|
||||||
|
// 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.startCoord, endCoord) && !this.hasFocus()) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
this.initFocus();
|
||||||
|
|
||||||
|
// temporarily prevent mouseup's from focusing
|
||||||
|
this.lastTouch = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this.lastTouch + 999 < Date.now()) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
this.setFocus();
|
||||||
|
this.regMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
initFocus() {
|
||||||
|
// begin the process of setting focus to the inner input element
|
||||||
|
|
||||||
|
let scrollView = this._scrollView;
|
||||||
|
|
||||||
|
if (scrollView && this._assist) {
|
||||||
|
// 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 scrollData = ItemInput.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getDimensions(), 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.regMove();
|
||||||
|
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.input.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.input.relocate(false);
|
||||||
|
|
||||||
|
// all good, allow clicks again
|
||||||
|
this._app.setEnabled(true);
|
||||||
|
this._nav && this._nav.setTransitioning(false);
|
||||||
|
this.regMove();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// not inside of a scroll view, just focus it
|
||||||
|
this.setFocus();
|
||||||
|
this.regMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setFocus() {
|
||||||
|
if (this.input) {
|
||||||
|
this._form.setAsFocused(this);
|
||||||
|
|
||||||
|
// set focus on the actual input element
|
||||||
|
this.input.setFocus();
|
||||||
|
|
||||||
|
// ensure the body hasn't scrolled down
|
||||||
|
document.body.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
regMove() {
|
||||||
|
if (this._assist && this._scrollView) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.deregMove();
|
||||||
|
this.deregScroll = this._scrollView.addScrollEventListener(this.scrollMove);
|
||||||
|
}, 80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
deregMove() {
|
||||||
|
this.deregScroll && this.deregScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
focusChange(inputHasFocus) {
|
||||||
|
this._renderer.setElementClass(this._elementRef, 'input-focused', inputHasFocus);
|
||||||
|
if (!inputHasFocus) {
|
||||||
|
this.deregMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
hasFocus() {
|
||||||
|
return !!this.input && this.input.hasFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
hasValue(inputValue) {
|
||||||
|
let inputHasValue = !!(inputValue && inputValue !== '');
|
||||||
|
this._renderer.setElementClass(this._elementRef, 'input-has-value', inputHasValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* This function is used to add the Angular css classes associated with inputs in forms
|
||||||
|
*/
|
||||||
|
hasClass(className) {
|
||||||
|
this.input && this.input.hasClass(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.deregMove();
|
||||||
|
this._form.deregister(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[scroll-assist]'
|
||||||
|
})
|
||||||
|
class InputScrollAssist {
|
||||||
|
|
||||||
|
constructor(private _form: Form, private _input: ItemInput) {}
|
||||||
|
|
||||||
|
@HostListener('focus')
|
||||||
|
receivedFocus() {
|
||||||
|
this._form.focusNext(this._input);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCROLL_ASSIST_SPEED = 0.4;
|
||||||
|
|
||||||
|
function getScrollAssistDuration(distanceToScroll) {
|
||||||
|
//return 3000;
|
||||||
|
distanceToScroll = Math.abs(distanceToScroll);
|
||||||
|
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
|
||||||
|
return Math.min(400, Math.max(100, duration));
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import {TextInput} from 'ionic/ionic';
|
import {ItemInput} from 'ionic/ionic';
|
||||||
|
|
||||||
export function run() {
|
export function run() {
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.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);
|
||||||
@ -33,7 +33,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.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);
|
||||||
@ -53,7 +53,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
|
|
||||||
expect(scrollData.scrollAmount).toBe(150);
|
expect(scrollData.scrollAmount).toBe(150);
|
||||||
expect(scrollData.scrollTo).toBe(100);
|
expect(scrollData.scrollTo).toBe(100);
|
||||||
@ -73,7 +73,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.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);
|
||||||
@ -91,7 +91,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.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);
|
||||||
@ -109,7 +109,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.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);
|
||||||
@ -128,7 +128,7 @@ export function run() {
|
|||||||
let keyboardHeight = 400;
|
let keyboardHeight = 400;
|
||||||
let platformHeight = 800;
|
let platformHeight = 800;
|
||||||
|
|
||||||
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
let scrollData = ItemInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
|
||||||
expect(scrollData.scrollAmount).toBe(0);
|
expect(scrollData.scrollAmount).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
@ -1,16 +1,6 @@
|
|||||||
import {Component, Directive, Attribute, forwardRef, Host, Optional, ElementRef, Renderer, Input, Output, EventEmitter, ContentChild, ContentChildren, HostListener} from 'angular2/core';
|
import {Directive, Attribute, ElementRef, Renderer, Input, Output, EventEmitter, HostListener} from 'angular2/core';
|
||||||
import {NgIf} from 'angular2/common';
|
|
||||||
|
|
||||||
import {NavController} from '../nav/nav-controller';
|
import {CSS, hasFocus} from '../../util/dom';
|
||||||
import {Config} from '../../config/config';
|
|
||||||
import {Form} from '../../util/form';
|
|
||||||
import {Label} from '../label/label';
|
|
||||||
import {IonicApp} from '../app/app';
|
|
||||||
import {Content} from '../content/content';
|
|
||||||
import {CSS, hasFocus, pointerCoord, hasPointerMoved} from '../../util/dom';
|
|
||||||
import {Platform} from '../../platform/platform';
|
|
||||||
import {Button} from '../button/button';
|
|
||||||
import {Icon} from '../icon/icon';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,7 +12,7 @@ import {Icon} from '../icon/icon';
|
|||||||
'class': 'text-input'
|
'class': 'text-input'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class TextInputElement {
|
export class TextInput {
|
||||||
@Input() value: string;
|
@Input() value: string;
|
||||||
@Input() ngModel: any;
|
@Input() ngModel: any;
|
||||||
@Output() valueChange: EventEmitter<string> = new EventEmitter();
|
@Output() valueChange: EventEmitter<string> = new EventEmitter();
|
||||||
@ -132,520 +122,6 @@ export class TextInputElement {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name Input
|
|
||||||
* @module ionic
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* `ion-input` is a generic wrapper for both inputs and textareas. You can give `ion-input` attributes to tell it how to handle a child `ion-label` component.
|
|
||||||
*
|
|
||||||
* @property [fixed-label] - a persistant label that sits next the the input
|
|
||||||
* @property [floating-label] - a label that will float about the input if the input is empty of looses focus
|
|
||||||
* @property [stacked-label] - A stacked label will always appear on top of the input
|
|
||||||
* @property [inset] - The input will be inset
|
|
||||||
* @property [clearInput] - A clear icon will appear in the input which clears it
|
|
||||||
*
|
|
||||||
* @usage
|
|
||||||
* ```html
|
|
||||||
* <ion-input>
|
|
||||||
* <ion-label>Username</ion-label>
|
|
||||||
* <input type="text" value="">
|
|
||||||
* </ion-input>
|
|
||||||
*
|
|
||||||
* <ion-input clearInput>
|
|
||||||
* <input type="text" placeholder="Username">
|
|
||||||
* </ion-input>
|
|
||||||
*
|
|
||||||
* <ion-input fixed-label>
|
|
||||||
* <ion-label>Username</ion-label>
|
|
||||||
* <input type="text" value="">
|
|
||||||
* </ion-input>
|
|
||||||
*
|
|
||||||
* <ion-input floating-label>
|
|
||||||
* <ion-label>Username</ion-label>
|
|
||||||
* <input type="text" value="">
|
|
||||||
* </ion-input>
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ion-input',
|
|
||||||
host: {
|
|
||||||
'(touchstart)': 'pointerStart($event)',
|
|
||||||
'(touchend)': 'pointerEnd($event)',
|
|
||||||
'(mouseup)': 'pointerEnd($event)',
|
|
||||||
'class': 'item',
|
|
||||||
'[class.ng-untouched]': 'hasClass("ng-untouched")',
|
|
||||||
'[class.ng-touched]': 'hasClass("ng-touched")',
|
|
||||||
'[class.ng-pristine]': 'hasClass("ng-pristine")',
|
|
||||||
'[class.ng-dirty]': 'hasClass("ng-dirty")',
|
|
||||||
'[class.ng-valid]': 'hasClass("ng-valid")',
|
|
||||||
'[class.ng-invalid]': 'hasClass("ng-invalid")'
|
|
||||||
},
|
|
||||||
template:
|
|
||||||
'<div class="item-inner">' +
|
|
||||||
'<ng-content></ng-content>' +
|
|
||||||
'<input [type]="type" aria-hidden="true" scroll-assist *ngIf="_assist">' +
|
|
||||||
'<button clear *ngIf="clearInput && value" class="text-input-clear-icon" (click)="clearTextInput()" (mousedown)="clearTextInput()"></button>' +
|
|
||||||
'</div>',
|
|
||||||
directives: [NgIf, forwardRef(() => InputScrollAssist), forwardRef(() => TextInputElement), Button]
|
|
||||||
})
|
|
||||||
export class TextInput {
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@Input() clearInput: any;
|
|
||||||
value: string = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
config: Config,
|
|
||||||
private _form: Form,
|
|
||||||
private _renderer: Renderer,
|
|
||||||
private _elementRef: ElementRef,
|
|
||||||
private _app: IonicApp,
|
|
||||||
private _platform: Platform,
|
|
||||||
@Optional() @Host() private _scrollView: Content,
|
|
||||||
@Optional() private _nav: NavController,
|
|
||||||
@Attribute('floating-label') isFloating: string,
|
|
||||||
@Attribute('stacked-label') isStacked: string,
|
|
||||||
@Attribute('fixed-label') isFixed: string,
|
|
||||||
@Attribute('inset') isInset: string
|
|
||||||
) {
|
|
||||||
_form.register(this);
|
|
||||||
|
|
||||||
this.type = 'text';
|
|
||||||
this.lastTouch = 0;
|
|
||||||
|
|
||||||
// make more gud with pending @Attributes API
|
|
||||||
this.displayType = (isFloating === '' ? 'floating' : (isStacked === '' ? 'stacked' : (isFixed === '' ? 'fixed' : (isInset === '' ? 'inset' : null))));
|
|
||||||
|
|
||||||
this._assist = config.get('scrollAssist');
|
|
||||||
this.keyboardHeight = config.get('keyboardHeight');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@ContentChild(TextInputElement)
|
|
||||||
set _setInput(textInputElement) {
|
|
||||||
if (textInputElement) {
|
|
||||||
textInputElement.addClass('item-input');
|
|
||||||
if (this.displayType) {
|
|
||||||
textInputElement.addClass(this.displayType + '-input');
|
|
||||||
}
|
|
||||||
this.input = textInputElement;
|
|
||||||
this.type = textInputElement.type;
|
|
||||||
|
|
||||||
this.hasValue(this.input.value);
|
|
||||||
textInputElement.valueChange.subscribe(inputValue => {
|
|
||||||
this.hasValue(inputValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.focusChange(this.hasFocus());
|
|
||||||
textInputElement.focusChange.subscribe(hasFocus => {
|
|
||||||
this.focusChange(hasFocus);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.error('<input> or <textarea> elements required within <ion-input>')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@ContentChild(Label)
|
|
||||||
set _setLabel(label) {
|
|
||||||
if (label && this.displayType) {
|
|
||||||
label.addClass(this.displayType + '-label');
|
|
||||||
}
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@ContentChildren(Button)
|
|
||||||
set _buttons(buttons) {
|
|
||||||
buttons.toArray().forEach(button => {
|
|
||||||
if (!button.isItem) {
|
|
||||||
button.addClass('item-button');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@ContentChildren(Icon)
|
|
||||||
set _icons(icons) {
|
|
||||||
icons.toArray().forEach(icon => {
|
|
||||||
icon.addClass('item-icon');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* On Initialization check for attributes
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
|
||||||
let clearInput = this.clearInput;
|
|
||||||
if (typeof clearInput === 'string') {
|
|
||||||
this.clearInput = (clearInput === '' || clearInput === 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ngAfterViewInit() {
|
|
||||||
let self = this;
|
|
||||||
if (self.input && self.label) {
|
|
||||||
// if there is an input and a label
|
|
||||||
// then give the label an ID
|
|
||||||
// and tell the input the ID of who it's labelled by
|
|
||||||
self.input.labelledBy(self.label.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scrollMove = function(ev) {
|
|
||||||
if (!(self._nav && self._nav.isTransitioning())) {
|
|
||||||
self.deregMove();
|
|
||||||
|
|
||||||
if (self.hasFocus()) {
|
|
||||||
self.input.hideFocus(true);
|
|
||||||
self._scrollView.onScrollEnd(function() {
|
|
||||||
self.input.hideFocus(false);
|
|
||||||
|
|
||||||
if (self.hasFocus()) {
|
|
||||||
self.regMove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
clearTextInput() {
|
|
||||||
console.log("Should clear input");
|
|
||||||
console.log(this.textInputElement.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
pointerStart(ev) {
|
|
||||||
if (this._assist && this._app.isEnabled()) {
|
|
||||||
// remember where the touchstart/mousedown started
|
|
||||||
this.startCoord = pointerCoord(ev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
pointerEnd(ev) {
|
|
||||||
if (!this._app.isEnabled()) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
} else if (this._assist && ev.type === 'touchend') {
|
|
||||||
// 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.startCoord, endCoord) && !this.hasFocus()) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
this.initFocus();
|
|
||||||
|
|
||||||
// temporarily prevent mouseup's from focusing
|
|
||||||
this.lastTouch = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (this.lastTouch + 999 < Date.now()) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
this.setFocus();
|
|
||||||
this.regMove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
initFocus() {
|
|
||||||
// begin the process of setting focus to the inner input element
|
|
||||||
|
|
||||||
let scrollView = this._scrollView;
|
|
||||||
|
|
||||||
if (scrollView && this._assist) {
|
|
||||||
// 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 scrollData = TextInput.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getDimensions(), 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.regMove();
|
|
||||||
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.input.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.input.relocate(false);
|
|
||||||
|
|
||||||
// all good, allow clicks again
|
|
||||||
this._app.setEnabled(true);
|
|
||||||
this._nav && this._nav.setTransitioning(false);
|
|
||||||
this.regMove();
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// not inside of a scroll view, just focus it
|
|
||||||
this.setFocus();
|
|
||||||
this.regMove();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
setFocus() {
|
|
||||||
if (this.input) {
|
|
||||||
this._form.setAsFocused(this);
|
|
||||||
|
|
||||||
// set focus on the actual input element
|
|
||||||
this.input.setFocus();
|
|
||||||
|
|
||||||
// ensure the body hasn't scrolled down
|
|
||||||
document.body.scrollTop = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
regMove() {
|
|
||||||
if (this._assist && this._scrollView) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.deregMove();
|
|
||||||
this.deregScroll = this._scrollView.addScrollEventListener(this.scrollMove);
|
|
||||||
}, 80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
deregMove() {
|
|
||||||
this.deregScroll && this.deregScroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
focusChange(hasFocus) {
|
|
||||||
this._renderer.setElementClass(this._elementRef, 'input-focused', hasFocus);
|
|
||||||
if (!hasFocus) {
|
|
||||||
this.deregMove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
hasFocus() {
|
|
||||||
return !!this.input && this.input.hasFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
hasValue(inputValue) {
|
|
||||||
let inputHasValue = !!(inputValue && inputValue !== '');
|
|
||||||
this._renderer.setElementClass(this._elementRef, 'input-has-value', inputHasValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* This function is used to add the Angular css classes associated with inputs in forms
|
|
||||||
*/
|
|
||||||
hasClass(className) {
|
|
||||||
this.input && this.input.hasClass(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.deregMove();
|
|
||||||
this._form.deregister(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@Directive({
|
|
||||||
selector: '[scroll-assist]'
|
|
||||||
})
|
|
||||||
class InputScrollAssist {
|
|
||||||
|
|
||||||
constructor(private _form: Form, private _input: TextInput) {}
|
|
||||||
|
|
||||||
@HostListener('focus')
|
|
||||||
receivedFocus() {
|
|
||||||
this._form.focusNext(this._input);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function cloneInput(srcInput, addCssClass) {
|
function cloneInput(srcInput, addCssClass) {
|
||||||
let clonedInputEle = srcInput.cloneNode(true);
|
let clonedInputEle = srcInput.cloneNode(true);
|
||||||
clonedInputEle.classList.add(addCssClass);
|
clonedInputEle.classList.add(addCssClass);
|
||||||
@ -655,12 +131,3 @@ function cloneInput(srcInput, addCssClass) {
|
|||||||
clonedInputEle.tabIndex = -1;
|
clonedInputEle.tabIndex = -1;
|
||||||
return clonedInputEle;
|
return clonedInputEle;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCROLL_ASSIST_SPEED = 0.4;
|
|
||||||
|
|
||||||
function getScrollAssistDuration(distanceToScroll) {
|
|
||||||
//return 3000;
|
|
||||||
distanceToScroll = Math.abs(distanceToScroll);
|
|
||||||
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
|
|
||||||
return Math.min(400, Math.max(100, duration));
|
|
||||||
}
|
|
||||||
|
@ -15,12 +15,13 @@ import {Tabs} from '../components/tabs/tabs';
|
|||||||
import {Tab} from '../components/tabs/tab';
|
import {Tab} from '../components/tabs/tab';
|
||||||
import {List, ListHeader} from '../components/list/list';
|
import {List, ListHeader} from '../components/list/list';
|
||||||
import {Item} from '../components/item/item';
|
import {Item} from '../components/item/item';
|
||||||
|
import {ItemInput} from '../components/item-input/item-input';
|
||||||
import {ItemSliding} from '../components/item/item-sliding';
|
import {ItemSliding} from '../components/item/item-sliding';
|
||||||
import {Toolbar, ToolbarTitle, ToolbarItem} from '../components/toolbar/toolbar';
|
import {Toolbar, ToolbarTitle, ToolbarItem} from '../components/toolbar/toolbar';
|
||||||
import {Icon} from '../components/icon/icon';
|
import {Icon} from '../components/icon/icon';
|
||||||
import {Checkbox} from '../components/checkbox/checkbox';
|
import {Checkbox} from '../components/checkbox/checkbox';
|
||||||
import {Toggle} from '../components/toggle/toggle';
|
import {Toggle} from '../components/toggle/toggle';
|
||||||
import {TextInput, TextInputElement} from '../components/text-input/text-input';
|
import {TextInput} from '../components/text-input/text-input';
|
||||||
import {Label} from '../components/label/label';
|
import {Label} from '../components/label/label';
|
||||||
import {Segment, SegmentButton} from '../components/segment/segment';
|
import {Segment, SegmentButton} from '../components/segment/segment';
|
||||||
import {RadioGroup, RadioButton} from '../components/radio/radio';
|
import {RadioGroup, RadioButton} from '../components/radio/radio';
|
||||||
@ -85,8 +86,8 @@ import {ShowWhen, HideWhen} from '../components/show-hide-when/show-hide-when';
|
|||||||
* - RadioGroup
|
* - RadioGroup
|
||||||
* - RadioButton
|
* - RadioButton
|
||||||
* - Toggle
|
* - Toggle
|
||||||
|
* - ItemInput
|
||||||
* - TextInput
|
* - TextInput
|
||||||
* - TextInputElement
|
|
||||||
* - Label
|
* - Label
|
||||||
*
|
*
|
||||||
* **Nav**
|
* **Nav**
|
||||||
@ -150,8 +151,8 @@ export const IONIC_DIRECTIVES = [
|
|||||||
RadioGroup,
|
RadioGroup,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
ItemInput,
|
||||||
TextInput,
|
TextInput,
|
||||||
TextInputElement,
|
|
||||||
Label,
|
Label,
|
||||||
|
|
||||||
// Nav
|
// Nav
|
||||||
|
Reference in New Issue
Block a user