chore(build): rename ionic directory to src and update all references in the build process.

This commit is contained in:
Josh Thomas
2016-05-19 13:20:59 -05:00
parent 8470ae04ac
commit c8f760f080
595 changed files with 73 additions and 87 deletions

View File

@@ -0,0 +1,585 @@
import {Directive, Input, Output, EventEmitter, HostListener, ViewChild, ElementRef} from '@angular/core';
import {NgControl} from '@angular/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 {isTrueProperty} from '../../util/util';
import {Label} from '../label/label';
import {pointerCoord, hasPointerMoved, closest, copyInputAttributes} from '../../util/dom';
import {NavController} from '../nav/nav-controller';
import {NativeInput, NextInput} from './native-input';
import {Platform} from '../../platform/platform';
export class InputBase {
protected _coord;
protected _deregScroll;
protected _disabled: boolean = false;
protected _keyboardHeight;
protected _scrollMove: EventListener;
protected _type: string = 'text';
protected _useAssist: boolean;
protected _usePadding: boolean;
protected _value = '';
protected _isTouch: boolean;
protected _autoFocusAssist: string;
protected _autoComplete: string;
protected _autoCorrect: string;
inputControl: NgControl;
@Input() clearInput;
@Input() placeholder: string = '';
@ViewChild(NativeInput) protected _native: NativeInput;
@Output() blur: EventEmitter<Event> = new EventEmitter;
@Output() focus: EventEmitter<Event> = new EventEmitter;
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 = config.getBoolean('scrollAssist', false);
this._usePadding = config.getBoolean('scrollPadding', this._useAssist);
this._keyboardHeight = config.getNumber('keyboardHeight');
this._autoFocusAssist = config.get('autoFocusAssist', 'delay');
this._autoComplete = config.get('autocomplete', 'off');
this._autoCorrect = config.get('autocorrect', 'off');
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|month|time|week/.test(val)) {
this._type = val;
}
}
}
@Input()
get disabled() {
return this._disabled;
}
set disabled(val) {
this._disabled = isTrueProperty(val);
this._item && this._item.setCssClass('item-input-disabled', this._disabled);
this._native && this._native.isDisabled(this._disabled);
}
/**
* @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);
this.checkHasValue(nativeInput.getValue());
if (!textInputHasFocus) {
this.onTouched(textInputHasFocus);
}
});
this.checkHasValue(nativeInput.getValue());
this.disabled = this._disabled;
var ionInputEle: HTMLElement = this._elementRef.nativeElement;
let nativeInputEle: HTMLElement = nativeInput.element();
// copy ion-input attributes to the native input element
copyInputAttributes(ionInputEle, nativeInputEle);
if (ionInputEle.hasAttribute('autofocus')) {
// the ion-input element has the autofocus attributes
ionInputEle.removeAttribute('autofocus');
if (this._autoFocusAssist === 'immediate') {
// config says to immediate focus on the input
// works best on android devices
nativeInputEle.focus();
} else if (this._autoFocusAssist === 'delay') {
// config says to chill out a bit and focus on the input after transitions
// works best on desktop
setTimeout(() => {
nativeInputEle.focus();
}, 650);
}
// traditionally iOS has big issues with autofocus on actual devices
// autoFocus is disabled by default with the iOS mode config
}
// by default set autocomplete="off" unless specified by the input
if (ionInputEle.hasAttribute('autocomplete')) {
this._autoComplete = ionInputEle.getAttribute('autocomplete');
}
nativeInputEle.setAttribute('autocomplete', this._autoComplete);
// by default set autocomplete="off" unless specified by the input
if (ionInputEle.hasAttribute('autocorrect')) {
this._autoCorrect = ionInputEle.getAttribute('autocorrect');
}
nativeInputEle.setAttribute('autocorrect', this._autoCorrect);
}
/**
* @private
*/
@ViewChild(NextInput)
private set _nextInput(nextInput: NextInput) {
if (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(val) {
this._value = val;
this.checkHasValue(val);
}
/**
* @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
var scrollView = this._scrollView;
if (scrollView) {
// this input is inside of a scroll view
// find out if text input should be manually scrolled into view
// get container of this input, probably an ion-item a few nodes up
var ele = this._elementRef.nativeElement;
ele = closest(ele, 'ion-item,[ion-item]') || ele;
var 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;
}
if (this._usePadding) {
// 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
var 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.beginFocus(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.beginFocus(false, 0);
// ensure this is the focused input
this.setFocus();
// all good, allow clicks again
this._app.setEnabled(true);
this._nav && this._nav.setTransitioning(false);
this.regScrollMove();
if (this._usePadding) {
this._scrollView.clearScrollPaddingFocusOut();
}
});
} else {
// not inside of a scroll view, just focus it
this.setFocus();
this.regScrollMove();
}
}
/**
* @private
*/
private setFocus() {
// immediately set focus
this._form.setAsFocused(this);
// set focus on the actual input element
console.debug(`input-base, setFocus ${this._native.element().value}`);
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 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.addScrollListener(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) {
distanceToScroll = Math.abs(distanceToScroll);
let duration = distanceToScroll / SCROLL_ASSIST_SPEED;
return Math.min(400, Math.max(150, duration));
}

View File

@@ -0,0 +1,86 @@
@import "../../globals.ios";
@import "./input";
// iOS Input
// --------------------------------------------------
$text-input-ios-background-color: $list-ios-background-color !default;
$text-input-ios-margin-top: $item-ios-padding-top !default;
$text-input-ios-margin-right: ($item-ios-padding-right / 2) !default;
$text-input-ios-margin-bottom: $item-ios-padding-bottom !default;
$text-input-ios-margin-left: 0 !default;
$text-input-ios-input-clear-icon-width: 30px !default;
$text-input-ios-input-clear-icon-color: rgba(0, 0, 0, .5) !default;
$text-input-ios-input-clear-icon-svg: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path fill='" + $text-input-ios-input-clear-icon-color + "' d='M403.1,108.9c-81.2-81.2-212.9-81.2-294.2,0s-81.2,212.9,0,294.2c81.2,81.2,212.9,81.2,294.2,0S484.3,190.1,403.1,108.9z M352,340.2L340.2,352l-84.4-84.2l-84,83.8L160,339.8l84-83.8l-84-83.8l11.8-11.8l84,83.8l84.4-84.2l11.8,11.8L267.6,256L352,340.2z'/></svg>" !default;
$text-input-ios-input-clear-icon-size: 18px !default;
// iOS Default Input
// --------------------------------------------------
.text-input {
margin: $text-input-ios-margin-top $text-input-ios-margin-right $text-input-ios-margin-bottom $text-input-ios-margin-left;
padding: 0;
width: calc(100% - #{$text-input-ios-margin-right} - #{$text-input-ios-margin-left});
}
// iOS Inset Input
// --------------------------------------------------
.inset-input {
margin: ($item-ios-padding-top / 2) $item-ios-padding-right ($item-ios-padding-bottom / 2) 0;
padding: ($item-ios-padding-top / 2) ($item-ios-padding-right / 2) ($item-ios-padding-bottom / 2) ($item-ios-padding-left / 2);
}
// iOS Stacked & Floating Inputs
// --------------------------------------------------
.item-label-stacked .text-input,
.item-label-floating .text-input {
margin-top: 8px;
margin-bottom: 8px;
margin-left: 0;
width: calc(100% - #{$text-input-ios-margin-right});
}
.item-label-stacked ion-select,
.item-label-floating ion-select {
padding-top: 8px;
padding-bottom: 8px;
padding-left: 0;
}
.item-label-floating .text-input.cloned-input,
.item-label-stacked .text-input.cloned-input {
top: 30px;
}
// iOS Clear Input Icon
// --------------------------------------------------
ion-input[clearInput] {
position: relative;
.text-input {
padding-right: $text-input-ios-input-clear-icon-width;
}
}
.text-input-clear-icon {
@include svg-background-image($text-input-ios-input-clear-icon-svg);
right: ($item-ios-padding-right / 2);
bottom: 0;
width: $text-input-ios-input-clear-icon-width;
height: 34px;
background-size: $text-input-ios-input-clear-icon-size;
}

View File

@@ -0,0 +1,120 @@
@import "../../globals.md";
@import "./input";
// Material Design Input
// --------------------------------------------------
$text-input-md-background-color: $list-md-background-color !default;
$text-input-md-highlight-color: color($colors-md, primary) !default;
$text-input-md-hightlight-color-valid: color($colors-md, secondary) !default;
$text-input-md-hightlight-color-invalid: color($colors-md, danger) !default;
$text-input-md-margin-top: $item-md-padding-top !default;
$text-input-md-margin-right: ($item-md-padding-right / 2) !default;
$text-input-md-margin-bottom: $item-md-padding-bottom !default;
$text-input-md-margin-left: ($item-md-padding-left / 2) !default;
$text-input-md-input-clear-icon-width: 30px !default;
$text-input-md-input-clear-icon-color: #5b5b5b !default;
$text-input-md-input-clear-icon-svg: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><polygon fill='" + $text-input-md-input-clear-icon-color + "' points='405,136.798 375.202,107 256,226.202 136.798,107 107,136.798 226.202,256 107,375.202 136.798,405 256,285.798 375.202,405 405,375.202 285.798,256'/></svg>" !default;
$text-input-md-input-clear-icon-size: 22px !default;
// Material Design Default Input
// --------------------------------------------------
.text-input {
margin: $text-input-md-margin-top $text-input-md-margin-right $text-input-md-margin-bottom $text-input-md-margin-left;
padding: 0;
width: calc(100% - #{$text-input-md-margin-right} - #{$text-input-md-margin-left});
}
// Material Design Inset Input
// --------------------------------------------------
.inset-input {
margin: ($item-md-padding-top / 2) $item-md-padding-right ($item-md-padding-bottom / 2) $item-md-padding-left;
padding: ($item-md-padding-top / 2) ($item-md-padding-right / 2) ($item-md-padding-bottom / 2) ($item-md-padding-left / 2);
}
// Material Design Highlighted Input
// --------------------------------------------------
ion-input::after {
position: absolute;
right: 0;
bottom: 0;
left: $item-md-padding-left;
border-bottom-width: 2px;
border-bottom-style: solid;
border-bottom-color: transparent;
content: "";
}
.input-has-focus::after {
border-bottom-color: $text-input-md-highlight-color;
}
ion-input.ng-valid.input-has-value::after {
border-bottom-color: $text-input-md-hightlight-color-valid;
}
ion-input.ng-invalid.ng-touched::after {
border-bottom-color: $text-input-md-hightlight-color-invalid;
}
// Material Design Stacked & Floating Inputs
// --------------------------------------------------
.item-label-stacked .text-input,
.item-label-floating .text-input {
margin-top: 8px;
margin-bottom: 8px;
margin-left: 0;
width: calc(100% - #{$text-input-md-margin-right});
}
.item-label-stacked ion-select,
.item-label-floating ion-select {
padding-top: 8px;
padding-bottom: 8px;
padding-left: 0;
}
.item-label-floating .text-input.cloned-input {
top: 32px;
}
.item-label-stacked .text-input.cloned-input {
top: 27px;
}
// Material Design Clear Input Icon
// --------------------------------------------------
ion-input[clearInput] {
position: relative;
.text-input {
padding-right: $text-input-md-input-clear-icon-width;
}
}
.text-input-clear-icon {
@include svg-background-image($text-input-md-input-clear-icon-svg);
right: ($item-md-padding-right / 2);
bottom: 4px;
width: $text-input-md-input-clear-icon-width;
background-size: $text-input-md-input-clear-icon-size;
}

View File

@@ -0,0 +1,156 @@
@import "../../globals.core";
// Input/Textarea Wrapper
// --------------------------------------------------
ion-input,
ion-textarea {
position: relative;
display: block;
flex: 1;
width: 100%;
}
.item-input ion-input,
.item-input ion-textarea {
position: static;
}
// Textarea Within An Item
// --------------------------------------------------
.item.item-textarea {
align-items: stretch;
}
// Native Text Input
// --------------------------------------------------
.text-input {
@include placeholder();
display: inline-block;
flex: 1;
width: 92%;
width: calc(100% - 10px);
border: 0;
border-radius: 0;
background: transparent;
-webkit-appearance: none;
}
textarea.text-input {
display: block;
}
.text-input[disabled] {
opacity: .4;
}
input.text-input:-webkit-autofill {
background-color: transparent;
}
.platform-mobile textarea.text-input {
resize: none;
}
// Input Cover: Unfocused
// --------------------------------------------------
// The input cover is the div that actually receives the
// tap/click event when scroll assist is configured to true.
// This make it so the native input element is not clickable.
// This will only show when the scroll assist is configured
// otherwise the .input-cover will not be rendered at all
.input-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
// Input Cover: Focused
// --------------------------------------------------
// When the input has focus, then the input cover should be hidden
.input-has-focus .input-cover {
display: none;
}
.input-has-focus {
pointer-events: none;
}
.input-has-focus input,
.input-has-focus textarea {
pointer-events: auto;
}
// Scroll Assist Input
// --------------------------------------------------
// This input is used to help the app handle
// Next and Previous input tabbing
[next-input] {
position: absolute;
bottom: 1px;
padding: 0;
width: 1px;
height: 1px;
border: 0;
background: transparent;
pointer-events: none;
}
// Clear Input Icon
// --------------------------------------------------
.text-input-clear-icon {
position: absolute;
display: none;
margin: 0;
padding: 0;
background-repeat: no-repeat;
background-position: center;
}
.input-has-value .text-input-clear-icon {
display: block;
}
// Cloned Input
// --------------------------------------------------
.text-input.cloned-input {
position: relative;
top: 0;
pointer-events: none;
}
.item-input:not(.item-label-floating) .text-input.cloned-active {
display: none;
}

View File

@@ -0,0 +1,206 @@
import {Component, Optional, ElementRef, ViewChild, ViewEncapsulation} from '@angular/core';
import {NgControl} from '@angular/common';
import {Config} from '../../config/config';
import {Content} from '../content/content';
import {Form} from '../../util/form';
import {InputBase} from './input-base';
import {IonicApp} from '../app/app';
import {Item} from '../item/item';
import {Label} from '../label/label';
import {NativeInput, NextInput} from './native-input';
import {NavController} from '../nav/nav-controller';
import {Platform} from '../../platform/platform';
/**
* @name Input
* @description
*
* `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.
*
* @property [type] - The HTML input type (text, password, email, number, search, tel, or url)
* @property [clearInput] - A clear icon will appear in the input which clears it
*
* @usage
* ```html
* <ion-item>
* <ion-label>Username</ion-label>
* <ion-input></ion-input>
* </ion-item>
*
* <ion-item>
* <ion-label fixed>Website</ion-label>
* <ion-input type="url"></ion-input>
* </ion-item>
*
* <ion-item>
* <ion-label floating>Email</ion-label>
* <ion-input type="email"></ion-input>
* </ion-item>
*
* <ion-item>
* <ion-label stacked>Phone</ion-label>
* <ion-input type="tel"></ion-input>
* </ion-item>
*
* <ion-item>
* <ion-input placeholder="Username" clearInput></ion-input>
* </ion-item>
* ```
*
* @demo /docs/v2/demos/input/
*/
@Component({
selector: 'ion-input',
template:
'<input [type]="type" [(ngModel)]="_value" (blur)="inputBlurred($event)" (focus)="inputFocused($event)" [placeholder]="placeholder" class="text-input">' +
'<input [type]="type" aria-hidden="true" next-input *ngIf="_useAssist">' +
'<button clear [hidden]="!clearInput" type="button" class="text-input-clear-icon" (click)="clearTextInput()" (mousedown)="clearTextInput()"></button>' +
'<div (touchstart)="pointerStart($event)" (touchend)="pointerEnd($event)" (mousedown)="pointerStart($event)" (mouseup)="pointerEnd($event)" class="input-cover" tappable *ngIf="_useAssist"></div>',
directives: [
NextInput,
NativeInput,
],
encapsulation: ViewEncapsulation.None,
})
export class TextInput extends InputBase {
constructor(
config: Config,
form: Form,
@Optional() item: Item,
app: IonicApp,
platform: Platform,
elementRef: ElementRef,
@Optional() scrollView: Content,
@Optional() nav: NavController,
@Optional() ngControl: NgControl
) {
super(config, form, item, app, platform, elementRef, scrollView, nav, ngControl);
}
/**
* @private
*/
inputBlurred(event) {
this.blur.emit(event);
}
/**
* @private
*/
inputFocused(event) {
this.focus.emit(event);
}
/**
* @private
*/
clearTextInput() {
console.debug('Should clear input');
this._value = '';
this.onChange(this._value);
this.writeValue(this._value);
}
}
/**
* @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 text area element, 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, we recommend using
* `<ion-input>` instead.
*
* @usage
* ```html
* <ion-item>
* <ion-label>Comments</ion-label>
* <ion-textarea></ion-textarea>
* </ion-item>
*
* <ion-item>
* <ion-label stacked>Message</ion-label>
* <ion-textarea [(ngModel)]="msg"></ion-textarea>
* </ion-item>
*
* <ion-item>
* <ion-label floating>Description</ion-label>
* <ion-textarea></ion-textarea>
* </ion-item>
* ```
*
* @demo /docs/v2/demos/textarea/
*/
@Component({
selector: 'ion-textarea',
template:
'<textarea [(ngModel)]="_value" (blur)="inputBlurred($event)" (focus)="inputFocused($event)" [placeholder]="placeholder" class="text-input"></textarea>' +
'<input type="text" aria-hidden="true" next-input *ngIf="_useAssist">' +
'<div (touchstart)="pointerStart($event)" (touchend)="pointerEnd($event)" (mousedown)="pointerStart($event)" (mouseup)="pointerEnd($event)" class="input-cover" tappable *ngIf="_useAssist"></div>',
directives: [
NextInput,
NativeInput
],
encapsulation: ViewEncapsulation.None,
})
export class TextArea extends InputBase {
constructor(
config: Config,
form: Form,
@Optional() item: Item,
app: IonicApp,
platform: Platform,
elementRef: ElementRef,
@Optional() scrollView: Content,
@Optional() nav: NavController,
@Optional() ngControl: NgControl
) {
super(config, form, item, app, platform, elementRef, scrollView, nav, ngControl);
}
/**
* @private
*/
ngOnInit() {
super.ngOnInit();
if (this._item) {
this._item.setCssClass('item-textarea', true);
}
}
/**
* @private
*/
inputBlurred(event) {
this.blur.emit(event);
}
/**
* @private
*/
inputFocused(event) {
this.focus.emit(event);
}
}

View File

@@ -0,0 +1,116 @@
@import "../../globals.wp";
@import "./input";
// Windows Input
// --------------------------------------------------
$text-input-wp-background-color: $list-wp-background-color !default;
$text-input-wp-border-color: $input-wp-border-color !default;
$text-input-wp-border-width: 2px !default;
$text-input-wp-margin-top: $item-wp-padding-top !default;
$text-input-wp-margin-right: ($item-wp-padding-right / 2) !default;
$text-input-wp-margin-bottom: $item-wp-padding-bottom !default;
$text-input-wp-margin-left: ($item-wp-padding-left / 2) !default;
$text-input-wp-padding-vertical: 0 !default;
$text-input-wp-padding-horizontal: 8px !default;
$text-input-wp-line-height: 3rem !default;
$text-input-wp-highlight-color: color($colors-wp, primary) !default;
$text-input-wp-hightlight-color-valid: color($colors-wp, secondary) !default;
$text-input-wp-hightlight-color-invalid: color($colors-wp, danger) !default;
$text-input-wp-input-clear-icon-width: 30px !default;
$text-input-wp-input-clear-icon-color: $input-wp-border-color !default;
$text-input-wp-input-clear-icon-svg: "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><polygon fill='" + $text-input-wp-input-clear-icon-color + "' points='405,136.798 375.202,107 256,226.202 136.798,107 107,136.798 226.202,256 107,375.202 136.798,405 256,285.798 375.202,405 405,375.202 285.798,256'/></svg>" !default;
$text-input-wp-input-clear-icon-size: 22px !default;
// Windows Default Input
// --------------------------------------------------
.text-input {
margin: $text-input-wp-margin-top $text-input-wp-margin-right $text-input-wp-margin-bottom $text-input-wp-margin-left;
padding: $text-input-wp-padding-vertical $text-input-wp-padding-horizontal;
width: calc(100% - #{$text-input-wp-margin-right} - #{$text-input-wp-margin-left});
border: $text-input-wp-border-width solid $text-input-wp-border-color;
line-height: $text-input-wp-line-height;
}
// Windows Inset Input
// --------------------------------------------------
.inset-input {
margin: ($item-wp-padding-top / 2) $item-wp-padding-right ($item-wp-padding-bottom / 2) $item-wp-padding-left;
padding: ($item-wp-padding-top / 2) ($item-wp-padding-right / 2) ($item-wp-padding-bottom / 2) ($item-wp-padding-left / 2);
}
// Windows Highlighted Input
// --------------------------------------------------
.input-has-focus .text-input {
border-color: $text-input-wp-highlight-color;
}
ion-input.ng-valid.input-has-value .text-input {
border-color: $text-input-wp-hightlight-color-valid;
}
ion-input.ng-invalid.ng-touched .text-input {
border-color: $text-input-wp-hightlight-color-invalid;
}
// Windows Stacked & Floating Inputs
// --------------------------------------------------
.item-label-stacked .text-input,
.item-label-floating .text-input,
.item-label-stacked ion-select,
.item-label-floating ion-select {
margin-top: 8px;
margin-bottom: 8px;
margin-left: 0;
width: calc(100% - #{$text-input-wp-margin-right});
}
.item-label-floating .text-input.cloned-input {
top: 32px;
}
.item-label-stacked .text-input.cloned-input {
top: 27px;
}
.item-label-stacked [item-right],
.item-label-floating [item-right] {
align-self: flex-end;
}
// Windows Clear Input Icon
// --------------------------------------------------
ion-input[clearInput] {
position: relative;
.text-input {
padding-right: $text-input-wp-input-clear-icon-width;
}
}
.text-input-clear-icon {
@include svg-background-image($text-input-wp-input-clear-icon-svg);
right: ($item-wp-padding-right / 2);
bottom: 7px;
width: $text-input-wp-input-clear-icon-width;
background-size: $text-input-wp-input-clear-icon-size;
}

View File

@@ -0,0 +1,214 @@
import {Directive, Attribute, ElementRef, Renderer, Input, Output, EventEmitter, HostListener} from '@angular/core';
import {NgControl} from '@angular/common';
import {Config} from '../../config/config';
import {CSS, hasFocus} from '../../util/dom';
/**
* @private
*/
@Directive({
selector: '.text-input'
})
export class NativeInput {
private _relocated: boolean;
private _clone: boolean;
private _blurring: boolean;
private _unrefBlur: Function;
@Output() focusChange: EventEmitter<boolean> = new EventEmitter();
@Output() valueChange: EventEmitter<string> = new EventEmitter();
constructor(
private _elementRef: ElementRef,
private _renderer: Renderer,
config: Config,
public ngControl: NgControl
) {
this._clone = config.getBoolean('inputCloning', false);
this._blurring = config.getBoolean('inputBlurring', false);
}
@HostListener('input', ['$event'])
private _change(ev) {
this.valueChange.emit(ev.target.value);
}
@HostListener('focus')
private _focus() {
var self = this;
self.focusChange.emit(true);
function docTouchEnd(ev) {
var tapped: HTMLElement = ev.target;
if (tapped && self.element()) {
if (tapped.tagName !== 'INPUT' && tapped.tagName !== 'TEXTAREA' && !tapped.classList.contains('input-cover')) {
self.element().blur();
}
}
}
if (self._blurring) {
// automatically blur input if:
// 1) this input has focus
// 2) the newly tapped document element is not an input
console.debug('input blurring enabled');
document.addEventListener('touchend', docTouchEnd, true);
self._unrefBlur = function() {
console.debug('input blurring disabled');
document.removeEventListener('touchend', docTouchEnd, true);
};
}
}
@HostListener('blur')
private _blur() {
this.focusChange.emit(false);
this.hideFocus(false);
this._unrefBlur && this._unrefBlur();
this._unrefBlur = null;
}
labelledBy(val: string) {
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-labelledby', val);
}
isDisabled(val: boolean) {
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', val ? '' : null);
}
setFocus() {
// let's set focus to the element
// but only if it does not already have focus
if (document.activeElement !== this.element()) {
this.element().focus();
}
}
beginFocus(shouldFocus: boolean, inputRelativeY: number) {
if (this._relocated !== shouldFocus) {
var focusedInputEle = this.element();
if (shouldFocus) {
// we should focus into this element
if (this._clone) {
// this platform needs the input to be cloned
// this allows for the actual input to receive the focus from
// the user's touch event, but before it receives focus, it
// moves the actual input to a location that will not screw
// up the app's layout, and does not allow the native browser
// to attempt to scroll the input into place (messing up headers/footers)
// the cloned input fills the area of where native input should be
// while the native input fakes out the browser by relocating itself
// before it receives the actual focus event
var clonedInputEle = cloneInput(focusedInputEle, 'cloned-focus');
focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle);
// move the native input to a location safe to receive focus
// according to the browser, the native input receives focus in an
// area which doesn't require the browser to scroll the input into place
focusedInputEle.style[CSS.transform] = `translate3d(-9999px,${inputRelativeY}px,0)`;
focusedInputEle.style.opacity = '0';
}
// let's now set focus to the actual native element
// at this point it is safe to assume the browser will not attempt
// to scroll the input into view itself (screwing up headers/footers)
this.setFocus();
if (this._clone) {
focusedInputEle.classList.add('cloned-active');
}
} else {
// should remove the focus
if (this._clone) {
// should remove the cloned node
focusedInputEle.classList.remove('cloned-active');
focusedInputEle.style[CSS.transform] = '';
focusedInputEle.style.opacity = '';
removeClone(focusedInputEle, 'cloned-focus');
}
}
this._relocated = shouldFocus;
}
}
hideFocus(shouldHideFocus: boolean) {
let focusedInputEle = this.element();
console.debug(`native input hideFocus, shouldHideFocus: ${shouldHideFocus}, input value: ${focusedInputEle.value}`);
if (shouldHideFocus) {
let clonedInputEle = cloneInput(focusedInputEle, 'cloned-move');
focusedInputEle.classList.add('cloned-active');
focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle);
} else {
focusedInputEle.classList.remove('cloned-active');
removeClone(focusedInputEle, 'cloned-move');
}
}
hasFocus(): boolean {
return hasFocus(this.element());
}
getValue(): string {
return this.element().value;
}
element(): HTMLInputElement {
return this._elementRef.nativeElement;
}
ngOnDestroy() {
this._unrefBlur && this._unrefBlur();
}
}
function cloneInput(focusedInputEle, addCssClass) {
let clonedInputEle = focusedInputEle.cloneNode(true);
clonedInputEle.classList.add('cloned-input');
clonedInputEle.classList.add(addCssClass);
clonedInputEle.setAttribute('aria-hidden', true);
clonedInputEle.removeAttribute('aria-labelledby');
clonedInputEle.tabIndex = -1;
clonedInputEle.style.width = (focusedInputEle.offsetWidth + 10) + 'px';
clonedInputEle.style.height = focusedInputEle.offsetHeight + 'px';
clonedInputEle.value = focusedInputEle.value;
return clonedInputEle;
}
function removeClone(focusedInputEle, queryCssClass) {
let clonedInputEle = focusedInputEle.parentElement.querySelector('.' + queryCssClass);
if (clonedInputEle) {
clonedInputEle.parentNode.removeChild(clonedInputEle);
}
}
/**
* @private
*/
@Directive({
selector: '[next-input]'
})
export class NextInput {
@Output() focused: EventEmitter<boolean> = new EventEmitter();
@HostListener('focus')
receivedFocus() {
console.debug('native-input, next-input received focus');
this.focused.emit(true);
}
}

View File

@@ -0,0 +1,4 @@
it('should clear input', function() {
element(by.css('.e2eClearInput .text-input-clear-icon')).click();
});

View File

@@ -0,0 +1,11 @@
import {App} from 'ionic-angular';
@App({
templateUrl: 'main.html'
})
class E2EApp {
constructor() {
this.myValue = 'value';
}
}

View File

@@ -0,0 +1,20 @@
<ion-toolbar>
<ion-title>Clear Input</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<ion-item>
<ion-label>Text 1:</ion-label>
<ion-input class="e2eClearInput" [(ngModel)]="myValue" clearInput></ion-input>
</ion-item>
</ion-list>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,21 @@
import {App, Page} from '../../../../../ionic';
@Page({
templateUrl: 'main.html'
})
class PageOne {
url;
input1: string = 'Text 1';
onEvent(event) {
console.log("Did Event:", event.type);
}
}
@App({
template: '<ion-nav [root]="root"></ion-nav>'
})
class E2EApp {
root = PageOne;
}

View File

@@ -0,0 +1,92 @@
<ion-toolbar>
<ion-title>Fixed Inline Label Text Input</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<ion-item>
<ion-label fixed>To</ion-label>
<ion-input [(ngModel)]="input1"
(blur)="onEvent($event)"
(input)="onEvent($event)"
(focus)="onEvent($event)">
</ion-input>
</ion-item>
<ion-item>
<ion-label fixed>CC</ion-label>
<ion-select>
<ion-option checked>Admin</ion-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label fixed>From</ion-label>
<ion-input value="Text 3"></ion-input>
<button clear item-right>
<ion-icon name="power"></ion-icon>
</button>
</ion-item>
<ion-item>
<ion-label fixed>Comments</ion-label>
<ion-textarea value="Comment value"
(blur)="onEvent($event)"
(input)="onEvent($event)"
(focus)="onEvent($event)">
</ion-textarea>
</ion-item>
<ion-item>
<ion-icon name="globe" item-left></ion-icon>
<ion-label fixed>Website</ion-label>
<ion-input value="http://ionic.io/" type="url"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="mail" item-left></ion-icon>
<ion-label fixed>Email</ion-label>
<ion-input value="email6@email.com" type="email"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="call" item-left></ion-icon>
<ion-label fixed>Phone</ion-label>
<ion-input value="867-5309" type="tel"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="contact" item-left></ion-icon>
<ion-input placeholder="Placeholder Text"></ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Score</ion-label>
<ion-input value="10" type="number"></ion-input>
<button outline item-right>Update</button>
</ion-item>
<ion-item>
<ion-label fixed>First Name</ion-label>
<ion-input value="Buzz" type="text"></ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Last Name</ion-label>
<ion-input value="Lightyear" type="text"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="create" item-left></ion-icon>
<ion-label fixed>Message</ion-label>
<ion-textarea value="To infinity and beyond"></ion-textarea>
</ion-item>
</ion-list>
<p>{{url}}</p>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,17 @@
import {App} from '../../../../../ionic';
@App({
templateUrl: 'main.html'
})
class E2EApp {
constructor() {
this.myParam = '';
this.myValues = {
value1: 'Dynamic Input',
value2: 'Dynamic Textarea'
};
}
}

View File

@@ -0,0 +1,83 @@
<ion-toolbar><ion-title>Floating Label Text Input</ion-title></ion-toolbar>
<ion-content>
<ion-list>
<ion-item>
<ion-label floating>Params: {{ myParam }}</ion-label>
<ion-input [(ngModel)]="myParam" type="text"></ion-input>
<button clear item-right>
<ion-icon name="settings"></ion-icon>
</button>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 1: {{ myValues.value1 }}</ion-label>
<ion-input [(ngModel)]='myValues.value1'></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 2</ion-label>
<ion-input value="Has Value"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 3</ion-label>
<ion-input type="number"></ion-input>
</ion-item>
<ion-item>
<ion-label floating primary>Floating Label 4: {{ myValues.value2 }}</ion-label>
<ion-textarea [(ngModel)]='myValues.value2'></ion-textarea>
<button outline item-right>
View
</button>
</ion-item>
<ion-item>
<ion-label floating secondary>Floating Label 5</ion-label>
<ion-input type="url"></ion-input>
</ion-item>
<ion-item>
<ion-label floating danger>Floating Label 6</ion-label>
<ion-input type="email"></ion-input>
<ion-avatar item-right>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==">
</ion-avatar>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 7</ion-label>
<ion-textarea></ion-textarea>
<ion-thumbnail item-right>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==">
</ion-thumbnail>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 8</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 9</ion-label>
<ion-input type="number"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Floating Label 10</ion-label>
<ion-textarea></ion-textarea>
</ion-item>
</ion-list>
</ion-content>
<style>
img {
height: 100px;
}
</style>

View File

View File

@@ -0,0 +1,39 @@
import {App} from '../../../../../ionic';
import {FormBuilder, Validators} from '@angular/common';
@App({
templateUrl: 'main.html'
})
class E2EApp {
constructor(fb: FormBuilder) {
this.loginForm = fb.group({
email: ["", Validators.required],
username: [""],
password: ["", Validators.required],
comments: ["", Validators.required]
});
this.login = {
email: 'help@ionic.io',
username: 'admin'
};
this.user = {
username: 'asdf',
password: '82'
};
this.submitted = false;
}
submit(ev, value) {
console.log("Submitted", value);
this.submitted = true;
}
disable() {
this.isTextAreaDisabled = !this.isTextAreaDisabled;
}
}

View File

@@ -0,0 +1,117 @@
<ion-toolbar><ion-title>Form Inputs</ion-title></ion-toolbar>
<ion-content>
<form [ngFormModel]="loginForm" #mf="ngForm" novalidate>
<ion-list>
<ion-item>
<ion-label floating>Email</ion-label>
<ion-input clearInput [(ngModel)]="login.email" ngControl="email" type="email" required></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Username</ion-label>
<ion-input clearInput [(ngModel)]="login.username" ngControl="username"></ion-input>
</ion-item>
<ion-item>
<ion-label>Password</ion-label>
<ion-input clearInput [(ngModel)]="login.password" ngControl="password" type="password" required></ion-input>
</ion-item>
<ion-item>
<ion-label>Comments</ion-label>
<ion-textarea clearInput [(ngModel)]="login.comments" ngControl="comments" required>Comment value</ion-textarea>
</ion-item>
<div padding-left padding-right>
<button block (click)="submit($event, login)">Login</button>
</div>
<div padding-left>
<b>Valid form?:</b> {{ mf.form.valid }}<br>
<b>Submitted form?:</b> {{ submitted }}<br>
<b>Email:</b> {{ login.email }}<br>
<b>Username:</b> {{ login.username }}<br>
<b>Password:</b> {{ login.password }}<br>
<b>Comments:</b> {{ login.comments }}
</div>
</ion-list>
</form>
<form (ngSubmit)="submit($event, user)" #lf="ngForm">
<ion-list>
<ion-item>
<ion-label floating>Username</ion-label>
<ion-input [(ngModel)]="user.username" ngControl="username" required></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Password</ion-label>
<ion-input type="password" [(ngModel)]="user.password" ngControl="password" required></ion-input>
</ion-item>
<div padding-left padding-right>
<button block type="submit">Login</button>
</div>
<div padding-left>
<b>Valid form?:</b> {{ lf.form.valid }}<br>
<b>Submitted form?:</b> {{ submitted }}<br>
<b>Username:</b> {{ user.username }}<br>
<b>Password:</b> {{ user.password }}<br>
</div>
</ion-list>
</form>
<ion-list>
<ion-item>
<ion-label>Email</ion-label>
<ion-input type="email" required></ion-input>
</ion-item>
<ion-item>
<ion-label>Username</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
<ion-label>Password</ion-label>
<ion-input type="password" required></ion-input>
</ion-item>
<ion-item>
<ion-label>Comments</ion-label>
<ion-textarea required>Comment value</ion-textarea>
</ion-item>
</ion-list>
<ion-input placeholder="Stand-alone ion-input"></ion-input>
<ion-input placeholder="Stand-alone textarea"></ion-input>
<ion-list>
<ion-item>
<ion-label>Custom Attrs</ion-label>
<ion-input autocomplete="off"
spellcheck="no"
required
some-weird-attr="value"
accept="sure"
class="no-copy" id="no-copy" checked=checked
value="copy custom attributes"></ion-input>
</ion-item>
<ion-item>
<ion-label>Disabled Input</ion-label>
<ion-input disabled value="Value"></ion-input>
</ion-item>
<ion-item>
<ion-label>Disabled TextArea</ion-label>
<ion-textarea [disabled]="isTextAreaDisabled" value="Value"></ion-textarea>
</ion-item>
</ion-list>
<button (click)="disable()">Disable</button>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,12 @@
import {App} from '../../../../../ionic';
@App({
templateUrl: 'main.html'
})
class E2EApp {
submit(ev) {
}
}

View File

@@ -0,0 +1,98 @@
<ion-toolbar><ion-title>Inline Label Text Input</ion-title></ion-toolbar>
<ion-content>
<form (ng-submit)="submit($event)">
<ion-list>
<ion-item>
<ion-label>To:</ion-label>
<ion-input value="Text 1"></ion-input>
</ion-item>
<ion-item>
<ion-label>CC:</ion-label>
<ion-input value="Text 2"></ion-input>
</ion-item>
<ion-item>
<ion-label>From:</ion-label>
<ion-input value="Text 3"></ion-input>
<button clear item-right>
<ion-icon name="power"></ion-icon>
</button>
</ion-item>
<ion-item>
<ion-label>Comments:</ion-label>
<ion-textarea value="Comment value"></ion-textarea>
</ion-item>
<ion-item>
<ion-icon name="globe" item-left></ion-icon>
<ion-label>Website:</ion-label>
<ion-input value="http://ionic.io/" type="url"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="mail" item-left></ion-icon>
<ion-label>Email:</ion-label>
<ion-input value="email6@email.com" type="email"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="create" item-left></ion-icon>
<ion-label>Feedback:</ion-label>
<ion-textarea placeholder="Placeholder Text"></ion-textarea>
</ion-item>
<ion-item>
<ion-label>More Info:</ion-label>
<ion-input placeholder="Placeholder Text"></ion-input>
<ion-icon name="flag" item-right></ion-icon>
</ion-item>
<ion-item>
<ion-label>Score:</ion-label>
<ion-input value="10" type="number"></ion-input>
<button outline item-right>Update</button>
</ion-item>
<ion-item>
<ion-label>First Name:</ion-label>
<ion-input value="Lightning"></ion-input>
</ion-item>
<ion-item>
<ion-label>Last Name:</ion-label>
<ion-input value="McQueen"></ion-input>
</ion-item>
<ion-item>
<ion-label>Message:</ion-label>
<ion-textarea value="KA-CHOW!"></ion-textarea>
</ion-item>
<ion-item>
<ion-label>Beginning:</ion-label>
<ion-input value="2005-07-30" type="date"></ion-input>
</ion-item>
<ion-item>
<ion-label>Time:</ion-label>
<ion-input value="12:00pm" type="time"></ion-input>
</ion-item>
</ion-list>
<ion-item>
<ion-label>No List</ion-label>
<ion-input value="ListFreeeee"></ion-input>
</ion-item>
</form>
</ion-content>

View File

@@ -0,0 +1,34 @@
import {App} from '../../../../../ionic';
@App({
templateUrl: 'main.html',
config: {
//scrollAssist: true
}
})
class E2EApp {
reload() {
window.location.reload();
}
}
document.addEventListener('click', function(ev) {
console.log(`CLICK, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
});
document.addEventListener('touchstart', function(ev) {
console.log(`TOUCH START, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
});
document.addEventListener('touchend', function(ev) {
console.log(`TOUCH END, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
});
document.addEventListener('focusin', function(ev) {console.log(`CLICK, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
console.log(`FOCUS IN, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
});
document.addEventListener('focusout', function(ev) {console.log(`CLICK, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
console.log(`FOCUS OUT, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
});

View File

@@ -0,0 +1,183 @@
<ion-toolbar primary>
<ion-title>Inset Focus</ion-title>
<ion-buttons end>
<button (click)="reload()">
Reload
</button>
</ion-buttons>
</ion-toolbar>
<ion-content>
<p>Paragraph text with a <a href="#">link</a>.</p>
<ion-list>
<ion-item>
<ion-label>Text 1:</ion-label>
<ion-input></ion-input>
</ion-item>
<ion-item>
Item with button right
<button item-right>Button 1</button>
</ion-item>
<ion-item>
<ion-label id="my-label1">Text 2:</ion-label>
<ion-input value="value"></ion-input>
</ion-item>
<button ion-item>
Button Item
</button>
<ion-item>
<ion-label>Text 3:</ion-label>
<ion-input></ion-input>
<button clear item-right>
<ion-icon name="power"></ion-icon>
</button>
</ion-item>
<ion-item>
<ion-label>Comments:</ion-label>
<ion-textarea>Comment value</ion-textarea>
</ion-item>
<ion-item>
<ion-icon name="globe" item-left></ion-icon>
<ion-label>Website:</ion-label>
<ion-input value="http://ionic.io/" type="url"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="mail" item-left></ion-icon>
<ion-label>Email:</ion-label>
<ion-input value="email6@email.com" type="email"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="create" item-left></ion-icon>
<ion-label>Feedback:</ion-label>
<ion-textarea placeholder="Placeholder Text"></ion-textarea>
</ion-item>
<ion-item>
<ion-label>Toggle</ion-label>
<ion-toggle></ion-toggle>
</ion-item>
<ion-item>
<ion-label>More Info:</ion-label>
<ion-input placeholder="Placeholder Text"></ion-input>
<ion-icon name="flag" item-right></ion-icon>
</ion-item>
<ion-item>
<ion-label>Checkbox</ion-label>
<ion-checkbox></ion-checkbox>
</ion-item>
<ion-item>
<ion-label>Score:</ion-label>
<ion-input value="10" type="number"></ion-input>
<button outline item-right>Update</button>
</ion-item>
<ion-item>
<ion-label>First Name:</ion-label>
<ion-input value="Lightning"></ion-input>
</ion-item>
<ion-item>
<ion-label>Last Name:</ion-label>
<ion-input value="McQueen"></ion-input>
</ion-item>
<ion-item>
<ion-label>Message:</ion-label>
<ion-textarea value="KA-CHOW!"></ion-textarea>
</ion-item>
<ion-item>
Item
</ion-item>
<ion-item>
Item
</ion-item>
</ion-list>
<ion-list radio-group>
<ion-list-header>
Radios
</ion-list-header>
<ion-item>
<ion-label>Radio 1</ion-label>
<ion-radio value="1"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Radio 2</ion-label>
<ion-radio value="2"></ion-radio>
</ion-item>
</ion-list>
<ion-list>
<ion-item>
<ion-label>Bottom input 9:</ion-label>
<ion-input value="bottom input 9"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 8:</ion-label>
<ion-input value="bottom input 8"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 7:</ion-label>
<ion-input value="bottom input 7"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 6:</ion-label>
<ion-input value="bottom input 6"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 5:</ion-label>
<ion-input value="bottom input 5"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 4:</ion-label>
<ion-input value="bottom input 4"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 3:</ion-label>
<ion-input value="bottom input 3"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 2:</ion-label>
<ion-input value="bottom input 2"></ion-input>
</ion-item>
<ion-item>
<ion-label>Bottom input 1:</ion-label>
<ion-input value="bottom input 1"></ion-input>
</ion-item>
</ion-list>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,7 @@
import {App} from '../../../../../ionic';
@App({
templateUrl: 'main.html'
})
class E2EApp {}

View File

@@ -0,0 +1,71 @@
<ion-toolbar>
<ion-title>Inset Inputs</ion-title>
</ion-toolbar>
<ion-content class="outer-content">
<ion-list inset>
<ion-item>
<ion-icon name="search" item-left></ion-icon>
<ion-input value="Input inside an inset list"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="call" item-left></ion-icon>
<ion-input type="tel"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="mail" item-left></ion-icon>
<ion-input placeholder="Placeholder text"></ion-input>
</ion-item>
</ion-list>
<ion-list inset>
<ion-item inset>
<ion-icon name="search" item-left></ion-icon>
<ion-input value="Inset input inside an inset list"></ion-input>
</ion-item>
<ion-item inset>
<ion-input value="Inset input inside an inset list"></ion-input>
<ion-icon name="mic" item-right></ion-icon>
</ion-item>
</ion-list>
<ion-list>
<ion-item inset>
<ion-icon name="search" item-left></ion-icon>
<ion-input value="Inset input inside a list"></ion-input>
<button outline item-right>Search</button>
</ion-item>
</ion-list>
<ion-card>
<ion-item>
<ion-icon name="search" item-left></ion-icon>
<ion-input value="Input inside a card"></ion-input>
</ion-item>
<ion-item>
<ion-input value="Input inside a card"></ion-input>
<ion-icon name="mic" item-right></ion-icon>
</ion-item>
</ion-card>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,7 @@
import {App} from '../../../../../ionic';
@App({
templateUrl: 'main.html'
})
class E2EApp {}

View File

@@ -0,0 +1,61 @@
<ion-toolbar>
<ion-title>Placeholder Label Text Input</ion-title>
</ion-toolbar>
<ion-content class="outer-content">
<ion-list>
<ion-item>
<ion-input placeholder="Text Input Placeholder"></ion-input>
</ion-item>
<ion-item>
<ion-input placeholder="Text Input Placeholder" value="Text Input Value"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="call" item-left></ion-icon>
<ion-input placeholder="Text Input Placeholder"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="call" item-left></ion-icon>
<ion-input placeholder="Text Input Placeholder" value="Text Input Value"></ion-input>
</ion-item>
<ion-item>
<ion-textarea placeholder="Textarea Placeholder"></ion-textarea>
</ion-item>
<ion-item>
<ion-textarea placeholder="ion-textarea Placeholder" value="Textarea value"></ion-textarea>
</ion-item>
</ion-list>
<ion-list inset>
<ion-item>
<ion-input placeholder="Inset List: Text Input Placeholder"></ion-input>
</ion-item>
<ion-item>
<ion-input placeholder="Inset List: Text Input Placeholder" value="Inset List: Text Input Value"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="call" item-left></ion-icon>
<ion-input placeholder="Inset List: Text Input Placeholder"></ion-input>
</ion-item>
<ion-item>
<ion-icon name="call" item-left></ion-icon>
<ion-input placeholder="Inset List: Text Input Placeholder" value="Inset List: Text Input Value"></ion-input>
</ion-item>
</ion-list>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,14 @@
import {App, Page} from '../../../../../ionic';
@App({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
class E2EApp {
rootPage = PageOne;
}
@Page({
templateUrl: 'main.html'
})
class PageOne {}

View File

@@ -0,0 +1,68 @@
<ion-toolbar>
<ion-title>Stacked Label Text Input</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<ion-item>
<ion-label stacked>Label 1</ion-label>
<ion-input value="Text 1"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Label 2</ion-label>
<ion-input value="Text 2"></ion-input>
<button item-right clear><ion-icon name="heart-outline"></ion-icon></button>
<button item-right clear><ion-icon ios="ios-call-outline" md="md-call"></ion-icon></button>
</ion-item>
<ion-item>
<ion-label stacked>Label 3</ion-label>
<ion-input value="3" type="number"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Label 4</ion-label>
<ion-textarea value="Textarea 4"></ion-textarea>
</ion-item>
<ion-item>
<ion-label stacked>Label 5</ion-label>
<ion-input value="http://url5.com/" type="url"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Label 6</ion-label>
<ion-select [(ngModel)]="gender">
<ion-option value="f" checked="true">Female</ion-option>
<ion-option value="m">Male</ion-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label stacked>Label 7</ion-label>
<ion-textarea value="Textarea 7"></ion-textarea>
</ion-item>
<ion-item>
<ion-label stacked>Label 8</ion-label>
<ion-input value="Text 8"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Label 9</ion-label>
<ion-input value="9" type="number"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Label 10</ion-label>
<ion-textarea value="Textarea 10"></ion-textarea>
</ion-item>
</ion-list>
</ion-content>

View File

@@ -0,0 +1,135 @@
import {TextInput} from '../../../../ionic';
export function run() {
it('should scroll, top and bottom below safe area, no room to scroll', () => {
let inputOffsetTop = 350;
let inputOffsetHeight = 35;
let scrollViewDimensions = {
contentTop: 100,
contentHeight: 700,
scrollTop: 30,
scrollHeight: 700
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(-205);
expect(scrollData.scrollTo).toBe(235);
expect(scrollData.scrollPadding).toBe(550);
});
it('should scroll, top and bottom below safe area, room to scroll', () => {
let inputOffsetTop = 350;
let inputOffsetHeight = 35;
let scrollViewDimensions = {
contentTop: 100,
contentHeight: 700,
scrollTop: 30,
scrollHeight: 1000
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(-205);
expect(scrollData.scrollTo).toBe(235);
expect(scrollData.scrollPadding).toBe(0);
});
it('should scroll, top above safe', () => {
// TextInput top within safe area, bottom below safe area, room to scroll
let inputOffsetTop = 100;
let inputOffsetHeight = 33;
let scrollViewDimensions = {
contentTop: 100,
contentHeight: 700,
scrollTop: 250,
scrollHeight: 700
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(150);
expect(scrollData.scrollTo).toBe(100);
expect(scrollData.scrollPadding).toBe(0);
});
it('should scroll, top in safe, bottom below safe, below more than top in, not enough padding', () => {
// TextInput top within safe area, bottom below safe area, room to scroll
let inputOffsetTop = 100;
let inputOffsetHeight = 320;
let scrollViewDimensions = {
contentTop: 100,
contentHeight: 700,
scrollTop: 20,
scrollHeight: 700
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(-80);
expect(scrollData.scrollTo).toBe(100);
expect(scrollData.scrollPadding).toBe(550);
});
it('should scroll, top in safe, bottom below safe, below more than top in, enough padding', () => {
// TextInput top within safe area, bottom below safe area, room to scroll
let inputOffsetTop = 20;
let inputOffsetHeight = 330;
let scrollViewDimensions = {
contentTop: 100,
scrollTop: 0,
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(-20);
expect(scrollData.scrollTo).toBe(20);
expect(scrollData.scrollPadding).toBe(0);
});
it('should scroll, top in safe, bottom below safe, below less than top in, enough padding', () => {
// TextInput top within safe area, bottom below safe area, room to scroll
let inputOffsetTop = 250;
let inputOffsetHeight = 80; // goes 30px below safe area
let scrollViewDimensions = {
contentTop: 100,
scrollTop: 0,
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(-180);
expect(scrollData.scrollTo).toBe(180);
expect(scrollData.scrollPadding).toBe(0);
});
it('should not scroll, top in safe, bottom in safe', () => {
// TextInput top within safe area, bottom within safe area
let inputOffsetTop = 100;
let inputOffsetHeight = 50;
let scrollViewDimensions = {
contentTop: 100,
scrollTop: 0,
keyboardTop: 400
};
let keyboardHeight = 400;
let platformHeight = 800;
let scrollData = TextInput.getScrollData(inputOffsetTop, inputOffsetHeight, scrollViewDimensions, keyboardHeight, platformHeight);
expect(scrollData.scrollAmount).toBe(0);
});
}