Revert "Revert "Merge branch 'master' of https://github.com/driftyco/ionic""

This reverts commit 4a6086c1f8.
This commit is contained in:
perry
2016-07-18 19:40:03 -05:00
parent 4a6086c1f8
commit 4f52135c13
86 changed files with 5592 additions and 3938 deletions

View File

@@ -304,6 +304,7 @@ class E2EPage {
</ion-header>
<ion-content padding>
Hi, I'm Bob, and I'm a modal.
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
</ion-content>
`
})

View File

@@ -4,6 +4,7 @@ import { Title } from '@angular/platform-browser';
import { ClickBlock } from '../../util/click-block';
import { Config } from '../../config/config';
import { NavController } from '../nav/nav-controller';
import { isTabs, isNav } from '../nav/nav-controller-base';
import { NavOptions } from '../nav/nav-interfaces';
import { NavPortal } from '../nav/nav-portal';
import { Platform } from '../../platform/platform';
@@ -31,6 +32,11 @@ export class App {
*/
clickBlock: ClickBlock;
/**
* @private
*/
appRoot: AppRoot;
viewDidLoad: EventEmitter<any> = new EventEmitter();
viewWillEnter: EventEmitter<any> = new EventEmitter();
viewDidEnter: EventEmitter<any> = new EventEmitter();
@@ -86,6 +92,17 @@ export class App {
}
}
/**
* @private
*/
setScrollDisabled(disabled: boolean) {
if (!this.appRoot) {
console.error('appRoot is missing, scrolling can not be enabled/disabled');
return;
}
this.appRoot.disableScroll = disabled;
}
/**
* @private
* Boolean if the app is actively enabled or not.
@@ -179,13 +196,7 @@ export class App {
// function used to climb up all parent nav controllers
function navPop(nav: any): Promise<any> {
if (nav) {
if (nav.length && nav.length() > 1) {
// this nav controller has more than one view
// pop the current view on this nav and we're done here
console.debug('app, goBack pop nav');
return nav.pop();
} else if (nav.previousTab) {
if (isTabs(nav)) {
// FYI, using "nav instanceof Tabs" throws a Promise runtime error for whatever reason, idk
// this is a Tabs container
// see if there is a valid previous tab to go to
@@ -195,6 +206,12 @@ export class App {
nav.select(prevTab);
return Promise.resolve();
}
} else if (isNav(nav) && nav.length() > 1) {
// this nav controller has more than one view
// pop the current view on this nav and we're done here
console.debug('app, goBack pop nav');
return nav.pop();
}
// try again using the parent nav (if there is one)
@@ -228,10 +245,9 @@ export class App {
console.debug('app, goBack exitApp');
this._platform.exitApp();
}
} else {
return navPromise;
}
return navPromise;
}
return Promise.resolve();
@@ -282,9 +298,13 @@ export class AppRoot {
@ViewChild('anchor', {read: ViewContainerRef}) private _viewport: ViewContainerRef;
constructor(
private _cmp: UserComponent,
private _cr: ComponentResolver,
private _renderer: Renderer) {}
private _cmp: UserComponent,
private _cr: ComponentResolver,
private _renderer: Renderer,
app: App
) {
app.appRoot = this;
}
ngAfterViewInit() {
// load the user app's root component

View File

@@ -219,7 +219,6 @@ ion-content.js-scroll > scroll-content {
[nav-viewport],
[nav-portal],
[tab-portal],
.nav-decor {
display: none;
}

View File

@@ -6,8 +6,8 @@ import {ionicBootstrap, Config, Animation} from '../../../../../src';
templateUrl: 'main.html'
})
class E2EPage {
duration;
easing;
duration: string;
easing: string;
constructor(config: Config) {
this.duration = '1000';

View File

@@ -1,16 +1,16 @@
import {Component} from '@angular/core';
import {App, Nav, Tabs, Tab, NavOptions, Config, ViewController, Platform} from '../../../../src';
import { Component } from '@angular/core';
import { App, Config, Nav, NavOptions, Platform, Tab, Tabs, ViewController } from '../../../../src';
import { mockNavController, mockTab, mockTabs } from '../../../../src/util/mock-providers';
export function run() {
describe('App', () => {
describe('navPop', () => {
it('should select the previous tab', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -40,8 +40,8 @@ describe('App', () => {
});
it('should pop from the active tab, when tabs is nested is the root nav', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -91,9 +91,9 @@ describe('App', () => {
});
it('should pop the root nav when nested nav has less than 2 views', () => {
let rootNav = mockNav();
let nestedNav = mockNav();
let portal = mockNav();
let rootNav = mockNavController();
let nestedNav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
rootNav.registerChildNav(nestedNav);
nestedNav.parent = rootNav;
@@ -120,9 +120,9 @@ describe('App', () => {
});
it('should pop a view from the nested nav that has more than 1 view', () => {
let rootNav = mockNav();
let nestedNav = mockNav();
let portal = mockNav();
let rootNav = mockNavController();
let nestedNav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(rootNav);
rootNav.registerChildNav(nestedNav);
@@ -149,8 +149,8 @@ describe('App', () => {
});
it('should pop the overlay in the portal of the root nav', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -173,8 +173,8 @@ describe('App', () => {
});
it('should pop the second view in the root nav', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -194,8 +194,8 @@ describe('App', () => {
});
it('should exit app when only one view in the root nav', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -217,8 +217,8 @@ describe('App', () => {
});
it('should not exit app when only one view in the root nav, but navExitApp config set', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -242,8 +242,8 @@ describe('App', () => {
});
it('should not go back if app is not enabled', () => {
let nav = mockNav();
let portal = mockNav();
let nav = mockNavController();
let portal = mockNavController();
app.setPortal(portal);
app.setRootNav(nav);
@@ -276,7 +276,7 @@ describe('App', () => {
describe('getActiveNav', () => {
it('should get active NavController when using tabs with nested nav', () => {
let nav = mockNav();
let nav = mockNavController();
app.setRootNav(nav);
let tabs = mockTabs();
@@ -285,9 +285,9 @@ describe('App', () => {
nav.registerChildNav(tabs);
tab2.setSelected(true);
let nav2 = mockNav();
let nav3 = mockNav();
let nav4 = mockNav();
let nav2 = mockNavController();
let nav3 = mockNavController();
let nav4 = mockNavController();
tab1.registerChildNav(nav4);
tab2.registerChildNav(nav2);
tab2.registerChildNav(nav3);
@@ -296,7 +296,7 @@ describe('App', () => {
});
it('should get active NavController when using tabs, nested in a root nav', () => {
let nav = mockNav();
let nav = mockNavController();
app.setRootNav(nav);
let tabs = mockTabs();
@@ -331,9 +331,9 @@ describe('App', () => {
});
it('should get active NavController when nested 3 deep', () => {
let nav1 = mockNav();
let nav2 = mockNav();
let nav3 = mockNav();
let nav1 = mockNavController();
let nav2 = mockNavController();
let nav3 = mockNavController();
app.setRootNav(nav1);
nav1.registerChildNav(nav2);
@@ -343,8 +343,8 @@ describe('App', () => {
});
it('should get active NavController when nested 2 deep', () => {
let nav1 = mockNav();
let nav2 = mockNav();
let nav1 = mockNavController();
let nav2 = mockNavController();
app.setRootNav(nav1);
nav1.registerChildNav(nav2);
@@ -352,13 +352,13 @@ describe('App', () => {
});
it('should get active NavController when only one nav controller', () => {
let nav = mockNav();
let nav = mockNavController();
app.setRootNav(nav);
expect(app.getActiveNav()).toBe(nav);
});
it('should set/get the root nav controller', () => {
let nav = mockNav();
let nav = mockNavController();
app.setRootNav(nav);
expect(app.getRootNav()).toBe(nav);
});
@@ -443,40 +443,13 @@ describe('App', () => {
var app: App;
var config: Config;
var platform: Platform;
var _cd: any;
function mockNav(): Nav {
return new Nav(null, null, null, config, null, null, null, null, null, null);
}
function mockTabs(): Tabs {
return new Tabs(null, null, null, config, null, null, null);
}
function mockTab(parentTabs: Tabs): Tab {
var tab = new Tab(parentTabs, app, config, null, null, null, null, null, _cd, null);
parentTabs.add(tab);
tab.root = SomePage;
tab.load = function(opts: any, cb: Function) {
cb();
};
return tab;
}
@Component({})
class SomePage {}
beforeEach(() => {
config = new Config();
platform = new Platform();
app = new App(config, platform);
_cd = {
reattach: function(){},
detach: function(){}
};
});
});
}

View File

View File

@@ -0,0 +1,70 @@
import { Component, ViewChild } from '@angular/core';
import { ionicBootstrap, MenuController, NavController, AlertController, Nav, Refresher } from '../../../../../src';
@Component({
templateUrl: 'page1.html'
})
class Page1 {
constructor(private nav: NavController, private alertCtrl: AlertController) {}
presentAlert() {
let alert = this.alertCtrl.create({
title: 'New Friend!',
message: 'Your friend, Obi wan Kenobi, just accepted your friend request!',
cssClass: 'my-alert',
buttons: ['Ok']
});
alert.present();
}
goToPage1() {
this.nav.push(Page1);
}
doRefresh(refresher: Refresher) {
setTimeout(() => {
refresher.complete();
}, 1000);
}
}
@Component({
templateUrl: 'main.html'
})
class E2EPage {
rootPage: any;
changeDetectionCount: number = 0;
pages: Array<{title: string, component: any}>;
@ViewChild(Nav) nav: Nav;
constructor(private menu: MenuController) {
this.rootPage = Page1;
this.pages = [
{ title: 'Page 1', component: Page1 },
{ title: 'Page 2', component: Page1 },
{ title: 'Page 3', component: Page1 },
];
}
openPage(page: any) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component).then(() => {
// wait for the root page to be completely loaded
// then close the menu
this.menu.close();
});
}
}
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>'
})
class E2EApp {
rootPage = E2EPage;
}
ionicBootstrap(E2EApp);

View File

@@ -0,0 +1,159 @@
<ion-menu [content]="content" side="left" persistent="true">
<ion-header>
<ion-toolbar secondary>
<ion-title>Left Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item-sliding *ngFor="let p of pages">
<button ion-item (click)="openPage(p)">
{{p.title}}
</button>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
<button ion-item menuClose="left" class="e2eCloseLeftMenu" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
<button ion-item menuClose="left" detail-none>
Close Menu
</button>
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar secondary>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</ion-menu>
<ion-menu [content]="content" side="right">
<ion-header>
<ion-toolbar danger>
<ion-title>Right Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
{{p.title}}
</button>
<button ion-item menuClose="right" class="e2eCloseRightMenu" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
<button ion-item menuClose="right" detail-none>
Close Menu
</button>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav [root]="rootPage" #content swipeBackEnabled="true"></ion-nav>

View File

@@ -0,0 +1,100 @@
<ion-header>
<ion-navbar>
<button menuToggle="left">
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>
Menu
</ion-title>
<button menuToggle="right" right secondary>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher (ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingText="Pull to refresh..."
refreshingSpinner="bubbles"
refreshingText="Refreshing...">
</ion-refresher-content>
</ion-refresher>
<h3>Page 1</h3>
<ion-list padding>
<ion-item-sliding>
<button ion-item class="e2eContentToggleLeftMenu" menuToggle="left">Toggle Left Menu</button>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-list>
<ion-item-sliding>
<button ion-item class="e2eContentToggleRightMenu" menuToggle="right">Toggle Right Menu</button>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
<ion-item-sliding>
<ion-item>LEFT button</ion-item>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
<ion-item-sliding>
<ion-item>RIGHT button</ion-item>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
<button ion-item (click)="goToPage1()">Push same page</button>
<ion-item>
<ion-label>RANGE</ion-label>
<ion-range></ion-range>
</ion-item>
<ion-item-sliding>
<ion-item>
<ion-label>SLIDING ITEM + RANGE</ion-label>
<ion-range></ion-range>
</ion-item>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
</ion-content>

View File

@@ -1,6 +1,6 @@
import { Directive, ElementRef, Input } from '@angular/core';
import { AppRoot } from '../app/app';
import { DisableScroll, GestureController, GestureDelegate } from '../../gestures/gesture-controller';
import { isTrueProperty } from '../../util/util';
@@ -16,41 +16,21 @@ import { isTrueProperty } from '../../util/util';
},
})
export class Backdrop {
private static nuBackDrops: number = 0;
private static push(appRoot: AppRoot) {
if (this.nuBackDrops === 0) {
appRoot.disableScroll = true;
}
this.nuBackDrops++;
}
private static pop(appRoot: AppRoot) {
if (this.nuBackDrops > 0) {
this.nuBackDrops--;
if (this.nuBackDrops === 0) {
appRoot.disableScroll = false;
}
}
}
private pushed: boolean = false;
private _gestureID: number = null;
@Input() disableScroll = true;
constructor(private _appRoot: AppRoot, private _elementRef: ElementRef) {}
constructor(private _gestureCtrl: GestureController, private _elementRef: ElementRef) {}
ngOnInit() {
if (isTrueProperty(this.disableScroll)) {
Backdrop.push(this._appRoot);
this.pushed = true;
this._gestureID = this._gestureCtrl.newID();
this._gestureCtrl.disableScroll(this._gestureID);
}
}
ngOnDestroy() {
if (this.pushed) {
Backdrop.pop(this._appRoot);
this.pushed = false;
if (this._gestureID) {
this._gestureCtrl.enableScroll(this._gestureID);
}
}

View File

@@ -524,7 +524,7 @@ export class Content extends Ion {
ele = parentEle;
let tabbarEle: HTMLElement;
while (ele && ele.tagName !== 'ION-MODAL' && !ele.classList.contains('tab-subpage')) {
while (ele && ele.tagName !== 'ION-MODAL') {
if (ele.tagName === 'ION-TABS') {
tabbarEle = <HTMLElement>ele.firstElementChild;

View File

@@ -8,7 +8,6 @@ import { Form } from '../../util/form';
import { Item } from '../item/item';
import { merge, isBlank, isPresent, isTrueProperty, isArray, isString } from '../../util/util';
import { dateValueRange, renderDateTime, renderTextFormat, convertFormatToKey, getValueFromFormat, parseTemplate, parseDate, updateDate, DateTimeData, convertDataToISO, daysInMonth, dateSortValue, dateDataSortValue, LocaleData } from '../../util/datetime-util';
import { NavController } from '../nav/nav-controller';
export const DATETIME_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => DateTime), multi: true});

View File

@@ -7,7 +7,7 @@ import {ionicBootstrap, InfiniteScroll, NavController} from '../../../../../src'
})
class E2EPage1 {
@ViewChild(InfiniteScroll) infiniteScroll: InfiniteScroll;
items = [];
items: number[] = [];
enabled: boolean = true;
constructor(private nav: NavController) {
@@ -68,7 +68,7 @@ function getAsyncData(): Promise<any[]> {
return new Promise(resolve => {
setTimeout(() => {
let data = [];
let data: number[] = [];
for (var i = 0; i < 30; i++) {
data.push(i);
}

View File

@@ -10,6 +10,7 @@ import { isTrueProperty } from '../../util/util';
import { Item } from '../item/item';
import { NativeInput, NextInput } from './native-input';
import { NavController } from '../nav/nav-controller';
import { NavControllerBase } from '../nav/nav-controller-base';
import { Platform } from '../../platform/platform';
@@ -27,6 +28,7 @@ export class InputBase {
protected _autoFocusAssist: string;
protected _autoComplete: string;
protected _autoCorrect: string;
protected _nav: NavControllerBase;
inputControl: NgControl;
@@ -44,9 +46,10 @@ export class InputBase {
protected _platform: Platform,
protected _elementRef: ElementRef,
protected _scrollView: Content,
protected _nav: NavController,
nav: NavController,
ngControl: NgControl
) {
this._nav = <NavControllerBase>nav;
this._useAssist = config.getBoolean('scrollAssist', false);
this._usePadding = config.getBoolean('scrollPadding', this._useAssist);
this._keyboardHeight = config.getNumber('keyboardHeight');

View File

@@ -16,6 +16,14 @@ $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;
$text-input-ios-show-focus-highlight: false !default;
$text-input-ios-show-valid-highlight: $text-input-ios-show-focus-highlight !default;
$text-input-ios-show-invalid-highlight: $text-input-ios-show-focus-highlight !default;
$text-input-ios-highlight-color: color($colors-ios, primary) !default;
$text-input-ios-highlight-color-valid: color($colors-ios, secondary) !default;
$text-input-ios-highlight-color-invalid: color($colors-ios, danger) !default;
// iOS Default Input
// --------------------------------------------------
@@ -82,3 +90,61 @@ ion-input[clearInput] {
background-size: $text-input-ios-input-clear-icon-size;
}
// iOS Highlighted Input
// --------------------------------------------------
// Input highlight mixin for focus, valid, and invalid states
@mixin ios-input-highlight($highlight-color) {
border-bottom-color: $highlight-color;
}
// Show the focus highlight when the input has focus
@if ($text-input-ios-show-focus-highlight) {
// In order to get a 2px border we need to add an inset
// box-shadow 1px (this is to avoid the div resizing)
.item-input.input-has-focus .item-inner {
@include ios-input-highlight($text-input-ios-highlight-color);
}
// The last item in a list has a border on the item, not the
// inner item, so add it to the item itself
ion-list .item-input.input-has-focus:last-child {
@include ios-input-highlight($text-input-ios-highlight-color);
.item-inner {
box-shadow: none;
}
}
}
// Show the valid highlight when it has the .ng-valid class and a value
@if ($text-input-ios-show-valid-highlight) {
.item-input.ng-valid.input-has-value:not(.input-has-focus) .item-inner {
@include ios-input-highlight($text-input-ios-highlight-color-valid);
}
ion-list .item-input.ng-valid.input-has-value:not(.input-has-focus):last-child {
@include ios-input-highlight($text-input-ios-highlight-color-valid);
.item-inner {
box-shadow: none;
}
}
}
// Show the invalid highlight when it has the invalid class and has been touched
@if ($text-input-ios-show-invalid-highlight) {
.item-input.ng-invalid.ng-touched:not(.input-has-focus) .item-inner {
@include ios-input-highlight($text-input-ios-highlight-color-invalid);
}
ion-list .item-input.ng-invalid.ng-touched:not(.input-has-focus):last-child {
@include ios-input-highlight($text-input-ios-highlight-color-invalid);
.item-inner {
box-shadow: none;
}
}
}

View File

@@ -5,9 +5,6 @@
// --------------------------------------------------
$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;
@@ -19,8 +16,13 @@ $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;
$text-input-md-show-success-highlight: true !default;
$text-input-md-show-error-highlight: true !default;
$text-input-md-show-focus-highlight: true !default;
$text-input-md-show-valid-highlight: $text-input-md-show-focus-highlight !default;
$text-input-md-show-invalid-highlight: $text-input-md-show-focus-highlight !default;
$text-input-md-highlight-color: color($colors-md, primary) !default;
$text-input-md-highlight-color-valid: color($colors-md, secondary) !default;
$text-input-md-highlight-color-invalid: color($colors-md, danger) !default;
// Material Design Default Input
@@ -46,42 +48,57 @@ $text-input-md-show-error-highlight: true !default;
// Material Design Highlighted Input
// --------------------------------------------------
.item-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 highlight mixin for focus, valid, and invalid states
@mixin md-input-highlight($highlight-color) {
border-bottom-color: $highlight-color;
box-shadow: inset 0 -1px 0 0 $highlight-color;
}
.item-input.input-has-focus::after {
border-bottom-color: $text-input-md-highlight-color;
}
// Show the focus highlight when the input has focus
@if ($text-input-md-show-focus-highlight) {
// In order to get a 2px border we need to add an inset
// box-shadow 1px (this is to avoid the div resizing)
.item-input.input-has-focus .item-inner {
@include md-input-highlight($text-input-md-highlight-color);
}
@if($text-input-md-show-success-highlight) {
.item-input.ng-valid.input-has-value {
&::after {
border-bottom-color: $text-input-md-hightlight-color-valid;
}
// The last item in a list has a border on the item, not the
// inner item, so add it to the item itself
ion-list .item-input.input-has-focus:last-child {
@include md-input-highlight($text-input-md-highlight-color);
&.input-has-focus::after {
border-bottom-color: $text-input-md-highlight-color;
.item-inner {
box-shadow: none;
}
}
}
@if($text-input-md-show-error-highlight) {
.item-input.ng-invalid.ng-touched {
&::after {
border-bottom-color: $text-input-md-hightlight-color-invalid;
}
// Show the valid highlight when it has the .ng-valid class and a value
@if ($text-input-md-show-valid-highlight) {
.item-input.ng-valid.input-has-value:not(.input-has-focus) .item-inner {
@include md-input-highlight($text-input-md-highlight-color-valid);
}
&.input-has-focus::after {
border-bottom-color: $text-input-md-highlight-color;
ion-list .item-input.ng-valid.input-has-value:not(.input-has-focus):last-child {
@include md-input-highlight($text-input-md-highlight-color-valid);
.item-inner {
box-shadow: none;
}
}
}
// Show the invalid highlight when it has the invalid class and has been touched
@if ($text-input-md-show-invalid-highlight) {
.item-input.ng-invalid.ng-touched:not(.input-has-focus) .item-inner {
@include md-input-highlight($text-input-md-highlight-color-invalid);
}
ion-list .item-input.ng-invalid.ng-touched:not(.input-has-focus):last-child {
@include md-input-highlight($text-input-md-highlight-color-invalid);
.item-inner {
box-shadow: none;
}
}
}

View File

@@ -16,15 +16,18 @@ $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;
$text-input-wp-show-focus-highlight: true !default;
$text-input-wp-show-valid-highlight: $text-input-wp-show-focus-highlight !default;
$text-input-wp-show-invalid-highlight: $text-input-wp-show-focus-highlight !default;
$text-input-wp-highlight-color: color($colors-wp, primary) !default;
$text-input-wp-highlight-color-valid: color($colors-wp, secondary) !default;
$text-input-wp-highlight-color-invalid: color($colors-wp, danger) !default;
// Windows Default Input
@@ -53,16 +56,25 @@ $text-input-wp-input-clear-icon-size: 22px !default;
// Windows Highlighted Input
// --------------------------------------------------
.input-has-focus .text-input {
border-color: $text-input-wp-highlight-color;
// Show the focus highlight when the input has focus
@if ($text-input-wp-show-focus-highlight) {
.item-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;
// Show the valid highlight when it has the .ng-valid class and a value
@if ($text-input-wp-show-valid-highlight) {
.item-input.ng-valid.input-has-value:not(.input-has-focus) .text-input {
border-color: $text-input-wp-highlight-color-valid;
}
}
ion-input.ng-invalid.ng-touched .text-input {
border-color: $text-input-wp-hightlight-color-invalid;
// Show the invalid highlight when it has the invalid class and has been touched
@if ($text-input-wp-show-invalid-highlight) {
.item-input.ng-invalid.ng-touched:not(.input-has-focus) .text-input {
border-color: $text-input-wp-highlight-color-invalid;
}
}

View File

View File

@@ -0,0 +1,54 @@
import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/common';
import { ionicBootstrap } from '../../../../../src';
@Component({
templateUrl: 'main.html'
})
class E2EPage {
loginForm: any;
login = {
email: 'help@ionic.io',
username: 'admin',
};
submitted: boolean = false;
constructor(fb: FormBuilder) {
this.loginForm = fb.group({
email: ["", Validators.compose([
Validators.required,
this.emailValidator
])],
username: [""],
password: ["", Validators.required],
comments: ["", Validators.required],
inset: ["", Validators.required]
});
}
emailValidator(control: any) {
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
if (!EMAIL_REGEXP.test(control.value)) {
return {invalidEmail: true};
}
}
submit(ev: UIEvent, value: any) {
console.log("Submitted", value);
this.submitted = true;
}
}
@Component({
template: '<ion-nav [root]="root"></ion-nav>'
})
class E2EApp {
root = E2EPage;
}
ionicBootstrap(E2EApp);

View File

@@ -0,0 +1,105 @@
<ion-header>
<ion-toolbar>
<ion-title>Form Inputs</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<form [formGroup]="loginForm" #mf="ngForm" novalidate>
<ion-list>
<ion-item>
<ion-label stacked>Stacked</ion-label>
<ion-input clearInput [(ngModel)]="login.email" formControlName="email" type="email" required></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Floating</ion-label>
<ion-input clearInput [(ngModel)]="login.username" formControlName="username"></ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Fixed</ion-label>
<ion-input clearInput [(ngModel)]="login.password" formControlName="password" type="password" required></ion-input>
</ion-item>
<ion-item>
<ion-label>Inline</ion-label>
<ion-textarea clearInput [(ngModel)]="login.comments" formControlName="comments" required>Comment value</ion-textarea>
</ion-item>
<ion-item inset>
<ion-label>Inset</ion-label>
<ion-input clearInput [(ngModel)]="login.inset" formControlName="inset" required></ion-input>
</ion-item>
</ion-list>
<ion-list>
<ion-item>
<ion-icon item-left name="logo-buffer"></ion-icon>
<ion-label stacked>Stacked</ion-label>
<ion-input clearInput [(ngModel)]="login.email" formControlName="email" type="email" required></ion-input>
</ion-item>
<ion-item>
<ion-icon item-left name="apps"></ion-icon>
<ion-label floating>Floating</ion-label>
<ion-input clearInput [(ngModel)]="login.username" formControlName="username"></ion-input>
</ion-item>
<ion-item>
<ion-icon item-left name="browsers"></ion-icon>
<ion-label fixed>Fixed</ion-label>
<ion-input clearInput [(ngModel)]="login.password" formControlName="password" type="password" required></ion-input>
</ion-item>
<ion-item>
<ion-icon item-left name="card"></ion-icon>
<ion-label>Inline</ion-label>
<ion-textarea clearInput [(ngModel)]="login.comments" formControlName="comments" required>Comment value</ion-textarea>
</ion-item>
<ion-item inset>
<ion-icon item-left name="checkmark-circle"></ion-icon>
<ion-label>Inset</ion-label>
<ion-input clearInput [(ngModel)]="login.inset" formControlName="inset" required></ion-input>
</ion-item>
</ion-list>
<ion-card>
<ion-list>
<ion-item>
<ion-icon item-left name="logo-buffer"></ion-icon>
<ion-label stacked>Stacked</ion-label>
<ion-input clearInput [(ngModel)]="login.email" formControlName="email" type="email" required></ion-input>
</ion-item>
<ion-item>
<ion-icon item-left name="apps"></ion-icon>
<ion-label floating>Floating</ion-label>
<ion-input clearInput [(ngModel)]="login.username" formControlName="username"></ion-input>
</ion-item>
<ion-item>
<ion-icon item-left name="browsers"></ion-icon>
<ion-label fixed>Fixed</ion-label>
<ion-input clearInput [(ngModel)]="login.password" formControlName="password" type="password" required></ion-input>
</ion-item>
<ion-item>
<ion-icon item-left name="card"></ion-icon>
<ion-label>Inline</ion-label>
<ion-textarea clearInput [(ngModel)]="login.comments" formControlName="comments" required>Comment value</ion-textarea>
</ion-item>
<ion-item inset>
<ion-icon item-left name="checkmark-circle"></ion-icon>
<ion-label>Inset</ion-label>
<ion-input clearInput [(ngModel)]="login.inset" formControlName="inset" required></ion-input>
</ion-item>
</ion-list>
</ion-card>
</form>
</ion-content>

View File

@@ -25,12 +25,13 @@ export class ItemReorderGesture {
private events: UIEventManager = new UIEventManager(false);
constructor(public list: ItemReorder) {
let element = this.list.getNativeElement();
this.events.pointerEvents(element,
this.onDragStart.bind(this),
this.onDragMove.bind(this),
this.onDragEnd.bind(this));
constructor(public reorderList: ItemReorder) {
this.events.pointerEvents({
element: this.reorderList.getNativeElement(),
pointerDown: this.onDragStart.bind(this),
pointerMove: this.onDragMove.bind(this),
pointerUp: this.onDragEnd.bind(this)
});
}
private onDragStart(ev: any): boolean {
@@ -44,7 +45,7 @@ export class ItemReorderGesture {
console.error('ion-reorder does not contain $ionComponent');
return false;
}
this.list.reorderPrepare();
this.reorderList.reorderPrepare();
let item = reorderMark.getReorderNode();
if (!item) {
@@ -60,13 +61,13 @@ export class ItemReorderGesture {
this.lastToIndex = indexForItem(item);
this.windowHeight = window.innerHeight - AUTO_SCROLL_MARGIN;
this.lastScrollPosition = this.list.scrollContent(0);
this.lastScrollPosition = this.reorderList.scrollContent(0);
this.offset = pointerCoord(ev);
this.offset.y += this.lastScrollPosition;
item.classList.add(ITEM_REORDER_ACTIVE);
this.list.reorderStart();
this.reorderList.reorderStart();
return true;
}
@@ -94,7 +95,7 @@ export class ItemReorderGesture {
this.lastToIndex = toIndex;
this.lastYcoord = posY;
this.emptyZone = false;
this.list.reorderMove(fromIndex, toIndex, this.selectedItemHeight);
this.reorderList.reorderMove(fromIndex, toIndex, this.selectedItemHeight);
}
} else {
this.emptyZone = true;
@@ -125,7 +126,7 @@ export class ItemReorderGesture {
} else {
reorderInactive();
}
this.list.reorderEmit(fromIndex, toIndex);
this.reorderList.reorderEmit(fromIndex, toIndex);
}
private itemForCoord(coord: Coordinates): HTMLElement {
@@ -134,9 +135,9 @@ export class ItemReorderGesture {
private scroll(posY: number): number {
if (posY < AUTO_SCROLL_MARGIN) {
this.lastScrollPosition = this.list.scrollContent(-SCROLL_JUMP);
this.lastScrollPosition = this.reorderList.scrollContent(-SCROLL_JUMP);
} else if (posY > this.windowHeight) {
this.lastScrollPosition = this.list.scrollContent(SCROLL_JUMP);
this.lastScrollPosition = this.reorderList.scrollContent(SCROLL_JUMP);
}
return this.lastScrollPosition;
}
@@ -148,17 +149,11 @@ export class ItemReorderGesture {
this.onDragEnd();
this.events.unlistenAll();
this.events = null;
this.list = null;
this.reorderList = null;
}
}
function itemForPosition(x: number, y: number): HTMLElement {
let element = <HTMLElement>document.elementFromPoint(x, y);
if (!element) {
return null;
}
if (element.nodeName !== 'ION-ITEM' && !element.hasAttribute('ion-item')) {
return null;
}
return findReorderItem(element);
}

View File

@@ -1,111 +1,102 @@
import {DragGesture} from '../../gestures/drag-gesture';
import {ItemSliding} from './item-sliding';
import {List} from '../list/list';
import { ItemSliding } from './item-sliding';
import { List } from '../list/list';
import {closest} from '../../util/dom';
import { closest, Coordinates, pointerCoord } from '../../util/dom';
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
import { GestureDelegate, GestureOptions, GesturePriority } from '../../gestures/gesture-controller';
import { PanGesture } from '../../gestures/drag-gesture';
const DRAG_THRESHOLD = 20;
const DRAG_THRESHOLD = 10;
const MAX_ATTACK_ANGLE = 20;
export class ItemSlidingGesture extends DragGesture {
onTap: any;
selectedContainer: ItemSliding = null;
openContainer: ItemSliding = null;
export class ItemSlidingGesture extends PanGesture {
private preSelectedContainer: ItemSliding = null;
private selectedContainer: ItemSliding = null;
private openContainer: ItemSliding = null;
private firstCoordX: number;
private firstTimestamp: number;
constructor(public list: List) {
super(list.getNativeElement(), {
direction: 'x',
threshold: DRAG_THRESHOLD
maxAngle: MAX_ATTACK_ANGLE,
threshold: DRAG_THRESHOLD,
gesture: list.gestureCtrl.create('item-sliding', {
priority: GesturePriority.SlidingItem,
})
});
this.listen();
}
onTapCallback(ev: any) {
if (isFromOptionButtons(ev)) {
return;
canStart(ev: any): boolean {
if (this.selectedContainer) {
return false;
}
let didClose = this.closeOpened();
if (didClose) {
console.debug('tap close sliding item, preventDefault');
ev.preventDefault();
}
}
onDragStart(ev: any): boolean {
let angle = Math.abs(ev.angle);
if (angle > MAX_ATTACK_ANGLE && Math.abs(angle - 180) > MAX_ATTACK_ANGLE) {
// Get swiped sliding container
let container = getContainer(ev);
if (!container) {
this.closeOpened();
return false;
}
if (this.selectedContainer) {
console.debug('onDragStart, another container is already selected');
return false;
}
let container = getContainer(ev);
if (!container) {
console.debug('onDragStart, no itemContainerEle');
return false;
}
// Close open container if it is not the selected one.
if (container !== this.openContainer) {
this.closeOpened();
}
this.selectedContainer = container;
this.openContainer = container;
container.startSliding(ev.center.x);
let coord = pointerCoord(ev);
this.preSelectedContainer = container;
this.firstCoordX = coord.x;
this.firstTimestamp = Date.now();
return true;
}
onDrag(ev: any): boolean {
if (this.selectedContainer) {
this.selectedContainer.moveSliding(ev.center.x);
ev.preventDefault();
}
return;
onDragStart(ev: any) {
ev.preventDefault();
let coord = pointerCoord(ev);
this.selectedContainer = this.openContainer = this.preSelectedContainer;
this.selectedContainer.startSliding(coord.x);
}
onDragMove(ev: any) {
ev.preventDefault();
let coordX = pointerCoord(ev).x;
this.selectedContainer.moveSliding(coordX);
}
onDragEnd(ev: any) {
if (!this.selectedContainer) {
return;
}
ev.preventDefault();
let openAmount = this.selectedContainer.endSliding(ev.velocityX);
let coordX = pointerCoord(ev).x;
let deltaX = (coordX - this.firstCoordX);
let deltaT = (Date.now() - this.firstTimestamp);
let openAmount = this.selectedContainer.endSliding(deltaX / deltaT);
this.selectedContainer = null;
this.preSelectedContainer = null;
}
// TODO: I am not sure listening for a tap event is the best idea
// we should try mousedown/touchstart
if (openAmount === 0) {
this.openContainer = null;
this.off('tap', this.onTap);
this.onTap = null;
} else if (!this.onTap) {
this.onTap = (event: any) => this.onTapCallback(event);
this.on('tap', this.onTap);
}
notCaptured(ev: any) {
this.closeOpened();
}
closeOpened(): boolean {
if (!this.openContainer) {
return false;
}
this.openContainer.close();
this.openContainer = null;
this.selectedContainer = null;
this.off('tap', this.onTap);
this.onTap = null;
return true;
if (this.openContainer) {
this.openContainer.close();
this.openContainer = null;
return true;
}
return false;
}
unlisten() {
destroy() {
super.destroy();
this.closeOpened();
super.unlisten();
this.list = null;
this.preSelectedContainer = null;
this.selectedContainer = null;
this.openContainer = null;
}
}
@@ -115,12 +106,4 @@ function getContainer(ev: any): ItemSliding {
return (<any>ele)['$ionComponent'];
}
return null;
}
function isFromOptionButtons(ev: any): boolean {
let button = closest(ev.target, '.button', true);
if (!button) {
return false;
}
return !!closest(button, 'ion-item-options', true);
}
}

View File

@@ -5,7 +5,7 @@ import { Item } from './item';
import { isPresent } from '../../util/util';
import { List } from '../list/list';
const SWIPE_MARGIN = 20;
const SWIPE_MARGIN = 30;
const ELASTIC_FACTOR = 0.55;
export const enum ItemSideFlags {

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController, NavParams} from '../../../../../src';
import { Component } from '@angular/core';
import { ionicBootstrap, NavController, NavParams } from '../../../../../src';
@Component({

View File

@@ -4,6 +4,7 @@ import { Content } from '../content/content';
import { Ion } from '../ion';
import { isTrueProperty } from '../../util/util';
import { ItemSlidingGesture } from '../item/item-sliding-gesture';
import { GestureController } from '../../gestures/gesture-controller';
/**
* The List is a widely used interface element in almost any mobile app,
@@ -29,7 +30,10 @@ export class List extends Ion {
private _containsSlidingItems: boolean = false;
private _slidingGesture: ItemSlidingGesture;
constructor(elementRef: ElementRef, private _rendered: Renderer) {
constructor(
elementRef: ElementRef,
private _rendered: Renderer,
public gestureCtrl: GestureController) {
super(elementRef);
}
@@ -78,16 +82,17 @@ export class List extends Ion {
this._updateSlidingState();
}
private _updateSlidingState() {
let shouldSlide = this._enableSliding && this._containsSlidingItems;
if (!shouldSlide) {
this._slidingGesture && this._slidingGesture.unlisten();
this._slidingGesture && this._slidingGesture.destroy();
this._slidingGesture = null;
} else if (!this._slidingGesture) {
console.debug('enableSlidingItems');
this._slidingGesture = new ItemSlidingGesture(this);
this._slidingGesture.listen();
}
}

View File

@@ -125,6 +125,10 @@ export class MenuController {
open(menuId?: string): Promise<boolean> {
let menu = this.get(menuId);
if (menu) {
let openedMenu = this.getOpen();
if (openedMenu && menu !== openedMenu) {
openedMenu.setOpen(false, false);
}
return menu.open();
}
@@ -147,7 +151,7 @@ export class MenuController {
} else {
// find the menu that is open
menu = this._menus.find(m => m.isOpen);
menu = this.getOpen();
}
if (menu) {
@@ -158,11 +162,6 @@ export class MenuController {
return Promise.resolve(false);
}
tempDisable(temporarilyDisable: boolean) {
this._menus.forEach(menu => {
menu.tempDisable(temporarilyDisable);
});
}
/**
* Toggle the menu. If it's closed, it will open, and if opened, it
@@ -173,6 +172,10 @@ export class MenuController {
toggle(menuId?: string): Promise<boolean> {
let menu = this.get(menuId);
if (menu) {
let openedMenu = this.getOpen();
if (openedMenu && menu !== openedMenu) {
openedMenu.setOpen(false, false);
}
return menu.toggle();
}
return Promise.resolve(false);
@@ -229,7 +232,7 @@ export class MenuController {
* provided, then it'll try to find the menu using the menu's `id`
* property. If a menu is not found then it'll return `null`.
* @param {string} [menuId] Optionally get the menu by its id, or side.
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
*/
get(menuId?: string): Menu {
var menu: Menu;
@@ -252,12 +255,21 @@ export class MenuController {
// return the first enabled menu
menu = this._menus.find(m => m.enabled);
if (menu) return menu;
if (menu) {
return menu;
}
// get the first menu in the array, if one exists
return (this._menus.length ? this._menus[0] : null);
}
/**
* @return {Menu} Returns the instance of the menu already opened, otherwise `null`.
*/
getOpen(): Menu {
return this._menus.find(m => m.isOpen);
}
/**
* @return {Array<Menu>} Returns an array of all menu instances.

View File

@@ -1,8 +1,8 @@
import {Menu} from './menu';
import {SlideEdgeGesture} from '../../gestures/slide-edge-gesture';
import {SlideData} from '../../gestures/slide-gesture';
import {assign} from '../../util/util';
import { Menu } from './menu';
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
import { SlideData } from '../../gestures/slide-gesture';
import { assign } from '../../util/util';
import { GesturePriority } from '../../gestures/gesture-controller';
/**
* Gesture attached to the content which the menu is assigned to
@@ -10,61 +10,29 @@ import {assign} from '../../util/util';
export class MenuContentGesture extends SlideEdgeGesture {
constructor(public menu: Menu, contentEle: HTMLElement, options: any = {}) {
super(contentEle, assign({
direction: 'x',
edge: menu.side,
threshold: 0,
maxEdgeStart: menu.maxEdgeStart || 75
maxEdgeStart: menu.maxEdgeStart || 50,
maxAngle: 40,
gesture: menu.gestureCtrl.create('menu-swipe', {
priority: GesturePriority.MenuSwipe,
})
}, options));
}
canStart(ev: any) {
canStart(ev: any): boolean {
let menu = this.menu;
if (!menu.enabled || !menu.swipeEnabled) {
console.debug('menu can not start, isEnabled:', menu.enabled, 'isSwipeEnabled:', menu.swipeEnabled, 'side:', menu.side);
return false;
}
if (ev.distance > 50) {
// the distance is longer than you'd expect a side menu swipe to be
console.debug('menu can not start, distance too far:', ev.distance, 'side:', menu.side);
if (menu.isOpen) {
return true;
} else if (menu.getMenuController().getOpen()) {
return false;
}
console.debug('menu canStart,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance);
if (menu.side === 'right') {
// right side
if (menu.isOpen) {
// right side, opened
return true;
} else {
// right side, closed
if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) {
return super.canStart(ev);
}
}
} else {
// left side
if (menu.isOpen) {
// left side, opened
return true;
} else {
// left side, closed
if (ev.angle > -40 && ev.angle < 40) {
return super.canStart(ev);
}
}
}
// didn't pass the test, don't open this menu
return false;
return super.canStart(ev);
}
// Set CSS, then wait one frame for it to apply before sliding starts
@@ -77,7 +45,6 @@ export class MenuContentGesture extends SlideEdgeGesture {
let z = (this.menu.side === 'right' ? slide.min : slide.max);
let stepValue = (slide.distance / z);
console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'min', slide.min, 'max', slide.max, 'z', z, 'stepValue', stepValue);
ev.srcEvent.preventDefault();
ev.preventDefault();
this.menu.swipeProgress(stepValue);
}
@@ -85,25 +52,24 @@ export class MenuContentGesture extends SlideEdgeGesture {
onSlideEnd(slide: SlideData, ev: any) {
let z = (this.menu.side === 'right' ? slide.min : slide.max);
let currentStepValue = (slide.distance / z);
let velocity = slide.velocity;
z = Math.abs(z * 0.5);
let shouldCompleteRight = (ev.velocityX >= 0)
&& (ev.velocityX > 0.2 || slide.delta > z);
let shouldCompleteLeft = (ev.velocityX <= 0)
&& (ev.velocityX < -0.2 || slide.delta < -z);
let shouldCompleteRight = (velocity >= 0)
&& (velocity > 0.2 || slide.delta > z);
let shouldCompleteLeft = (velocity <= 0)
&& (velocity < -0.2 || slide.delta < -z);
console.debug(
'menu gesture, onSlide', this.menu.side,
'distance', slide.distance,
'delta', slide.delta,
'velocityX', ev.velocityX,
'velocity', velocity,
'min', slide.min,
'max', slide.max,
'shouldCompleteLeft', shouldCompleteLeft,
'shouldCompleteRight', shouldCompleteRight,
'currentStepValue', currentStepValue);
this.menu.swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue);
}
@@ -134,14 +100,3 @@ export class MenuContentGesture extends SlideEdgeGesture {
}
}
/**
* Gesture attached to the actual menu itself
*/
export class MenuTargetGesture extends MenuContentGesture {
constructor(menu: Menu, menuEle: HTMLElement) {
super(menu, menuEle, {
maxEdgeStart: 0
});
}
}

View File

@@ -15,11 +15,15 @@ export class MenuType {
ani: Animation = new Animation();
isOpening: boolean;
setOpen(shouldOpen: boolean, done: Function) {
this.ani
.onFinish(done, true)
.reverse(!shouldOpen)
.play();
setOpen(shouldOpen: boolean, animated: boolean, done: Function) {
let ani = this.ani
.onFinish(done, true)
.reverse(!shouldOpen);
if (animated) {
ani.play();
} else {
ani.play({ duration: 0 });
}
}
setProgressStart(isOpen: boolean) {

View File

@@ -5,10 +5,11 @@ import { Config } from '../../config/config';
import { Ion } from '../ion';
import { isTrueProperty } from '../../util/util';
import { Keyboard } from '../../util/keyboard';
import { MenuContentGesture, MenuTargetGesture } from './menu-gestures';
import { MenuContentGesture } from './menu-gestures';
import { MenuController } from './menu-controller';
import { MenuType } from './menu-types';
import { Platform } from '../../platform/platform';
import { GestureController } from '../../gestures/gesture-controller';
/**
@@ -190,15 +191,13 @@ import { Platform } from '../../platform/platform';
export class Menu extends Ion {
private _preventTime: number = 0;
private _cntEle: HTMLElement;
private _cntGesture: MenuTargetGesture;
private _menuGesture: MenuContentGesture;
private _cntGesture: MenuContentGesture;
private _type: MenuType;
private _resizeUnreg: Function;
private _isEnabled: boolean = true;
private _isSwipeEnabled: boolean = true;
private _isPers: boolean = false;
private _init: boolean = false;
private _prevEnabled: boolean;
/**
* @private
@@ -302,7 +301,8 @@ export class Menu extends Ion {
private _platform: Platform,
private _renderer: Renderer,
private _keyboard: Keyboard,
private _zone: NgZone
private _zone: NgZone,
public gestureCtrl: GestureController
) {
super(_elementRef);
}
@@ -335,8 +335,7 @@ export class Menu extends Ion {
self._renderer.setElementAttribute(self._elementRef.nativeElement, 'type', self.type);
// add the gestures
self._cntGesture = new MenuContentGesture(self, self.getContentElement());
self._menuGesture = new MenuTargetGesture(self, self.getNativeElement());
self._cntGesture = new MenuContentGesture(self, document.body);
// register listeners if this menu is enabled
// check if more than one menu is on the same side
@@ -387,16 +386,12 @@ export class Menu extends Ion {
if (self._isEnabled && self._isSwipeEnabled && !self._cntGesture.isListening) {
// should listen, but is not currently listening
console.debug('menu, gesture listen', self.side);
self._zone.runOutsideAngular(function() {
self._cntGesture.listen();
self._menuGesture.listen();
});
self._cntGesture.listen();
} else if (self._cntGesture.isListening && (!self._isEnabled || !self._isSwipeEnabled)) {
// should not listen, but is currently listening
console.debug('menu, gesture unlisten', self.side);
self._cntGesture.unlisten();
self._menuGesture.unlisten();
}
}
}
@@ -418,7 +413,7 @@ export class Menu extends Ion {
/**
* @private
*/
setOpen(shouldOpen: boolean): Promise<boolean> {
setOpen(shouldOpen: boolean, animated: boolean = true): Promise<boolean> {
// _isPrevented is used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the MenuToggle button
if ((shouldOpen && this.isOpen) || this._isPrevented()) {
@@ -428,7 +423,7 @@ export class Menu extends Ion {
this._before();
return new Promise(resolve => {
this._getType().setOpen(shouldOpen, () => {
this._getType().setOpen(shouldOpen, animated, () => {
this._after(shouldOpen);
resolve(this.isOpen);
});
@@ -519,21 +514,6 @@ export class Menu extends Ion {
}
}
/**
* @private
*/
tempDisable(temporarilyDisable: boolean) {
if (temporarilyDisable) {
this._prevEnabled = this._isEnabled;
this._getType().setProgessStep(0);
this.enable(false);
} else {
this.enable(this._prevEnabled);
this._after(false);
}
}
private _prevent() {
// used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the MenuToggle
@@ -617,13 +597,19 @@ export class Menu extends Ion {
return this.backdrop.getNativeElement();
}
/**
* @private
*/
getMenuController(): MenuController {
return this._menuCtrl;
}
/**
* @private
*/
ngOnDestroy() {
this._menuCtrl.unregister(this);
this._cntGesture && this._cntGesture.destroy();
this._menuGesture && this._menuGesture.destroy();
this._type && this._type.destroy();
this._resizeUnreg && this._resizeUnreg();
this._cntEle = null;

View File

@@ -67,6 +67,14 @@ class E2EPage {
});
}
openRightMenu() {
this.menu.open('right');
}
openLeftMenu() {
this.menu.open('left');
}
onDrag(ev: any) {
console.log('Menu is being dragged', ev);
}

View File

@@ -14,6 +14,10 @@
{{p.title}}
</button>
<button ion-item (click)="openRightMenu()">
Open Right Menu
</button>
<button ion-item menuClose="left" class="e2eCloseLeftMenu" detail-none>
Close Menu
</button>
@@ -90,6 +94,10 @@
{{p.title}}
</button>
<button ion-item (click)="openLeftMenu()">
Open Left Menu
</button>
<button ion-item menuClose="right" class="e2eCloseRightMenu" detail-none>
Close Menu
</button>
@@ -148,6 +156,6 @@
</ion-menu>
<ion-nav [root]="rootPage" #content></ion-nav>
<ion-nav [root]="rootPage" #content swipeBackEnabled="true"></ion-nav>
<div [hidden]="isChangeDetecting()"></div>

View File

@@ -35,9 +35,19 @@
<h3>Page 1</h3>
<p>
<button class="e2eContentToggleLeftMenu" menuToggle="left">Toggle Left Menu</button>
</p>
<ion-list>
<ion-item-sliding>
<button ion-item class="e2eContentToggleLeftMenu" menuToggle="left">Toggle Left Menu</button>
<ion-item-options side="left">
<button>Test</button>
</ion-item-options>
<ion-item-options>
<button>Test</button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<p>
<button class="e2eContentToggleRightMenu" menuToggle="right">Toggle Right Menu</button>

View File

@@ -1,12 +1,12 @@
import { Component, Injectable } from '@angular/core';
import { ActionSheetController, Config, ionicBootstrap, ModalController, NavController, NavParams, PageTransition, Platform, TransitionOptions, ViewController } from '../../../../../src';
import { ActionSheetController, App, Config, ionicBootstrap, ModalController, NavController, NavParams, PageTransition, Platform, TransitionOptions, ViewController } from '../../../../../src';
@Injectable()
class SomeComponentProvider {
constructor(private config: Config) {
console.log('SomeComponentProvider constructor')
console.log('SomeComponentProvider constructor');
}
getName() {
@@ -17,7 +17,7 @@ class SomeComponentProvider {
@Injectable()
class SomeAppProvider {
constructor(private config: Config) {
console.log('SomeAppProvider constructor')
console.log('SomeAppProvider constructor');
}
getData() {
@@ -84,7 +84,7 @@ class E2EPage {
}
presentModalWithInputs() {
let modal = this.modalCtrl.create(ModalWithInputs);
let modal = this.modalCtrl.create(ModalWithInputs);
modal.onDidDismiss((data: any) => {
console.log('Modal with inputs data:', data);
});
@@ -98,7 +98,7 @@ class E2EPage {
});
}
presentNavigableModal(){
presentNavigableModal() {
this.modalCtrl.create(NavigableModal).present();
}
}
@@ -117,11 +117,11 @@ class E2EPage {
`
})
class NavigableModal {
constructor(private navController:NavController) {
constructor(private nav: NavController) {
}
submit(){
this.navController.push(NavigableModal2);
this.nav.push(NavigableModal2);
}
}
@@ -139,10 +139,10 @@ class NavigableModal {
`
})
class NavigableModal2 {
constructor(private navController:NavController) {
constructor(private navController: NavController) {
}
submit(){
submit() {
this.navController.pop();
}
}
@@ -188,23 +188,23 @@ class ModalPassData {
this.viewCtrl.dismiss(this.data);
}
ionViewLoaded(){
ionViewLoaded() {
console.log('ModalPassData ionViewLoaded fired');
}
ionViewWillEnter(){
ionViewWillEnter() {
console.log('ModalPassData ionViewWillEnter fired');
}
ionViewDidEnter(){
ionViewDidEnter() {
console.log('ModalPassData ionViewDidEnter fired');
}
ionViewWillLeave(){
ionViewWillLeave() {
console.log('ModalPassData ionViewWillLeave fired');
}
ionViewDidLeave(){
ionViewDidLeave() {
console.log('ModalPassData ionViewDidLeave fired');
}
}
@@ -375,10 +375,10 @@ class ContactUs {
})
class ModalFirstPage {
private items:any[];
constructor(private nav: NavController, private actionSheetCtrl: ActionSheetController) {
private items: any[];
constructor(private nav: NavController, private app: App, private actionSheetCtrl: ActionSheetController) {
this.items = [];
for ( let i = 0; i < 50; i++ ){
for ( let i = 0; i < 50; i++ ) {
this.items.push({
value: (i + 1)
});
@@ -387,24 +387,24 @@ class ModalFirstPage {
push() {
let page = ModalSecondPage;
let params = { id: 8675309, myData: [1,2,3,4] };
let params = { id: 8675309, myData: [1, 2, 3, 4] };
this.nav.push(page, params);
}
dismiss() {
this.nav.rootNav.pop();
this.app.getRootNav().pop();
}
ionViewLoaded(){
ionViewLoaded() {
console.log('ModalFirstPage ionViewLoaded fired');
}
ionViewWillEnter(){
ionViewWillEnter() {
console.log('ModalFirstPage ionViewWillEnter fired');
}
ionViewDidEnter(){
ionViewDidEnter() {
console.log('ModalFirstPage ionViewDidEnter fired');
}
@@ -430,8 +430,8 @@ class ModalFirstPage {
// overlays are added and removed from the root navigation
// find the root navigation, and pop this alert
// when the alert is done animating out, then pop off the modal
this.nav.rootNav.pop().then(() => {
this.nav.rootNav.pop();
this.app.getRootNav().pop().then(() => {
this.app.getRootNav().pop();
});
// by default an alert will dismiss itself
@@ -477,15 +477,15 @@ class ModalSecondPage {
console.log('Second page params:', params);
}
ionViewLoaded(){
ionViewLoaded() {
console.log('ModalSecondPage ionViewLoaded');
}
ionViewWillEnter(){
ionViewWillEnter() {
console.log('ModalSecondPage ionViewWillEnter');
}
ionViewDidEnter(){
ionViewDidEnter() {
console.log('ModalSecondPage ionViewDidEnter');
}
}

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -13,3 +13,6 @@ export interface NavOptions {
climbNav?: boolean;
ev?: any;
}
export const DIRECTION_BACK = 'back';
export const DIRECTION_FORWARD = 'forward';

View File

@@ -1,11 +1,12 @@
import { Directive, Optional } from '@angular/core';
import { Directive, HostListener, Input, Optional } from '@angular/core';
import { NavController } from './nav-controller';
import { noop } from '../../util/util';
/**
* @name NavPop
* @description
* Directive for declaratively pop the current page off from the navigation stack.
* Directive to declaratively pop the current page off from the
* navigation stack.
*
* @usage
* ```html
@@ -22,11 +23,7 @@ import { NavController } from './nav-controller';
* @see {@link ../NavPush NavPush API Docs}
*/
@Directive({
selector: '[nav-pop]',
host: {
'(click)': 'onClick()',
'role': 'link'
}
selector: '[navPop]'
})
export class NavPop {
@@ -36,10 +33,15 @@ export class NavPop {
}
}
/**
* @private
*/
onClick() {
this._nav && this._nav.pop();
@HostListener('click')
onClick(): boolean {
// If no target, or if target is _self, prevent default browser behavior
if (this._nav) {
this._nav.pop(null, noop);
return false;
}
return true;
}
}

View File

@@ -2,9 +2,9 @@ import { ComponentResolver, Directive, ElementRef, forwardRef, Inject, NgZone, O
import { App } from '../app/app';
import { Config } from '../../config/config';
import { GestureController } from '../../gestures/gesture-controller';
import { Keyboard } from '../../util/keyboard';
import { MenuController } from '../menu/menu-controller';
import { NavController } from '../nav/nav-controller';
import { NavControllerBase } from '../nav/nav-controller-base';
/**
* @private
@@ -12,7 +12,7 @@ import { NavController } from '../nav/nav-controller';
@Directive({
selector: '[nav-portal]'
})
export class NavPortal extends NavController {
export class NavPortal extends NavControllerBase {
constructor(
@Inject(forwardRef(() => App)) app: App,
config: Config,
@@ -21,11 +21,11 @@ export class NavPortal extends NavController {
zone: NgZone,
renderer: Renderer,
compiler: ComponentResolver,
menuCtrl: MenuController,
gestureCtrl: GestureController,
viewPort: ViewContainerRef
) {
super(null, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
this.isPortal = true;
super(null, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
this._isPortal = true;
this.setViewport(viewPort);
app.setPortal(this);

View File

@@ -1,28 +1,35 @@
import { Directive, Input, Optional } from '@angular/core';
import { Directive, HostListener, Input, Optional } from '@angular/core';
import { NavController } from './nav-controller';
import { noop } from '../../util/util';
/**
* @name NavPush
* @description
* Directive for declaratively linking to a new page instead of using
* {@link ../NavController/#push NavController.push}. Similar to ui-router's `ui-sref`.
* Directive to declaratively push a new page to the current nav
* stack.
*
* @usage
* ```html
* <button [navPush]="pushPage"></button>
* ```
* To specify parameters you can use array syntax or the `nav-params` property:
*
* To specify parameters you can use array syntax or the `navParams`
* property:
*
* ```html
* <button [navPush]="pushPage" [navParams]="params"></button>
* <button [navPush]="pushPage" [navParams]="params">Go</button>
* ```
* Where `pushPage` and `params` are specified in your component, and `pushPage`
* contains a reference to a [@Page component](../../../config/Page/):
*
* Where `pushPage` and `params` are specified in your component,
* and `pushPage` contains a reference to a
* [@Page component](../../../config/Page/):
*
* ```ts
* import {LoginPage} from 'login';
* import { LoginPage } from './login';
*
* @Component({
* template: `<button [navPush]="pushPage" [navParams]="params"></button>`
* template: `<button [navPush]="pushPage" [navParams]="params">Go</button>`
* })
* class MyPage {
* constructor(){
@@ -32,61 +39,42 @@ import { NavController } from './nav-controller';
* }
* ```
*
* ### Alternate syntax
* You can also use syntax similar to Angular2's router, passing an array to
* NavPush:
* ```html
* <button [navPush]="[pushPage, params]"></button>
* ```
* @demo /docs/v2/demos/navigation/
* @see {@link /docs/v2/components#navigation Navigation Component Docs}
* @see {@link ../NavPop NavPop API Docs}
*
*/
@Directive({
selector: '[navPush]',
host: {
'(click)': 'onClick()',
'role': 'link'
}
selector: '[navPush]'
})
export class NavPush {
/**
* @input {Page} the page you want to push
*/
@Input() navPush: any;
/**
* @input {any} Any parameters you want to pass along
*/
@Input() navParams: any;
constructor(
@Optional() private _nav: NavController
) {
if (!_nav) {
console.error('nav-push must be within a NavController');
}
}
/**
* @private
* @input {Page} The Page to push onto the Nav.
*/
onClick() {
let destination: any, params: any;
@Input() navPush: any[]|string;
if (this.navPush instanceof Array) {
if (this.navPush.length > 2) {
throw 'Too many [navPush] arguments, expects [View, { params }]';
}
destination = this.navPush[0];
params = this.navPush[1] || this.navParams;
/**
* @input {any} Parameters to pass to the page.
*/
@Input() navParams: {[k: string]: any};
} else {
destination = this.navPush;
params = this.navParams;
constructor(@Optional() private _nav: NavController) {
if (!_nav) {
console.error('navPush must be within a NavController');
}
}
@HostListener('click')
onClick(): boolean {
// If no target, or if target is _self, prevent default browser behavior
if (this._nav) {
this._nav.push(this.navPush, this.navParams, noop);
return false;
}
this._nav && this._nav.push(destination, params);
return true;
}
}

View File

@@ -3,9 +3,9 @@ import { AfterViewInit, Component, ComponentResolver, ElementRef, Input, Optiona
import { App } from '../app/app';
import { Config } from '../../config/config';
import { Keyboard } from '../../util/keyboard';
import { GestureController } from '../../gestures/gesture-controller';
import { isTrueProperty } from '../../util/util';
import { MenuController } from '../menu/menu-controller';
import { NavController } from './nav-controller';
import { NavControllerBase } from './nav-controller-base';
import { ViewController } from './view-controller';
/**
@@ -114,13 +114,13 @@ import { ViewController } from './view-controller';
`,
encapsulation: ViewEncapsulation.None,
})
export class Nav extends NavController implements AfterViewInit {
export class Nav extends NavControllerBase implements AfterViewInit {
private _root: any;
private _hasInit: boolean = false;
constructor(
@Optional() viewCtrl: ViewController,
@Optional() parent: NavController,
@Optional() parent: NavControllerBase,
app: App,
config: Config,
keyboard: Keyboard,
@@ -128,9 +128,9 @@ export class Nav extends NavController implements AfterViewInit {
zone: NgZone,
renderer: Renderer,
compiler: ComponentResolver,
menuCtrl: MenuController
gestureCtrl: GestureController
) {
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
if (viewCtrl) {
// an ion-nav can also act as an ion-page within a parent ion-nav
@@ -164,9 +164,6 @@ export class Nav extends NavController implements AfterViewInit {
this._hasInit = true;
if (this._root) {
if (typeof this._root !== 'function') {
throw 'The [root] property in <ion-nav> must be given a reference to a component class from within the constructor.';
}
this.push(this._root);
}
}

View File

@@ -1,6 +1,7 @@
import { assign } from '../../util/util';
import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
import { MenuController } from '../menu/menu-controller';
import { NavController } from './nav-controller';
import { NavControllerBase } from './nav-controller-base';
import { SlideData } from '../../gestures/slide-gesture';
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
@@ -10,36 +11,32 @@ export class SwipeBackGesture extends SlideEdgeGesture {
constructor(
element: HTMLElement,
options: any,
private _nav: NavController,
private _menuCtrl: MenuController
private _nav: NavControllerBase,
gestureCtlr: GestureController
) {
super(element, assign({
direction: 'x',
maxEdgeStart: 75
maxEdgeStart: 75,
gesture: gestureCtlr.create('goback-swipe', {
priority: GesturePriority.GoBackSwipe,
})
}, options));
}
canStart(ev: any) {
canStart(ev: any): boolean {
// the gesture swipe angle must be mainly horizontal and the
// gesture distance would be relatively short for a swipe back
// and swipe back must be possible on this nav controller
if (ev.angle > -40 &&
ev.angle < 40 &&
ev.distance < 50 &&
this._nav.canSwipeBack()) {
// passed the tests, now see if the super says it's cool or not
return super.canStart(ev);
}
// nerp, not today
return false;
return (
this._nav.canSwipeBack() &&
super.canStart(ev)
);
}
onSlideBeforeStart(slideData: SlideData, ev: any) {
console.debug('swipeBack, onSlideBeforeStart', ev.srcEvent.type);
this._nav.swipeBackStart();
this._menuCtrl.tempDisable(true);
onSlideBeforeStart(slideData: SlideData, ev: any) {
console.debug('swipeBack, onSlideBeforeStart', ev.type);
this._nav.swipeBackStart();
}
onSlide(slide: SlideData) {
@@ -49,15 +46,10 @@ export class SwipeBackGesture extends SlideEdgeGesture {
}
onSlideEnd(slide: SlideData, ev: any) {
let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
let shouldComplete = (Math.abs(slide.velocity) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
let currentStepValue = (slide.distance / slide.max);
console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
this._menuCtrl.tempDisable(false);
}
}

View File

@@ -1,6 +1,6 @@
import { Component, ViewChild } from '@angular/core';
import { NavController, AlertController, Content } from '../../../../../src';
import { ionicBootstrap } from '../../../../../src';
import { ionicBootstrap, App } from '../../../../../src';
import { NavParams, ViewController } from '../../../../../src';;
@@ -148,6 +148,7 @@ class FirstPage {
class FullPage {
constructor(
private nav: NavController,
private app: App,
private alertCtrl: AlertController,
private params: NavParams
) {}
@@ -184,8 +185,8 @@ class FullPage {
// overlays are added and removed from the root navigation
// ensure you using the root navigation, and pop this alert
// when the alert is done animating out, then pop off the active page
this.nav.rootNav.pop().then(() => {
this.nav.rootNav.pop();
this.app.getRootNav().pop().then(() => {
this.app.getRootNav().pop();
});
// by default an alert will dismiss itself

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController} from '../../../../../src';
import { Component} from '@angular/core';
import { ionicBootstrap, NavController } from '../../../../../src';
@Component({
template: `<ion-nav [root]="root"></ion-nav>`,
@@ -29,11 +29,11 @@ ionicBootstrap(E2EApp);
})
class LandingPage{
constructor(private _navController: NavController){
constructor(private nav: NavController){
}
goToPage(){
this._navController.push(FirstPage);
this.nav.push(FirstPage);
}
}

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController} from '../../../../../src';
import { Component} from '@angular/core';
import { ionicBootstrap, NavController } from '../../../../../src';
@Component({

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController} from '../../../../../src';
import { Component} from '@angular/core';
import { ionicBootstrap, NavController } from '../../../../../src';
let delay = 100;
@@ -16,7 +16,7 @@ let count = 0;
`
})
class Page1 {
tmr;
tmr: number;
constructor(private nav: NavController) {}
@@ -50,7 +50,7 @@ class Page1 {
`
})
class Page2 {
tmr;
tmr: number;
constructor(private nav: NavController) {}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ import { ViewController } from '../nav/view-controller';
template: `
<div *ngIf="col.prefix" class="picker-prefix" [style.width]="col.prefixWidth">{{col.prefix}}</div>
<div class="picker-opts" #colEle [style.width]="col.optionsWidth">
<button *ngFor="let o of col.options; let i=index" [style.transform]="o._trans" [style.transitionDuration]="o._dur" [class.picker-opt-selected]="col.selectedIndex === i" [class.picker-opt-disabled]="o.disabled" (click)="optClick($event, i)" type="button" category="picker-opt">
<button *ngFor="let o of col.options; let i=index" [style.transform]="o._trans" [style.transitionDuration]="o._dur" [style.webkitTransform]="o._trans" [style.webkitTransitionDuration]="o._dur" [class.picker-opt-selected]="col.selectedIndex === i" [class.picker-opt-disabled]="o.disabled" (click)="optClick($event, i)" type="button" category="picker-opt">
{{o.text}}
</button>
</div>
@@ -74,11 +74,12 @@ export class PickerColumnCmp {
this.setSelected(this.col.selectedIndex, 0);
// Listening for pointer events
this.events.pointerEventsRef(this.elementRef,
(ev: any) => this.pointerStart(ev),
(ev: any) => this.pointerMove(ev),
(ev: any) => this.pointerEnd(ev)
);
this.events.pointerEvents({
elementRef: this.elementRef,
pointerDown: this.pointerStart.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerEnd.bind(this)
});
}
ngOnDestroy() {

View File

@@ -364,11 +364,12 @@ export class Range implements AfterViewInit, ControlValueAccessor, OnDestroy {
this._renderer.setElementStyle(this._bar.nativeElement, 'right', barR);
// add touchstart/mousedown listeners
this._events.pointerEventsRef(this._slider,
this.pointerDown.bind(this),
this.pointerMove.bind(this),
this.pointerUp.bind(this));
this._events.pointerEvents({
elementRef: this._slider,
pointerDown: this.pointerDown.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerUp.bind(this)
});
this.createTicks();
}
@@ -430,18 +431,12 @@ export class Range implements AfterViewInit, ControlValueAccessor, OnDestroy {
ev.preventDefault();
ev.stopPropagation();
if (this._start !== null && this._active !== null) {
// only use pointer move if it's a valid pointer
// and we already have start coordinates
// update the ratio for the active knob
this.updateKnob(pointerCoord(ev), this._rect);
// update the ratio for the active knob
this.updateKnob(pointerCoord(ev), this._rect);
// update the active knob's position
this._active.position();
this._pressed = this._active.pressed = true;
}
// update the active knob's position
this._active.position();
this._pressed = this._active.pressed = true;
}
/**

View File

@@ -2,6 +2,7 @@ import { Directive, EventEmitter, Host, Input, Output, NgZone } from '@angular/c
import { Content } from '../content/content';
import { CSS, pointerCoord } from '../../util/dom';
import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
import { isTrueProperty } from '../../util/util';
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
@@ -98,6 +99,7 @@ export class Refresher {
private _didStart: boolean;
private _lastCheck: number = 0;
private _isEnabled: boolean = true;
private _gesture: GestureDelegate;
private _events: UIEventManager = new UIEventManager(false);
private _pointerEvents: PointerEvents;
private _top: string = '';
@@ -196,8 +198,11 @@ export class Refresher {
@Output() ionStart: EventEmitter<Refresher> = new EventEmitter<Refresher>();
constructor(@Host() private _content: Content, private _zone: NgZone) {
constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) {
_content.addCssClass('has-refresher');
this._gesture = gestureCtrl.create('refresher', {
priority: GesturePriority.Refresher,
});
}
private _onStart(ev: TouchEvent): any {
@@ -216,6 +221,10 @@ export class Refresher {
return false;
}
if (!this._gesture.canStart()) {
return false;
}
let coord = pointerCoord(ev);
console.debug('Pull-to-refresh, onStart', ev.type, 'y:', coord.y);
@@ -228,7 +237,7 @@ export class Refresher {
this.startY = this.currentY = coord.y;
this.progress = 0;
this.state = STATE_PULLING;
this.state = STATE_INACTIVE;
return true;
}
@@ -242,6 +251,10 @@ export class Refresher {
return 1;
}
if (!this._gesture.canStart()) {
return 0;
}
// do nothing if it's actively refreshing
// or it's in the process of closing
// or this was never a startY
@@ -462,10 +475,12 @@ export class Refresher {
this._events.unlistenAll();
this._pointerEvents = null;
if (shouldListen) {
this._pointerEvents = this._events.pointerEvents(this._content.getScrollElement(),
this._onStart.bind(this),
this._onMove.bind(this),
this._onEnd.bind(this));
this._pointerEvents = this._events.pointerEvents({
element: this._content.getScrollElement(),
pointerDown: this._onStart.bind(this),
pointerMove: this._onMove.bind(this),
pointerUp: this._onEnd.bind(this)
});
}
}
@@ -482,6 +497,7 @@ export class Refresher {
* @private
*/
ngOnDestroy() {
this._gesture.destroy();
this._setListeners(false);
}

View File

@@ -1,4 +1,4 @@
import {Refresher, Content, Config, Ion} from '../../../../src';
import { Refresher, Content, Config, GestureController, Ion } from '../../../../src';
export function run() {
@@ -218,17 +218,19 @@ describe('Refresher', () => {
let refresher: Refresher;
let content: Content;
let contentElementRef;
let gestureController: GestureController;
let zone = {
run: function(cb) {cb()},
runOutsideAngular: function(cb) {cb()}
run: function (cb) { cb(); },
runOutsideAngular: function (cb) { cb(); }
};
beforeEach(() => {
contentElementRef = mockElementRef();
content = new Content(contentElementRef, config, null, null, null);
gestureController = new GestureController();
content = new Content(contentElementRef, config, null, null, zone, null, null);
content._scrollEle = document.createElement('scroll-content');
refresher = new Refresher(content, zone, mockElementRef());
refresher = new Refresher(content, zone, gestureController);
});
function touchEv(y: number) {

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController} from '../../../../../src';
import { Component} from '@angular/core';
import { ionicBootstrap, NavController } from '../../../../../src';
@Component({
@@ -8,7 +8,7 @@ import {ionicBootstrap, NavController} from '../../../../../src';
class IntroPage {
continueText: string = "Skip";
startingIndex: number = 1;
mySlideOptions;
mySlideOptions: any;
showSlide: boolean = true;
constructor(private nav: NavController) {
@@ -20,16 +20,16 @@ class IntroPage {
};
}
onSlideChanged(slider) {
console.log("Slide changed", slider);
onSlideChanged(slider: any) {
console.log('Slide changed', slider);
}
onSlideChangeStart(slider) {
console.log("Slide change start", slider);
slider.isEnd ? this.continueText = "Continue" : this.continueText = "Skip";
onSlideChangeStart(slider: any) {
console.log('Slide change start', slider);
slider.isEnd ? this.continueText = 'Continue' : this.continueText = "Skip";
}
onSlideMove(slider) {
onSlideMove(slider: any) {
console.log("Slide move", slider);
}

View File

@@ -14,16 +14,16 @@ class E2EApp {
constructor() {
this.slides = [
{
name: "Slide 1",
class: "yellow"
name: 'Slide 1',
class: 'yellow'
},
{
name: "Slide 2",
class: "red"
name: 'Slide 2',
class: 'red'
},
{
name: "Slide 3",
class: "blue"
name: 'Slide 3',
class: 'blue'
}
];

View File

@@ -1,11 +1,11 @@
import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Optional, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
import { App } from '../app/app';
import { Config } from '../../config/config';
import { GestureController } from '../../gestures/gesture-controller';
import { isTrueProperty} from '../../util/util';
import { Keyboard} from '../../util/keyboard';
import { MenuController } from '../menu/menu-controller';
import { NavController } from '../nav/nav-controller';
import { NavControllerBase } from '../nav/nav-controller-base';
import { NavOptions} from '../nav/nav-interfaces';
import { TabButton} from './tab-button';
import { Tabs} from './tabs';
@@ -128,7 +128,7 @@ import { ViewController} from '../nav/view-controller';
template: '<div #viewport></div><div class="nav-decor"></div>',
encapsulation: ViewEncapsulation.None,
})
export class Tab extends NavController {
export class Tab extends NavControllerBase {
private _isInitial: boolean;
private _isEnabled: boolean = true;
private _isShown: boolean = true;
@@ -229,17 +229,13 @@ export class Tab extends NavController {
renderer: Renderer,
compiler: ComponentResolver,
private _cd: ChangeDetectorRef,
menuCtrl: MenuController
gestureCtrl: GestureController
) {
// A Tab is a NavController for its child pages
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
parent.add(this);
if (parent.rootNav) {
this._sbEnabled = parent.rootNav.isSwipeBackEnabled();
}
this._tabId = 'tabpanel-' + this.id;
this._btnId = 'tab-' + this.id;
}
@@ -264,7 +260,7 @@ export class Tab extends NavController {
*/
load(opts: NavOptions, done?: Function) {
if (!this._loaded && this.root) {
this.push(this.root, this.rootParams, opts).then(() => {
this.push(this.root, this.rootParams, opts, () => {
done(true);
});
this._loaded = true;
@@ -294,20 +290,7 @@ export class Tab extends NavController {
* @private
*/
loadPage(viewCtrl: ViewController, viewport: ViewContainerRef, opts: NavOptions, done: Function) {
let isTabSubPage = (this.parent.subPages && viewCtrl.index > 0);
if (isTabSubPage) {
viewport = this.parent.portal;
}
super.loadPage(viewCtrl, viewport, opts, () => {
if (isTabSubPage) {
// add the .tab-subpage css class to tabs pages that should act like subpages
let pageEleRef = viewCtrl.pageRef();
if (pageEleRef) {
this._renderer.setElementClass(pageEleRef.nativeElement, 'tab-subpage', true);
}
}
done();
});
}

View File

@@ -12,7 +12,8 @@ $tabbar-md-item-icon-size: 2.4rem !default;
$tabbar-md-item-height: 4.8rem !default;
$tab-button-md-active-color: $toolbar-md-active-color !default;
$tab-button-md-inactive-color: rgba($toolbar-md-inactive-color, .7) !default;
$tab-button-md-inactive-opacity: .7 !default;
$tab-button-md-inactive-color: rgba($toolbar-md-inactive-color, $tab-button-md-inactive-opacity) !default;
ion-tabbar {
@@ -103,7 +104,7 @@ tab-highlight {
background-color: $color-base;
.tab-button {
color: $color-contrast;
color: rgba($color-contrast, $tab-button-md-inactive-opacity);
}
.tab-button:hover:not(.disable-hover),

View File

@@ -7,9 +7,11 @@ import { Config } from '../../config/config';
import { Content } from '../content/content';
import { Icon } from '../icon/icon';
import { Ion } from '../ion';
import { isBlank, isTrueProperty } from '../../util/util';
import { isBlank, isPresent, isTrueProperty } from '../../util/util';
import { nativeRaf } from '../../util/dom';
import { NavController, DIRECTION_FORWARD } from '../nav/nav-controller';
import { NavController } from '../nav/nav-controller';
import { NavControllerBase } from '../nav/nav-controller-base';
import { NavOptions, DIRECTION_FORWARD } from '../nav/nav-interfaces';
import { Platform } from '../../platform/platform';
import { Tab } from './tab';
import { TabButton } from './tab-button';
@@ -148,7 +150,6 @@ import { ViewController } from '../nav/view-controller';
<tab-highlight></tab-highlight>
</ion-tabbar>
<ng-content></ng-content>
<div #portal tab-portal></div>
`,
directives: [Badge, Icon, NgClass, NgFor, NgIf, TabButton, TabHighlight],
encapsulation: ViewEncapsulation.None,
@@ -165,18 +166,13 @@ export class Tabs extends Ion {
/**
* @private
*/
id: number;
id: string;
/**
* @private
*/
selectHistory: string[] = [];
/**
* @private
*/
subPages: boolean;
/**
* @input {number} The default selected tab index when first loaded. If a selected index isn't provided then it will use `0`, the first tab.
*/
@@ -225,12 +221,7 @@ export class Tabs extends Ion {
/**
* @private
*/
@ViewChild('portal', {read: ViewContainerRef}) portal: ViewContainerRef;
/**
* @private
*/
parent: NavController;
parent: NavControllerBase;
constructor(
@Optional() parent: NavController,
@@ -243,16 +234,14 @@ export class Tabs extends Ion {
) {
super(_elementRef);
this.parent = parent;
this.id = ++tabIds;
this.parent = <NavControllerBase>parent;
this.id = 't' + (++tabIds);
this._sbPadding = _config.getBoolean('statusbarPadding');
this.subPages = _config.getBoolean('tabsHideOnSubPages');
this._useHighlight = _config.getBoolean('tabsHighlight');
// TODO deprecated 07-07-2016 beta.11
if (_config.get('tabSubPages') !== null) {
console.warn('Config option "tabSubPages" has been deprecated. Please use "tabsHideOnSubPages" instead.');
this.subPages = _config.getBoolean('tabSubPages');
console.warn('Config option "tabSubPages" has been deprecated. The Material Design spec now supports Bottom Navigation: https://material.google.com/components/bottom-navigation.html');
}
// TODO deprecated 07-07-2016 beta.11
@@ -261,9 +250,9 @@ export class Tabs extends Ion {
this._useHighlight = _config.getBoolean('tabbarHighlight');
}
if (parent) {
if (this.parent) {
// this Tabs has a parent Nav
parent.registerChildNav(this);
this.parent.registerChildNav(this);
} else if (this._app) {
// this is the root navcontroller for the entire app
@@ -333,41 +322,32 @@ export class Tabs extends Ion {
* @private
*/
initTabs() {
// first check if preloadTab is set as an input @Input, then check the config
let preloadTabs = (isBlank(this.preloadTabs) ? this._config.getBoolean('preloadTabs') : isTrueProperty(this.preloadTabs));
// get the selected index from the input
// otherwise default it to use the first index
let selectedIndex = (isBlank(this.selectedIndex) ? 0 : parseInt(this.selectedIndex, 10));
// get the selected index
let selectedIndex = this.selectedIndex ? parseInt(this.selectedIndex, 10) : 0;
// ensure the selectedIndex isn't a hidden or disabled tab
// also find the first available index incase we need it later
let availableIndex = -1;
this._tabs.forEach((tab, index) => {
if (tab.enabled && tab.show && availableIndex < 0) {
// we know this tab index is safe to show
availableIndex = index;
}
if (index === selectedIndex && (!tab.enabled || !tab.show)) {
// the selectedIndex is not safe to show
selectedIndex = -1;
}
});
if (selectedIndex < 0) {
// the selected index wasn't safe to show
// instead use an available index found to be safe to show
selectedIndex = availableIndex;
// get the selectedIndex and ensure it isn't hidden or disabled
let selectedTab = this._tabs.find((t, i) => i === selectedIndex && t.enabled && t.show);
if (!selectedTab) {
// wasn't able to select the tab they wanted
// try to find the first tab that's available
selectedTab = this._tabs.find(t => t.enabled && t.show);
}
this._tabs.forEach((tab, index) => {
if (index === selectedIndex) {
this.select(tab);
if (selectedTab) {
// we found a tab to select
this.select(selectedTab);
}
} else if (preloadTabs) {
tab.preload(1000 * index);
}
});
// check if preloadTab is set as an input @Input
// otherwise check the preloadTabs config
let shouldPreloadTabs = (isBlank(this.preloadTabs) ? this._config.getBoolean('preloadTabs') : isTrueProperty(this.preloadTabs));
if (shouldPreloadTabs) {
// preload all the tabs which isn't the selected tab
this._tabs.filter((t) => t !== selectedTab).forEach((tab, index) => {
tab.preload(this._config.getNumber('tabsPreloadDelay', 1000) * index);
});
}
}
/**
@@ -392,31 +372,33 @@ export class Tabs extends Ion {
/**
* @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select.
*/
select(tabOrIndex: number | Tab) {
select(tabOrIndex: number | Tab, opts: NavOptions = {}, done?: Function): Promise<any> {
let promise: Promise<any>;
if (!done) {
promise = new Promise(res => { done = res; });
}
let selectedTab: Tab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
if (isBlank(selectedTab)) {
return;
return Promise.resolve();
}
let deselectedTab = this.getSelected();
if (selectedTab === deselectedTab) {
// no change
return this._touchActive(selectedTab);
this._touchActive(selectedTab);
return Promise.resolve();
}
console.debug(`Tabs, select: ${selectedTab.id}`);
let opts = {
animate: false
};
let deselectedPage: ViewController;
if (deselectedTab) {
deselectedPage = deselectedTab.getActive();
deselectedPage && deselectedPage.fireWillLeave();
}
opts.animate = false;
let selectedPage = selectedTab.getActive();
selectedPage && selectedPage.fireWillEnter();
@@ -464,7 +446,11 @@ export class Tabs extends Ion {
});
}
}
done();
});
return promise;
}
/**
@@ -526,6 +512,13 @@ export class Tabs extends Ion {
return this._tabs.indexOf(tab);
}
/**
* @private
*/
length(): number {
return this._tabs.length;
}
/**
* @private
* "Touch" the active tab, going back to the root view of the tab
@@ -561,20 +554,6 @@ export class Tabs extends Ion {
return Promise.resolve();
}
/**
* @private
* Returns the root NavController. Returns `null` if Tabs is not
* within a NavController.
* @returns {NavController}
*/
get rootNav(): NavController {
let nav = this.parent;
while (nav && nav.parent) {
nav = nav.parent;
}
return nav;
}
/**
* @private
* DOM WRITE

View File

@@ -12,7 +12,8 @@ $tabbar-wp-item-icon-size: 2.4rem !default;
$tabbar-wp-item-height: 4.8rem !default;
$tab-button-wp-active-color: $toolbar-wp-active-color !default;
$tab-button-wp-inactive-color: rgba($toolbar-wp-inactive-color, .7) !default;
$tab-button-wp-inactive-opacity: .7 !default;
$tab-button-wp-inactive-color: rgba($toolbar-wp-inactive-color, $tab-button-wp-inactive-opacity) !default;
$tab-button-wp-background-activated: rgba(0, 0, 0, .1) !default;
@@ -96,7 +97,7 @@ ion-tabbar {
background-color: $color-base;
.tab-button {
color: $color-contrast;
color: rgba($color-contrast, $tab-button-wp-inactive-opacity);
}
.tab-button:hover:not(.disable-hover),

View File

@@ -1,6 +1,6 @@
import { Component, ViewChild } from '@angular/core';
import { ionicBootstrap, NavController, NavParams, ModalController, ViewController, Tabs, Tab } from '../../../../../src';
import { App, ionicBootstrap, NavController, NavParams, ModalController, ViewController, Tabs, Tab } from '../../../../../src';
@Component({
@@ -92,7 +92,7 @@ class TabsPage {
class Tab1Page1 {
userId: string;
constructor(private nav: NavController, private tabs: Tabs, private params: NavParams) {
constructor(private nav: NavController, private app: App, private tabs: Tabs, private params: NavParams) {
this.userId = params.get('userId');
}
@@ -102,7 +102,7 @@ class Tab1Page1 {
goBack() {
console.log('go back begin');
this.nav.pop().then((val) => {
this.nav.pop().then((val: any) => {
console.log('go back completed', val);
});;
}
@@ -112,7 +112,7 @@ class Tab1Page1 {
}
logout() {
this.nav.rootNav.setRoot(SignIn, null, { animate: true, direction: 'back' });
this.app.getRootNav().setRoot(SignIn, null, { animate: true, direction: 'back' });
}
ionViewWillEnter() {
@@ -323,10 +323,8 @@ class Tab3Page1 {
@Component({
template: '<ion-nav [root]="root" swipeBackEnabled="false"></ion-nav>'
template: '<ion-nav swipeBackEnabled="false"></ion-nav>'
})
class E2EApp {
root = SignIn;
}
class E2EApp {}
ionicBootstrap(E2EApp);

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController, App, AlertController, ModalController, ViewController, Tab, Tabs} from '../../../../../src';
import { Component} from '@angular/core';
import { ionicBootstrap, App, AlertController, ModalController, ViewController, Tab, Tabs } from '../../../../../src';
//
// Modal

View File

@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {ionicBootstrap, NavController, App, Alert, Modal, ViewController, Tab, Tabs} from '../../../../../src';
import { Component } from '@angular/core';
import { ionicBootstrap, NavController, App, Alert, Modal, ViewController, Tab, Tabs } from '../../../../../src';
//

View File

@@ -1,10 +1,89 @@
import {Component} from '@angular/core';
import {App, Nav, Tabs, Tab, NavOptions, Config, ViewController, Platform} from '../../../../src';
import { Component } from '@angular/core';
import { App, Config, Nav, NavOptions, Platform, Tab, Tabs, ViewController } from '../../../../src';
import { mockTab, mockTabs } from '../../../../src/util/mock-providers';
export function run() {
describe('Tabs', () => {
describe('initTabs', () => {
it('should preload all tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tab0.root = SomePage;
tab1.root = SomePage;
tab0.preload = () => {};
tab1.preload = () => {};
spyOn(tab0, 'preload');
spyOn(tab1, 'preload');
tabs.preloadTabs = true;
tabs.initTabs();
expect(tab0.isSelected).toEqual(true);
expect(tab1.isSelected).toEqual(false);
expect(tab0.preload).not.toHaveBeenCalled();
expect(tab1.preload).toHaveBeenCalled();
});
it('should not select a hidden or disabled tab', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tab0.root = SomePage;
tab1.root = SomePage;
tab1.enabled = false;
tab1.show = false;
tabs.selectedIndex = '1';
tabs.initTabs();
expect(tab0.isSelected).toEqual(true);
expect(tab1.isSelected).toEqual(false);
});
it('should select the second tab from selectedIndex input', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tab0.root = SomePage;
tab1.root = SomePage;
tabs.selectedIndex = '1';
tabs.initTabs();
expect(tab0.isSelected).toEqual(false);
expect(tab1.isSelected).toEqual(true);
});
it('should select the first tab by default', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tab0.root = SomePage;
tab1.root = SomePage;
spyOn(tab0, 'preload');
spyOn(tab1, 'preload');
tabs.initTabs();
expect(tab0.isSelected).toEqual(true);
expect(tab1.isSelected).toEqual(false);
expect(tab0.preload).not.toHaveBeenCalled();
expect(tab1.preload).not.toHaveBeenCalled();
});
});
describe('previousTab', () => {
it('should find the previous tab when there has been 3 selections', () => {
@@ -12,9 +91,6 @@ describe('Tabs', () => {
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
var tab2 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
tabs.add(tab2);
tab0.root = SomePage;
tab1.root = SomePage;
tab2.root = SomePage;
@@ -36,8 +112,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
@@ -56,8 +130,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
@@ -87,8 +159,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
@@ -103,12 +173,11 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
expect(tabs.length()).toEqual(2);
expect(tab0.isSelected).toBeUndefined();
expect(tab1.isSelected).toBeUndefined();
@@ -118,16 +187,6 @@ describe('Tabs', () => {
expect(tab1.isSelected).toEqual(false);
});
it('should not select an invalid tab index', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
expect(tabs.select(22)).toBeUndefined();
});
});
describe('getByIndex', () => {
@@ -137,8 +196,6 @@ describe('Tabs', () => {
var tab0 = mockTab(tabs);
tab0.setRoot(<any>{});
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
expect(tabs.getIndex(tab0)).toEqual(0);
expect(tabs.getIndex(tab1)).toEqual(1);
@@ -152,8 +209,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
tab1.setSelected(true);
@@ -164,48 +219,15 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
tabs.add(tab0);
tabs.add(tab1);
expect(tabs.getSelected()).toEqual(null);
});
});
var app: App;
var config: Config;
var platform: Platform;
var _cd: any;
function mockNav(): Nav {
return new Nav(null, null, null, config, null, null, null, null, null);
}
function mockTabs(): Tabs {
return new Tabs(null, null, null, config, null, null, null);
}
function mockTab(parentTabs: Tabs): Tab {
var tab = new Tab(parentTabs, app, config, null, null, null, null, null, _cd);
tab.load = function(opts: any, cb: Function) {
cb();
};
return tab;
}
@Component({})
class SomePage {}
beforeEach(() => {
config = new Config();
platform = new Platform();
app = new App(config, platform);
_cd = {
reattach: function(){},
detach: function(){}
};
});
});

View File

@@ -4,7 +4,6 @@ import { NgIf } from '@angular/common';
import { Animation } from '../../animations/animation';
import { Config } from '../../config/config';
import { isPresent } from '../../util/util';
import { NavController } from '../nav/nav-controller';
import { NavParams } from '../nav/nav-params';
import { Transition, TransitionOptions } from '../../transitions/transition';
import { ViewController } from '../nav/view-controller';
@@ -44,7 +43,6 @@ export class ToastCmp implements AfterViewInit {
private id: number;
constructor(
private _nav: NavController,
private _viewCtrl: ViewController,
private _config: Config,
private _elementRef: ElementRef,

View File

@@ -242,11 +242,12 @@ export class Toggle implements AfterContentInit, ControlValueAccessor, OnDestroy
*/
ngAfterContentInit() {
this._init = true;
this._events.pointerEventsRef(this._elementRef,
(ev: any) => this.pointerDown(ev),
(ev: any) => this.pointerMove(ev),
(ev: any) => this.pointerUp(ev)
);
this._events.pointerEvents({
elementRef: this._elementRef,
pointerDown: this.pointerDown.bind(this),
pointerMove: this.pointerMove.bind(this),
pointerUp: this.pointerUp.bind(this)
});
}
/**