From 66fcaa7a63e30a0f17f0a4ab74a1889bf4307c90 Mon Sep 17 00:00:00 2001 From: Justin Willis Date: Fri, 3 Mar 2017 09:14:22 -0600 Subject: [PATCH 01/13] chore(angular): update to 2.4.8 (#10592) * chore(angular): update to 2.4.8 * chore(index): export extra utils that we import and use --- package.json | 22 +++++++++++----------- src/index.ts | 4 ++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 9445473d42..1369bdecda 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,18 @@ "link": "gulp release.prepareReleasePackage && cd dist/ionic-angular && npm link" }, "dependencies": { - "@angular/common": "2.2.1", - "@angular/compiler": "2.2.1", - "@angular/compiler-cli": "2.2.1", - "@angular/core": "2.2.1", - "@angular/forms": "2.2.1", - "@angular/http": "2.2.1", - "@angular/platform-browser": "2.2.1", - "@angular/platform-browser-dynamic": "2.2.1", - "@angular/platform-server": "2.2.1", + "@angular/common": "2.4.8", + "@angular/compiler": "2.4.8", + "@angular/compiler-cli": "2.4.8", + "@angular/core": "2.4.8", + "@angular/forms": "2.4.8", + "@angular/http": "2.4.8", + "@angular/platform-browser": "2.4.8", + "@angular/platform-browser-dynamic": "2.4.8", + "@angular/platform-server": "2.4.8", "ionicons": "~3.0.0", - "rxjs": "5.0.0-beta.12", - "zone.js": "~0.6.26" + "rxjs": "5.0.1", + "zone.js": "0.7.2" }, "devDependencies": { "@ionic/app-scripts": "1.1.3", diff --git a/src/index.ts b/src/index.ts index b3464dacbb..09b16f774a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -259,6 +259,10 @@ export { reorderArray } from './util/util'; export { Animation, AnimationOptions, EffectProperty, EffectState, PlayOptions } from './animations/animation'; export { PageTransition } from './transitions/page-transition'; export { Transition } from './transitions/transition'; +export { PlatformConfigToken } from './platform/platform-registry'; +export { registerModeConfigs } from './config/mode-registry'; +export { registerTransitions } from './transitions/transition-registry'; +export { IonicGestureConfig } from './gestures/gesture-config'; From 9e4c3a6e3e434674ba690a32802f4b4cdadeb795 Mon Sep 17 00:00:00 2001 From: Manu MA Date: Fri, 3 Mar 2017 20:13:07 +0100 Subject: [PATCH 02/13] feat(split-panel): split panel support for ion-nav and ion-menu (#10343) * feat(split-panel): split panel support for ion-nav and ion-menu Revert some changes adds support split-panel support for tabs Removes .orig removes e2e.ts Removes unneeded changes in menu.ts improves stuff * fix(split-panel): resize is called when menu wraps a ion-nav * test(split-panel): improves split-panel/basic test * feat(split-panel): ionChange is an ng2 @Output() * fix(split-panel): fix tabs as side content * fix(menu): forzedClose works as expected * feat(split-panel): split-panel works with several menus * chore(split-panel): renames to split-pane * refactor(split-pane): splitPane can be injected * fix(split-pane): it is a directive * fix(slides): integration with split-panel * Make gulp validate happy --- src/animations/animation.ts | 14 +- src/components/app/app.ts | 5 +- src/components/menu/menu-controller.ts | 17 ++ src/components/menu/menu-toggle.ts | 7 +- src/components/menu/menu-types.ts | 6 +- src/components/menu/menu.ts | 188 +++++++++++------- .../menu/test/enable-disable/app.module.ts | 6 - src/components/nav/nav.ts | 19 +- src/components/nav/overlay-portal.ts | 4 + src/components/slides/slides.ts | 9 +- src/components/slides/swiper/swiper-events.ts | 12 +- src/components/split-pane/split-pane.ios.scss | 12 ++ src/components/split-pane/split-pane.md.scss | 13 ++ src/components/split-pane/split-pane.scss | 74 +++++++ src/components/split-pane/split-pane.ts | 173 ++++++++++++++++ src/components/split-pane/split-pane.wp.scss | 12 ++ .../split-pane/test/basic/app.module.ts | 120 +++++++++++ src/components/split-pane/test/basic/e2e.ts | 1 + .../split-pane/test/basic/main.html | 7 + .../split-pane/test/menus/app.module.ts | 102 ++++++++++ src/components/split-pane/test/menus/e2e.ts | 1 + .../split-pane/test/menus/main.html | 52 +++++ .../split-pane/test/nested/app.module.ts | 162 +++++++++++++++ src/components/split-pane/test/nested/e2e.ts | 1 + .../split-pane/test/nested/main.html | 10 + .../split-pane/test/tabs/app.module.ts | 98 +++++++++ src/components/split-pane/test/tabs/e2e.ts | 1 + src/components/split-pane/test/tabs/main.html | 8 + src/components/tabs/tab.ts | 21 +- src/components/tabs/tabs.ios.scss | 2 +- src/components/tabs/tabs.ts | 31 ++- src/index.ts | 4 + src/navigation/nav-controller-base.ts | 14 +- src/navigation/nav-controller.ts | 5 + src/themes/ionic.components.scss | 6 + src/util/mock-providers.ts | 4 +- 36 files changed, 1121 insertions(+), 100 deletions(-) create mode 100644 src/components/split-pane/split-pane.ios.scss create mode 100644 src/components/split-pane/split-pane.md.scss create mode 100644 src/components/split-pane/split-pane.scss create mode 100644 src/components/split-pane/split-pane.ts create mode 100644 src/components/split-pane/split-pane.wp.scss create mode 100644 src/components/split-pane/test/basic/app.module.ts create mode 100644 src/components/split-pane/test/basic/e2e.ts create mode 100644 src/components/split-pane/test/basic/main.html create mode 100644 src/components/split-pane/test/menus/app.module.ts create mode 100644 src/components/split-pane/test/menus/e2e.ts create mode 100644 src/components/split-pane/test/menus/main.html create mode 100644 src/components/split-pane/test/nested/app.module.ts create mode 100644 src/components/split-pane/test/nested/e2e.ts create mode 100644 src/components/split-pane/test/nested/main.html create mode 100644 src/components/split-pane/test/tabs/app.module.ts create mode 100644 src/components/split-pane/test/tabs/e2e.ts create mode 100644 src/components/split-pane/test/tabs/main.html diff --git a/src/animations/animation.ts b/src/animations/animation.ts index e6b0d175e5..aabeb857be 100644 --- a/src/animations/animation.ts +++ b/src/animations/animation.ts @@ -351,6 +351,18 @@ export class Animation { }); } + syncPlay() { + // If the animation was already invalidated (it did finish), do nothing + if (!this.plt) { + return; + } + const opts = { duration: 0 }; + this._isAsync = false; + this._clearAsync(); + this._playInit(opts); + this._playDomInspect(opts); + } + /** * @private * DOM WRITE @@ -569,7 +581,6 @@ export class Animation { return true; } } - return false; } @@ -589,7 +600,6 @@ export class Animation { return true; } } - return false; } diff --git a/src/components/app/app.ts b/src/components/app/app.ts index 90a3332ec1..15ceaf33be 100644 --- a/src/components/app/app.ts +++ b/src/components/app/app.ts @@ -85,8 +85,9 @@ export class App { runInDev(() => { // During developement, navPop can be triggered by calling - if (!(_plt.win())['HWBackButton']) { - (_plt.win())['HWBackButton'] = () => { + const win = _plt.win(); + if (!win['HWBackButton']) { + win['HWBackButton'] = () => { let p = this.goBack(); p && p.catch(() => console.debug('hardware go back cancelled')); return p; diff --git a/src/components/menu/menu-controller.ts b/src/components/menu/menu-controller.ts index 698e3280f9..6b67dd9dd9 100644 --- a/src/components/menu/menu-controller.ts +++ b/src/components/menu/menu-controller.ts @@ -309,6 +309,23 @@ export class MenuController { removeArrayItem(this._menus, menu); } + /** + * @private + */ + _setActiveMenu(menu: Menu) { + assert(menu.enabled); + assert(this._menus.indexOf(menu) >= 0, 'menu is not registered'); + + // if this menu should be enabled + // then find all the other menus on this same side + // and automatically disable other same side menus + const side = menu.side; + this._menus + .filter(m => m.side === side && m !== menu) + .map(m => m.enable(false)); + } + + /** * @private */ diff --git a/src/components/menu/menu-toggle.ts b/src/components/menu/menu-toggle.ts index 5b1569cf45..9c4266a500 100644 --- a/src/components/menu/menu-toggle.ts +++ b/src/components/menu/menu-toggle.ts @@ -129,7 +129,7 @@ export class MenuToggle { */ @HostListener('click') toggle() { - let menu = this._menu.get(this.menuToggle); + const menu = this._menu.get(this.menuToggle); menu && menu.toggle(); } @@ -137,13 +137,16 @@ export class MenuToggle { * @private */ get isHidden() { + const menu = this._menu.get(this.menuToggle); + if (!menu || !menu._canOpen()) { + return true; + } if (this._inNavbar && this._viewCtrl) { if (this._viewCtrl.isFirst()) { // this is the first view, so it should always show return false; } - let menu = this._menu.get(this.menuToggle); if (menu) { // this is not the root view, so see if this menu // is configured to still be enabled if it's not the root view diff --git a/src/components/menu/menu-types.ts b/src/components/menu/menu-types.ts index 3f9d458a0b..9ca1a59d8b 100644 --- a/src/components/menu/menu-types.ts +++ b/src/components/menu/menu-types.ts @@ -24,14 +24,14 @@ export class MenuType { } setOpen(shouldOpen: boolean, animated: boolean, done: Function) { - let ani = this.ani - .onFinish(done, true) + const ani = this.ani + .onFinish(done, true, true) .reverse(!shouldOpen); if (animated) { ani.play(); } else { - ani.play({ duration: 0 }); + ani.syncPlay(); } } diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index c1d2ef064e..6728e1d383 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, Input, NgZone, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, forwardRef, Input, NgZone, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; import { App } from '../app/app'; import { Backdrop } from '../backdrop/backdrop'; @@ -11,8 +11,10 @@ import { Keyboard } from '../../platform/keyboard'; import { MenuContentGesture } from './menu-gestures'; import { MenuController } from './menu-controller'; import { MenuType } from './menu-types'; +import { Nav } from '../nav/nav'; import { Platform } from '../../platform/platform'; import { UIEventManager } from '../../gestures/ui-event-manager'; +import { RootNode } from '../split-pane/split-pane'; /** * @name Menu @@ -188,19 +190,21 @@ import { UIEventManager } from '../../gestures/ui-event-manager'; }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, + providers: [{provide: RootNode, useExisting: forwardRef(() => Menu) }] }) -export class Menu { +export class Menu implements RootNode { private _cntEle: HTMLElement; private _gesture: MenuContentGesture; private _type: MenuType; - private _isEnabled: boolean = true; + private _isEnabled: boolean = false; private _isSwipeEnabled: boolean = true; private _isAnimating: boolean = false; private _isPersistent: boolean = false; private _init: boolean = false; private _events: UIEventManager; private _gestureBlocker: BlockerDelegate; + private _isPane: boolean = false; /** * @private @@ -217,6 +221,11 @@ export class Menu { */ @ContentChild(Content) menuContent: Content; + /** + * @private + */ + @ContentChild(Nav) menuNav: Nav; + /** * @input {any} A reference to the content element the menu should use. */ @@ -248,8 +257,8 @@ export class Menu { } set enabled(val: boolean) { - this._isEnabled = isTrueProperty(val); - this._setListeners(); + const isEnabled = isTrueProperty(val); + this.enable(isEnabled); } /** @@ -261,8 +270,8 @@ export class Menu { } set swipeEnabled(val: boolean) { - this._isSwipeEnabled = isTrueProperty(val); - this._setListeners(); + const isEnabled = isTrueProperty(val); + this.swipeEnable(isEnabled); } /** @@ -344,22 +353,20 @@ export class Menu { // add the gestures this._gesture = new MenuContentGesture(this._plt, this, this._gestureCtrl, this._domCtrl); - // register listeners if this menu is enabled - // check if more than one menu is on the same side - let hasEnabledSameSideMenu = this._menuCtrl.getMenus().some(m => { - return m.side === this.side && m.enabled; - }); - if (hasEnabledSameSideMenu) { - // auto-disable if another menu on the same side is already enabled - this._isEnabled = false; - } - this._setListeners(); - + // add menu's content classes this._cntEle.classList.add('menu-content'); this._cntEle.classList.add('menu-content-' + this.type); + // check if more than one menu is on the same side + let shouldEnable = !this._menuCtrl.getMenus().some(m => { + return m.side === this.side && m.enabled; + }); + // register this menu with the app's menu controller this._menuCtrl._register(this); + + // mask it as enabled / disabled + this.enable(shouldEnable); } /** @@ -371,27 +378,6 @@ export class Menu { this._menuCtrl.close(); } - /** - * @private - */ - private _setListeners() { - if (!this._init) { - return; - } - const gesture = this._gesture; - // only listen/unlisten if the menu has initialized - if (this._isEnabled && this._isSwipeEnabled && !gesture.isListening) { - // should listen, but is not currently listening - console.debug('menu, gesture listen', this.side); - gesture.listen(); - - } else if (gesture.isListening && (!this._isEnabled || !this._isSwipeEnabled)) { - // should not listen, but is currently listening - console.debug('menu, gesture unlisten', this.side); - gesture.unlisten(); - } - } - /** * @private */ @@ -411,13 +397,11 @@ export class Menu { */ setOpen(shouldOpen: boolean, animated: boolean = true): Promise { // If the menu is disabled or it is currenly being animated, let's do nothing - if ((shouldOpen === this.isOpen) || !this._isEnabled || this._isAnimating) { + if ((shouldOpen === this.isOpen) || !this._canOpen() || this._isAnimating) { return Promise.resolve(this.isOpen); } - - this._before(); - return new Promise(resolve => { + this._before(); this._getType().setOpen(shouldOpen, animated, () => { this._after(shouldOpen); resolve(this.isOpen); @@ -425,13 +409,21 @@ export class Menu { }); } + _forceClosing() { + assert(this.isOpen, 'menu cannot be closed'); + this._isAnimating = true; + this._getType().setOpen(false, false, () => { + this._after(false); + }); + } + /** * @private */ canSwipe(): boolean { - return this._isEnabled && - this._isSwipeEnabled && + return this._isSwipeEnabled && !this._isAnimating && + this._canOpen() && this._app.isEnabled(); } @@ -442,6 +434,7 @@ export class Menu { return this._isAnimating; } + _swipeBeforeStart() { if (!this.canSwipe()) { assert(false, 'canSwipe() has to be true'); @@ -500,7 +493,7 @@ export class Menu { // this css class doesn't actually kick off any animations this.setElementClass('show-menu', true); this.backdrop.setElementClass('show-backdrop', true); - this.menuContent && this.menuContent.resize(); + this.resize(); this._keyboard.close(); this._isAnimating = true; } @@ -554,6 +547,16 @@ export class Menu { return this.setOpen(false); } + /** + * @private + */ + resize() { + const content: Content | Nav = this.menuContent + ? this.menuContent + : this.menuNav; + content && content.resize(); + } + /** * @private */ @@ -561,38 +564,82 @@ export class Menu { return this.setOpen(!this.isOpen); } + _canOpen(): boolean { + return this._isEnabled && !this._isPane; + } + + _isSideContent(): boolean { + return true; + } + + /** + * @private + */ + _updateState() { + const canOpen = this._canOpen(); + + // Close menu inmediately + if (!canOpen && this.isOpen) { + // close if this menu is open, and should not be enabled + this._forceClosing(); + } + + if (this._isEnabled && this._menuCtrl) { + this._menuCtrl._setActiveMenu(this); + } + + if (!this._init) { + return; + } + const gesture = this._gesture; + // only listen/unlisten if the menu has initialized + if (canOpen && this._isSwipeEnabled && !gesture.isListening) { + // should listen, but is not currently listening + console.debug('menu, gesture listen', this.side); + gesture.listen(); + + } else if (gesture.isListening && (!canOpen || !this._isSwipeEnabled)) { + // should not listen, but is currently listening + console.debug('menu, gesture unlisten', this.side); + gesture.unlisten(); + } + if (this.isOpen || (this._isPane && this._isEnabled)) { + this.resize(); + } + assert(!this._isAnimating, 'can not be animating'); + } + /** * @private */ enable(shouldEnable: boolean): Menu { - this.enabled = shouldEnable; - if (!shouldEnable && this.isOpen) { - // close if this menu is open, and should not be enabled - this.close(); - } - - if (shouldEnable) { - // if this menu should be enabled - // then find all the other menus on this same side - // and automatically disable other same side menus - this._menuCtrl.getMenus() - .filter(m => m.side === this.side && m !== this) - .map(m => m.enabled = false); - } - - // TODO - // what happens if menu is disabled while swipping? - + this._isEnabled = shouldEnable; + this.setElementClass('menu-enabled', shouldEnable); + this._updateState(); return this; } + /** + * @internal + */ + initPane(): boolean { + return false; + } + + /** + * @internal + */ + paneChanged(isPane: boolean) { + this._isPane = isPane; + this._updateState(); + } + /** * @private */ swipeEnable(shouldEnable: boolean): Menu { - this.swipeEnabled = shouldEnable; - // TODO - // what happens if menu swipe is disabled while swipping? + this._isSwipeEnabled = shouldEnable; + this._updateState(); return this; } @@ -652,6 +699,13 @@ export class Menu { this._renderer.setElementAttribute(this._elementRef.nativeElement, attributeName, value); } + /** + * @private + */ + getElementRef(): ElementRef { + return this._elementRef; + } + /** * @private */ diff --git a/src/components/menu/test/enable-disable/app.module.ts b/src/components/menu/test/enable-disable/app.module.ts index 116141b939..097825ba35 100644 --- a/src/components/menu/test/enable-disable/app.module.ts +++ b/src/components/menu/test/enable-disable/app.module.ts @@ -37,17 +37,11 @@ export class E2EApp { menu1Active() { this.menuCtrl.enable(true, 'menu1'); - this.menuCtrl.enable(false, 'menu2'); - this.menuCtrl.enable(false, 'menu3'); } menu2Active() { - this.menuCtrl.enable(false, 'menu1'); this.menuCtrl.enable(true, 'menu2'); - this.menuCtrl.enable(false, 'menu3'); } menu3Active() { - this.menuCtrl.enable(false, 'menu1'); - this.menuCtrl.enable(false, 'menu2'); this.menuCtrl.enable(true, 'menu3'); } } diff --git a/src/components/nav/nav.ts b/src/components/nav/nav.ts index 6ccc896b5e..022059b84b 100644 --- a/src/components/nav/nav.ts +++ b/src/components/nav/nav.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, Input, Optional, NgZone, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; +import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, forwardRef, Input, Optional, NgZone, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { App } from '../app/app'; import { Config } from '../../config/config'; @@ -13,6 +13,7 @@ import { NavOptions } from '../../navigation/nav-util'; import { Platform } from '../../platform/platform'; import { TransitionController } from '../../transitions/transition-controller'; import { ViewController } from '../../navigation/view-controller'; +import { RootNode } from '../split-pane/split-pane'; /** * @name Nav @@ -52,8 +53,9 @@ import { ViewController } from '../../navigation/view-controller'; '
' + '', encapsulation: ViewEncapsulation.None, + providers: [{provide: RootNode, useExisting: forwardRef(() => Nav) }] }) -export class Nav extends NavControllerBase implements AfterViewInit { +export class Nav extends NavControllerBase implements AfterViewInit, RootNode { private _root: any; private _hasInit: boolean = false; @@ -160,8 +162,19 @@ export class Nav extends NavControllerBase implements AfterViewInit { /** * @private */ - destroy() { + ngOnDestroy() { this.destroy(); } + initPane(): boolean { + const isMain = this._elementRef.nativeElement.hasAttribute('main'); + return isMain; + } + + paneChanged(isPane: boolean) { + if (isPane) { + this.resize(); + } + } + } diff --git a/src/components/nav/overlay-portal.ts b/src/components/nav/overlay-portal.ts index 96ef72a9ba..ea1f119365 100644 --- a/src/components/nav/overlay-portal.ts +++ b/src/components/nav/overlay-portal.ts @@ -49,5 +49,9 @@ export class OverlayPortal extends NavControllerBase { this._zIndexOffset = (val || 0); } + ngOnDestroy() { + this.destroy(); + } + } diff --git a/src/components/slides/slides.ts b/src/components/slides/slides.ts index 054fe5b5d5..2c1d318034 100644 --- a/src/components/slides/slides.ts +++ b/src/components/slides/slides.ts @@ -890,7 +890,14 @@ export class Slides extends Ion { - constructor(config: Config, private _plt: Platform, zone: NgZone, @Optional() viewCtrl: ViewController, elementRef: ElementRef, renderer: Renderer) { + constructor( + config: Config, + private _plt: Platform, + zone: NgZone, + @Optional() viewCtrl: ViewController, + elementRef: ElementRef, + renderer: Renderer, + ) { super(config, elementRef, renderer, 'slides'); this._zone = zone; diff --git a/src/components/slides/swiper/swiper-events.ts b/src/components/slides/swiper/swiper-events.ts index f8f20cf3a2..0dc1b8e73e 100644 --- a/src/components/slides/swiper/swiper-events.ts +++ b/src/components/slides/swiper/swiper-events.ts @@ -85,7 +85,6 @@ export function initEvents(s: Slides, plt: Platform): Function { // onresize let resizeObs = plt.resize.subscribe(() => onResize(s, plt, false)); - // Next, Prev, Index if (s.nextButton) { plt.registerListener(s.nextButton, 'click', (ev) => { @@ -817,7 +816,18 @@ function onTouchEnd(s: Slides, plt: Platform, ev: SlideUIEvent) { /*========================= Resize Handler ===========================*/ +let resizeId: number; function onResize(s: Slides, plt: Platform, forceUpdatePagination: boolean) { + // TODO: hacky, we should use Resize Observer in the future + if (resizeId) { + plt.cancelTimeout(resizeId); + resizeId = null; + } + resizeId = plt.timeout(() => doResize(s, plt, forceUpdatePagination), 200); +} + +function doResize(s: Slides, plt: Platform, forceUpdatePagination: boolean) { + resizeId = null; // Disable locks on resize var allowSwipeToPrev = s._allowSwipeToPrev; var allowSwipeToNext = s._allowSwipeToNext; diff --git a/src/components/split-pane/split-pane.ios.scss b/src/components/split-pane/split-pane.ios.scss new file mode 100644 index 0000000000..59daf3d997 --- /dev/null +++ b/src/components/split-pane/split-pane.ios.scss @@ -0,0 +1,12 @@ + +@import "../../themes/ionic.globals.ios"; + +// Split Pane +// -------------------------------------------------- + +.split-pane-ios.split-pane-visible >.split-pane-side { + min-width: 270px; + max-width: 28%; + + border-right: $hairlines-width solid $list-ios-border-color; +} diff --git a/src/components/split-pane/split-pane.md.scss b/src/components/split-pane/split-pane.md.scss new file mode 100644 index 0000000000..e9a76e2d9b --- /dev/null +++ b/src/components/split-pane/split-pane.md.scss @@ -0,0 +1,13 @@ + +@import "../../themes/ionic.globals.md"; + +// Split Pane +// -------------------------------------------------- + +.split-pane-md.split-pane-visible >.split-pane-side { + min-width: 270px; + max-width: 28%; + + border-right: 1px solid $list-md-border-color; +} + diff --git a/src/components/split-pane/split-pane.scss b/src/components/split-pane/split-pane.scss new file mode 100644 index 0000000000..539d998234 --- /dev/null +++ b/src/components/split-pane/split-pane.scss @@ -0,0 +1,74 @@ + +@import "../../themes/ionic.globals"; + +// Split Pane +// -------------------------------------------------- + +ion-split-pane { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + display: flex; + + flex-wrap: nowrap; + + contain: strict; +} + +.split-pane-side:not(ion-menu) { + display: none; +} + +.split-pane-visible >.split-pane-side, +.split-pane-visible >.split-pane-main { + // scss-lint:disable ImportantRule + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 0; + + flex: 1; + + box-shadow: none !important; +} + +.split-pane-visible >.split-pane-side { + flex-shrink: 0; + + order: -1; +} + +.split-pane-visible >.split-pane-main, +.split-pane-visible >ion-nav.split-pane-side, +.split-pane-visible >ion-tabs.split-pane-side, +.split-pane-visible >ion-menu.menu-enabled { + display: block; +} + +.split-pane-visible >ion-split-pane.split-pane-side, +.split-pane-visible >ion-split-pane.split-pane-main { + display: flex; +} + +.split-pane-visible >ion-menu.menu-enabled { + >.menu-inner { + // scss-lint:disable ImportantRule + right: 0; + left: 0; + + width: auto; + + box-shadow: none !important; + transform: none !important; + } + + >.ion-backdrop { + // scss-lint:disable ImportantRule + display: hidden !important; + } +} diff --git a/src/components/split-pane/split-pane.ts b/src/components/split-pane/split-pane.ts new file mode 100644 index 0000000000..74d464ddb8 --- /dev/null +++ b/src/components/split-pane/split-pane.ts @@ -0,0 +1,173 @@ +import { ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, Input, Output, QueryList, NgZone, Renderer } from '@angular/core'; +import { Ion } from '../ion'; +import { assert } from '../../util/util'; +import { Config } from '../../config/config'; +import { Platform } from '../../platform/platform'; + +const QUERY: { [key: string]: string } = { + xs: '(min-width: 0px)', + sm: '(min-width: 576px)', + md: '(min-width: 768px)', + lg: '(min-width: 992px)', + xl: '(min-width: 1200px)', + never: '' +}; + +/** + * @private + */ +export abstract class RootNode { + abstract getElementRef(): ElementRef; + abstract initPane(): boolean; + abstract paneChanged?(visible: boolean): void; +} + +/** + * @name SplitPane + */ +@Directive({ + selector: 'ion-split-pane', + providers: [{provide: RootNode, useExisting: forwardRef(() => SplitPane) }] +}) +export class SplitPane extends Ion implements RootNode { + + _rmListener: any; + _visible: boolean = false; + _init: boolean = false; + _mediaQuery: string | boolean = QUERY['md']; + _children: RootNode[]; + + sideContent: RootNode = null; + mainContent: RootNode = null; + + @ContentChildren(RootNode, { descendants: false }) + set _setchildren(query: QueryList) { + const children = this._children = query.filter((child => child !== this)); + children.forEach(child => { + var isMain = child.initPane(); + this._setPaneCSSClass(child.getElementRef(), isMain); + }); + } + + @Input() + set when(query: string | boolean) { + if (typeof query === 'boolean') { + this._mediaQuery = query; + } else { + const defaultQuery = QUERY[query]; + this._mediaQuery = (defaultQuery) + ? defaultQuery + : query; + } + this._update(); + } + get when(): string | boolean { + return this._mediaQuery; + } + + @Output() ionChange: EventEmitter = new EventEmitter(); + + constructor( + private _zone: NgZone, + private _plt: Platform, + config: Config, + elementRef: ElementRef, + renderer: Renderer, + ) { + super(config, elementRef, renderer, 'split-pane'); + } + + _register(node: RootNode, isMain: boolean, callback: Function): boolean { + if (this.getElementRef().nativeElement !== node.getElementRef().nativeElement.parentNode) { + return false; + } + this._setPaneCSSClass(node.getElementRef(), isMain); + if (callback) { + this.ionChange.subscribe(callback); + } + if (isMain) { + if (this.mainContent) { + console.error('split pane: main content was already set'); + } + this.mainContent = node; + } + return true; + } + + ngAfterViewInit() { + this._init = true; + this._update(); + } + + _update() { + if (!this._init) { + return; + } + // Unlisten + this._rmListener && this._rmListener(); + this._rmListener = null; + + const query = this._mediaQuery; + if (typeof query === 'boolean') { + this._setVisible(query); + return; + } + if (query && query.length > 0) { + // Listen + const callback = (query: MediaQueryList) => this._setVisible(query.matches); + const mediaList = this._plt.win().matchMedia(query); + mediaList.addListener(callback); + this._setVisible(mediaList.matches); + this._rmListener = function () { + mediaList.removeListener(callback); + }; + } else { + this._setVisible(false); + } + } + + _updateChildren() { + this.mainContent = null; + this.sideContent = null; + const visible = this._visible; + this._children.forEach(child => child.paneChanged && child.paneChanged(visible)); + } + + _setVisible(visible: boolean) { + if (this._visible === visible) { + return; + } + this._visible = visible; + this.setElementClass('split-pane-visible', visible); + this._updateChildren(); + this._zone.run(() => { + this.ionChange.emit(this); + }); + } + + isVisible(): boolean { + return this._visible; + } + + setElementClass(className: string, add: boolean) { + this._renderer.setElementClass(this._elementRef.nativeElement, className, add); + } + + _setPaneCSSClass(elementRef: ElementRef, isMain: boolean) { + const ele = elementRef.nativeElement; + this._renderer.setElementClass(ele, 'split-pane-main', isMain); + this._renderer.setElementClass(ele, 'split-pane-side', !isMain); + } + + ngOnDestroy() { + assert(this._rmListener, 'at this point _rmListerner should be valid'); + + this._rmListener && this._rmListener(); + this._rmListener = null; + } + + initPane(): boolean { + return true; + } + +} diff --git a/src/components/split-pane/split-pane.wp.scss b/src/components/split-pane/split-pane.wp.scss new file mode 100644 index 0000000000..49f91aa36b --- /dev/null +++ b/src/components/split-pane/split-pane.wp.scss @@ -0,0 +1,12 @@ + +@import "../../themes/ionic.globals.wp"; + +// Split Pane +// -------------------------------------------------- + +.split-pane-wp.split-pane-visible >.split-pane-side { + min-width: 270px; + max-width: 28%; + + border-right: 1px solid $list-wp-border-color; +} diff --git a/src/components/split-pane/test/basic/app.module.ts b/src/components/split-pane/test/basic/app.module.ts new file mode 100644 index 0000000000..ce1cd257a7 --- /dev/null +++ b/src/components/split-pane/test/basic/app.module.ts @@ -0,0 +1,120 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, NavController, MenuController, SplitPane } from '../../../../../ionic-angular'; + + +@Component({ + template: ` + + Navigation + + + + Hola 1 + Hola 2 + Hola 3 + + Hola + Hola + Hola + + + + ` +}) +export class SidePage { + constructor(public navCtrl: NavController) { } + push() { + this.navCtrl.push(SidePage); + } +} + +@Component({ + template: ` + + + + Page 2 + + + +

Page 2

+
+ ` +}) +export class E2EPage2 {} + + +@Component({ + template: ` + + + + Navigation + + + +

Page 1

+ + +
+
+
+
+ +
+ ` +}) +export class E2EPage { + constructor( + public navCtrl: NavController, + public menuCtrl: MenuController, + ) { } + + push() { + this.navCtrl.push(E2EPage2); + } + + menu() { + this.menuCtrl.enable(!this.menuCtrl.isEnabled()); + } +} + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EApp { + root = E2EPage; + root2 = SidePage; + + splitPaneChanged(splitPane: SplitPane) { + console.log('Split pane changed, visible: ', splitPane.isVisible()); + } +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage, + E2EPage2, + SidePage, + ], + imports: [ + IonicModule.forRoot(E2EApp, { + swipeBackEnabled: true + }) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage, + E2EPage2, + SidePage, + ] +}) +export class AppModule {} + diff --git a/src/components/split-pane/test/basic/e2e.ts b/src/components/split-pane/test/basic/e2e.ts new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/components/split-pane/test/basic/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-pane/test/basic/main.html b/src/components/split-pane/test/basic/main.html new file mode 100644 index 0000000000..0ccaf46b3d --- /dev/null +++ b/src/components/split-pane/test/basic/main.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/split-pane/test/menus/app.module.ts b/src/components/split-pane/test/menus/app.module.ts new file mode 100644 index 0000000000..7d817418ce --- /dev/null +++ b/src/components/split-pane/test/menus/app.module.ts @@ -0,0 +1,102 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, NavController, MenuController, SplitPane } from '../../../../../ionic-angular'; + +@Component({ + template: ` + + + + Page 2 + + + +

Page 2

+
+ ` +}) +export class E2EPage2 {} + + +@Component({ + template: ` + + + + Navigation + + + +

Page 1

+ + + + + + +
+
+
+
+ +
+ ` +}) +export class E2EPage { + constructor( + public navCtrl: NavController, + public menuCtrl: MenuController, + ) { } + + push() { + this.navCtrl.push(E2EPage2); + } + menu1Active() { + this.menuCtrl.enable(true, 'menu1'); + } + menu2Active() { + this.menuCtrl.enable(true, 'menu2'); + } + menu3Active() { + this.menuCtrl.enable(true, 'menu3'); + } + disableAll() { + this.menuCtrl.enable(false); + } +} + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EApp { + root = E2EPage; + + splitPaneChanged(splitPane: SplitPane) { + console.log('Split pane changed, visible: ', splitPane.isVisible()); + } +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage, + E2EPage2, + ], + imports: [ + IonicModule.forRoot(E2EApp, { + swipeBackEnabled: true + }) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage, + E2EPage2, + ] +}) +export class AppModule {} + diff --git a/src/components/split-pane/test/menus/e2e.ts b/src/components/split-pane/test/menus/e2e.ts new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/components/split-pane/test/menus/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-pane/test/menus/main.html b/src/components/split-pane/test/menus/main.html new file mode 100644 index 0000000000..353e613197 --- /dev/null +++ b/src/components/split-pane/test/menus/main.html @@ -0,0 +1,52 @@ + + + + + + + Menu 1 + + + + + + Example + + + + + + + + + + + Menu 2 + + + + + + Example + + + + + + + + + + Menu 3 + + + + + + Example + + + + + + \ No newline at end of file diff --git a/src/components/split-pane/test/nested/app.module.ts b/src/components/split-pane/test/nested/app.module.ts new file mode 100644 index 0000000000..30827fcb39 --- /dev/null +++ b/src/components/split-pane/test/nested/app.module.ts @@ -0,0 +1,162 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, NavController } from '../../../../../ionic-angular'; + + +@Component({ + template: ` + + Nested 1 + + + +
+
+
+
+
+ ` +}) +export class E2ENested { + constructor( + public navCtrl: NavController, + ) { } + + push() { + this.navCtrl.push(E2ENested); + } +} + +@Component({ + template: ` + + Nested 2 + + + +
+
+
+
+
+ ` +}) +export class E2ENested2 { + constructor( + public navCtrl: NavController, + ) { } + + push() { + this.navCtrl.push(E2ENested2); + } +} + +@Component({ + template: ` + + + + Nested 3 + + + + +
+
+
+
+
+ ` +}) +export class E2ENested3 { + constructor( + public navCtrl: NavController, + ) { } + + push() { + this.navCtrl.push(E2ENested3); + } +} + + +@Component({ + template: ` + + Navigation + + + + Hola + Hola + Hola + + Hola + Hola + Hola + + + + ` +}) +export class SidePage { + constructor(public navCtrl: NavController) { } + push() { + this.navCtrl.push(SidePage); + } +} + +@Component({ + template: ` + + + + + + + + + + ` +}) +export class E2EPage { + root = E2ENested; + root2 = E2ENested2; + root3 = E2ENested3; +} + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EApp { + root = E2EPage; + root2 = SidePage; +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage, + SidePage, + E2ENested, + E2ENested2, + E2ENested3 + ], + imports: [ + IonicModule.forRoot(E2EApp, { + swipeBackEnabled: true + }) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage, + SidePage, + E2ENested, + E2ENested2, + E2ENested3 + ] +}) +export class AppModule {} + diff --git a/src/components/split-pane/test/nested/e2e.ts b/src/components/split-pane/test/nested/e2e.ts new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/components/split-pane/test/nested/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-pane/test/nested/main.html b/src/components/split-pane/test/nested/main.html new file mode 100644 index 0000000000..b7c24ede8d --- /dev/null +++ b/src/components/split-pane/test/nested/main.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/components/split-pane/test/tabs/app.module.ts b/src/components/split-pane/test/tabs/app.module.ts new file mode 100644 index 0000000000..c28f385002 --- /dev/null +++ b/src/components/split-pane/test/tabs/app.module.ts @@ -0,0 +1,98 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, NavController, MenuController } from '../../../../../ionic-angular'; + + +@Component({ + template: ` + + Navigation + + + + + +

Slide 1

+
+ + +

Slide 2

+
+ + +

Slide 3

+
+ +
+
+ ` +}) +export class SidePage { + constructor(public navCtrl: NavController) { } + push() { + this.navCtrl.push(SidePage); + } +} + + +@Component({ + template: ` + + + + Navigation + + + + +
+
+
+
+ +
+ ` +}) +export class E2EPage { + constructor( + public navCtrl: NavController, + public menuCtrl: MenuController, + ) { } + + push() { + this.navCtrl.push(E2EPage); + } +} + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EApp { + root = E2EPage; + root2 = SidePage; +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage, + SidePage, + ], + imports: [ + IonicModule.forRoot(E2EApp, { + swipeBackEnabled: true + }) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage, + SidePage, + ] +}) +export class AppModule {} + diff --git a/src/components/split-pane/test/tabs/e2e.ts b/src/components/split-pane/test/tabs/e2e.ts new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/components/split-pane/test/tabs/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-pane/test/tabs/main.html b/src/components/split-pane/test/tabs/main.html new file mode 100644 index 0000000000..342508f5a6 --- /dev/null +++ b/src/components/split-pane/test/tabs/main.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index efdd9fc995..bb5549feb9 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -314,17 +314,24 @@ export class Tab extends NavControllerBase { // to refresh the tabbar and content dimensions to be sure // they're lined up correctly this._dom.read(() => { - const active = this.getActive(); - if (!active) { - return; - } - const content = active.getIONContent(); - content && content.resize(); + this.resize(); }); done(true); } } + /** + * @private + */ + resize() { + const active = this.getActive(); + if (!active) { + return; + } + const content = active.getIONContent(); + content && content.resize(); + } + /** * @private */ @@ -385,7 +392,7 @@ export class Tab extends NavControllerBase { /** * @private */ - destroy() { + ngOnDestroy() { this.destroy(); } diff --git a/src/components/tabs/tabs.ios.scss b/src/components/tabs/tabs.ios.scss index b44d90e033..ac71ff5f97 100644 --- a/src/components/tabs/tabs.ios.scss +++ b/src/components/tabs/tabs.ios.scss @@ -10,7 +10,7 @@ $tabs-ios-border: $hairlines-width solid $tabs-ios-border-colo $tabs-ios-tab-padding: 0 2px !default; /// @prop - Max width of the tab button -$tabs-ios-tab-max-width: 240px !default; +$tabs-ios-tab-max-width: 110px !default; /// @prop - Minimum height of the tab button $tabs-ios-tab-min-height: 49px !default; diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 75d082a409..aeab923d56 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, EventEmitter, forwardRef, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { App } from '../app/app'; import { Config } from '../../config/config'; @@ -8,6 +8,7 @@ import { isBlank, assert } from '../../util/util'; import { NavController } from '../../navigation/nav-controller'; import { NavControllerBase } from '../../navigation/nav-controller-base'; import { getComponent, NavOptions, DIRECTION_SWITCH } from '../../navigation/nav-util'; +import { RootNode } from '../split-pane/split-pane'; import { Platform } from '../../platform/platform'; import { Tab } from './tab'; import { TabHighlight } from './tab-highlight'; @@ -161,8 +162,9 @@ import { ViewController } from '../../navigation/view-controller'; '' + '
', encapsulation: ViewEncapsulation.None, + providers: [{provide: RootNode, useExisting: forwardRef(() => Tabs) }] }) -export class Tabs extends Ion implements AfterViewInit { +export class Tabs extends Ion implements AfterViewInit, RootNode { /** @internal */ _ids: number = -1; /** @internal */ @@ -559,6 +561,31 @@ export class Tabs extends Ion implements AfterViewInit { } } + /** + * @internal + */ + resize() { + const tab = this.getSelected(); + tab && tab.resize(); + } + + /** + * @internal + */ + initPane(): boolean { + const isMain = this._elementRef.nativeElement.hasAttribute('main'); + return isMain; + } + + /** + * @internal + */ + paneChanged(isPane: boolean) { + if (isPane) { + this.resize(); + } + } + } let tabIds = -1; diff --git a/src/index.ts b/src/index.ts index 09b16f774a..26b2a41a4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,6 +106,7 @@ import { ShowWhen, HideWhen } from './components/show-hide-when/show-hide-when'; import { Slide } from './components/slides/slide'; import { Slides } from './components/slides/slides'; import { Spinner } from './components/spinner/spinner'; +import { SplitPane } from './components/split-pane/split-pane'; import { Tab } from './components/tabs/tab'; import { Tabs } from './components/tabs/tabs'; import { TabButton } from './components/tabs/tab-button'; @@ -191,6 +192,7 @@ export { ShowWhen, HideWhen, DisplayWhen } from './components/show-hide-when/sho export { Slide } from './components/slides/slide'; export { Slides } from './components/slides/slides'; export { Spinner } from './components/spinner/spinner'; +export { SplitPane, RootNode } from './components/split-pane/split-pane'; export { Tab } from './components/tabs/tab'; export { TabButton } from './components/tabs/tab-button'; export { TabHighlight } from './components/tabs/tab-highlight'; @@ -376,6 +378,7 @@ export { IonicGestureConfig } from './gestures/gesture-config'; Slide, Slides, Spinner, + SplitPane, Tab, Tabs, TabButton, @@ -470,6 +473,7 @@ export { IonicGestureConfig } from './gestures/gesture-config'; Slide, Slides, Spinner, + SplitPane, Tab, Tabs, TabButton, diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index 125cfb49e7..17de3ac7f7 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -800,7 +800,7 @@ export class NavControllerBase extends Ion implements NavController { _cleanup(activeView: ViewController) { // ok, cleanup time!! Destroy all of the views that are // INACTIVE and come after the active view - const activeViewIndex = this.indexOf(activeView); + const activeViewIndex = this._views.indexOf(activeView); const views = this._views; let reorderZIndexes = false; let view: ViewController; @@ -1019,7 +1019,8 @@ export class NavControllerBase extends Ion implements NavController { if (!view) { view = this.getActive(); } - return this._views[this.indexOf(view) - 1]; + const views = this._views; + return views[views.indexOf(view) - 1]; } first(): ViewController { @@ -1064,6 +1065,15 @@ export class NavControllerBase extends Ion implements NavController { this._viewport = val; } + resize() { + const active = this.getActive(); + if (!active) { + return; + } + const content = active.getIONContent(); + content && content.resize(); + } + } let ctrlIds = -1; diff --git a/src/navigation/nav-controller.ts b/src/navigation/nav-controller.ts index cb9959959b..1b77e61b19 100644 --- a/src/navigation/nav-controller.ts +++ b/src/navigation/nav-controller.ts @@ -609,4 +609,9 @@ export abstract class NavController { * @private */ abstract registerChildNav(nav: any): void; + + /** + * @private + */ + abstract resize(): void; } diff --git a/src/themes/ionic.components.scss b/src/themes/ionic.components.scss index 9c6bdbaf04..3477a394ce 100644 --- a/src/themes/ionic.components.scss +++ b/src/themes/ionic.components.scss @@ -190,6 +190,12 @@ @import "../components/slides/slides"; +@import +"../components/split-pane/split-pane", +"../components/split-pane/split-pane.ios", +"../components/split-pane/split-pane.md", +"../components/split-pane/split-pane.wp"; + @import "../components/spinner/spinner", "../components/spinner/spinner.ios", diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts index c7542c862e..00223ec668 100644 --- a/src/util/mock-providers.ts +++ b/src/util/mock-providers.ts @@ -496,7 +496,9 @@ export function mockMenu(): Menu { let app = mockApp(); let gestureCtrl = new GestureController(app); let dom = mockDomController(); - return new Menu(null, null, null, null, null, null, null, gestureCtrl, dom, app); + let elementRef = mockElementRef(); + let renderer = mockRenderer(); + return new Menu(null, elementRef, null, null, renderer, null, null, gestureCtrl, dom, app); } export function mockDeepLinkConfig(links?: any[]): DeepLinkConfig { From dc53c8e9f633a79a8b6ae72f09a36a5df6028eff Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Fri, 3 Mar 2017 20:26:37 +0100 Subject: [PATCH 03/13] fix(menu): all menus can be disabled --- src/components/menu/menu.ts | 19 ++++++++++++------- .../menu/test/multiple/app.module.ts | 7 +++---- src/components/menu/test/multiple/main.html | 4 ++-- src/components/slides/slides.ts | 8 +++++++- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 6728e1d383..a8c7a9383d 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -197,7 +197,7 @@ export class Menu implements RootNode { private _cntEle: HTMLElement; private _gesture: MenuContentGesture; private _type: MenuType; - private _isEnabled: boolean = false; + private _isEnabled: boolean; private _isSwipeEnabled: boolean = true; private _isAnimating: boolean = false; private _isPersistent: boolean = false; @@ -357,16 +357,18 @@ export class Menu implements RootNode { this._cntEle.classList.add('menu-content'); this._cntEle.classList.add('menu-content-' + this.type); - // check if more than one menu is on the same side - let shouldEnable = !this._menuCtrl.getMenus().some(m => { - return m.side === this.side && m.enabled; - }); - + let isEnabled = this._isEnabled; + if (isEnabled === true || typeof isEnabled === 'undefined') { + // check if more than one menu is on the same side + isEnabled = !this._menuCtrl.getMenus().some(m => { + return m.side === this.side && m.enabled; + }); + } // register this menu with the app's menu controller this._menuCtrl._register(this); // mask it as enabled / disabled - this.enable(shouldEnable); + this.enable(isEnabled); } /** @@ -580,6 +582,7 @@ export class Menu implements RootNode { // Close menu inmediately if (!canOpen && this.isOpen) { + assert(this._init, 'menu must be initialized'); // close if this menu is open, and should not be enabled this._forceClosing(); } @@ -591,6 +594,7 @@ export class Menu implements RootNode { if (!this._init) { return; } + const gesture = this._gesture; // only listen/unlisten if the menu has initialized if (canOpen && this._isSwipeEnabled && !gesture.isListening) { @@ -603,6 +607,7 @@ export class Menu implements RootNode { console.debug('menu, gesture unlisten', this.side); gesture.unlisten(); } + if (this.isOpen || (this._isPane && this._isEnabled)) { this.resize(); } diff --git a/src/components/menu/test/multiple/app.module.ts b/src/components/menu/test/multiple/app.module.ts index 1a8e3874e4..f1d042980d 100644 --- a/src/components/menu/test/multiple/app.module.ts +++ b/src/components/menu/test/multiple/app.module.ts @@ -6,11 +6,10 @@ import { IonicApp, IonicModule, MenuController } from '../../../../../ionic-angu templateUrl: 'page1.html' }) export class Page1 { - activeMenu: string; + activeMenu: string = 'none'; + + constructor(private menu: MenuController) { } - constructor(private menu: MenuController) { - this.menu1Active(); - } menu1Active() { this.activeMenu = 'menu1'; this.menu.enable(true, 'menu1'); diff --git a/src/components/menu/test/multiple/main.html b/src/components/menu/test/multiple/main.html index ace2c5772d..47c8783aeb 100644 --- a/src/components/menu/test/multiple/main.html +++ b/src/components/menu/test/multiple/main.html @@ -1,4 +1,4 @@ - + @@ -17,7 +17,7 @@ - + diff --git a/src/components/slides/slides.ts b/src/components/slides/slides.ts index 2c1d318034..86a9e7aac8 100644 --- a/src/components/slides/slides.ts +++ b/src/components/slides/slides.ts @@ -107,7 +107,7 @@ import { ViewController } from '../../navigation/view-controller'; * ```ts * import { ViewChild } from '@angular/core'; * import { Slides } from 'ionic-angular'; - + * class MyPage { * @ViewChild(Slides) slides: Slides; * @@ -971,6 +971,12 @@ export class Slides extends Ion { } } + resize() { + if (this._init) { + + } + } + /** * Transition to the specified slide. * From 6e1ae9710e5386217f42ffcd6294c8617fc5f8e9 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 27 Feb 2017 19:07:16 +0100 Subject: [PATCH 04/13] refactor(Tabs): moves markup to TabButton component --- src/components/tabs/tab-button.ts | 13 ++++++++++--- src/components/tabs/tabs.ts | 7 +------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/tabs/tab-button.ts b/src/components/tabs/tab-button.ts index 7ef63750f6..fe33a89e12 100644 --- a/src/components/tabs/tab-button.ts +++ b/src/components/tabs/tab-button.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Renderer } from '@angular/core'; +import { Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Renderer } from '@angular/core'; import { Config } from '../../config/config'; import { Ion } from '../ion'; @@ -7,8 +7,13 @@ import { Tab } from './tab'; /** * @private */ -@Directive({ +@Component({ selector: '.tab-button', + template: + '' + + '{{tab.tabTitle}}' + + '{{tab.tabBadge}}' + + '
', host: { '[attr.id]': 'tab._btnId', '[attr.aria-controls]': 'tab._tabId', @@ -18,7 +23,9 @@ import { Tab } from './tab'; '[class.has-title-only]': 'hasTitleOnly', '[class.icon-only]': 'hasIconOnly', '[class.has-badge]': 'hasBadge', - '[class.disable-hover]': 'disHover' + '[class.disable-hover]': 'disHover', + '[class.tab-disabled]': '!tab.enabled', + '[class.tab-hidden]': '!tab.show', } }) export class TabButton extends Ion implements OnInit { diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 0c2ef9c05f..5209c518fd 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -150,12 +150,7 @@ import { ViewController } from '../../navigation/view-controller'; selector: 'ion-tabs', template: '' + '' + From 5a4c6093a72059577544dafe372e06018ee1ed19 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 25 Feb 2017 22:29:24 +0100 Subject: [PATCH 05/13] refactor(NavController): adds better error handling fixes #10090 --- src/components/app/app.ts | 2 - src/components/menu/menu-controller.ts | 2 +- .../popover/test/basic/app.module.ts | 14 ++++- src/components/popover/test/basic/main.html | 5 ++ src/navigation/nav-controller-base.ts | 57 ++++++++++++------- src/navigation/nav-util.ts | 7 ++- src/navigation/test/view-controller.spec.ts | 5 +- src/navigation/view-controller.ts | 32 +++++++---- src/util/mock-providers.ts | 4 +- src/util/util.ts | 2 +- 10 files changed, 85 insertions(+), 45 deletions(-) diff --git a/src/components/app/app.ts b/src/components/app/app.ts index 15ceaf33be..5c3fad45f9 100644 --- a/src/components/app/app.ts +++ b/src/components/app/app.ts @@ -220,8 +220,6 @@ export class App { present(enteringView: ViewController, opts: NavOptions, appPortal?: AppPortal): Promise { const portal = this._appRoot._getPortal(appPortal); - enteringView._setNav(portal); - opts.keyboardClose = false; opts.direction = DIRECTION_FORWARD; diff --git a/src/components/menu/menu-controller.ts b/src/components/menu/menu-controller.ts index 6b67dd9dd9..d9fd882907 100644 --- a/src/components/menu/menu-controller.ts +++ b/src/components/menu/menu-controller.ts @@ -313,7 +313,7 @@ export class MenuController { * @private */ _setActiveMenu(menu: Menu) { - assert(menu.enabled); + assert(menu.enabled, 'menu must be enabled'); assert(this._menus.indexOf(menu) >= 0, 'menu is not registered'); // if this menu should be enabled diff --git a/src/components/popover/test/basic/app.module.ts b/src/components/popover/test/basic/app.module.ts index b0a99271fb..4cdd29228e 100644 --- a/src/components/popover/test/basic/app.module.ts +++ b/src/components/popover/test/basic/app.module.ts @@ -1,5 +1,5 @@ import { Component, ViewChild, ElementRef, ViewEncapsulation, NgModule } from '@angular/core'; -import { IonicApp, IonicModule, PopoverController, NavParams, ViewController } from '../../../../../ionic-angular'; +import { IonicApp, IonicModule, PopoverController, NavParams, ToastController, ViewController } from '../../../../../ionic-angular'; @Component({ @@ -188,7 +188,10 @@ export class E2EPage { @ViewChild('popoverContent', {read: ElementRef}) content: ElementRef; @ViewChild('popoverText', {read: ElementRef}) text: ElementRef; - constructor(private popoverCtrl: PopoverController) {} + constructor( + private popoverCtrl: PopoverController, + private toastCtrl: ToastController, + ) { } presentListPopover(ev: UIEvent) { let popover = this.popoverCtrl.create(PopoverListPage); @@ -221,6 +224,13 @@ export class E2EPage { this.popoverCtrl.create(PopoverListPage).present(); } + presentToast() { + this.toastCtrl.create({ + message: 'Toast example', + duration: 1000 + }).present(); + } + } diff --git a/src/components/popover/test/basic/main.html b/src/components/popover/test/basic/main.html index 2aeb0a626a..21a2cebe22 100644 --- a/src/components/popover/test/basic/main.html +++ b/src/components/popover/test/basic/main.html @@ -62,6 +62,11 @@
Aenean rhoncus urna at interdum blandit. Donec ac massa nec libero vehicula tincidunt. Sed sit amet hendrerit risus. Aliquam vitae vestibulum ipsum, non feugiat orci. Vivamus eu rutrum elit. Nulla dapibus tortor non dignissim pretium. Nulla in luctus turpis. Etiam non mattis tortor, at aliquet ex. Nunc ut ante varius, auctor dui vel, volutpat elit. Nunc laoreet augue sit amet ultrices porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum pellentesque lobortis est, ut tincidunt ligula mollis sit amet. In porta risus arcu, quis pellentesque dolor mattis non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
+ + + diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index 17de3ac7f7..c379f0a1bb 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -202,8 +202,8 @@ export class NavControllerBase extends Ion implements NavController { }); } + // ti.resolve() is called when the navigation transition is finished successfully ti.resolve = (hasCompleted: boolean, isAsync: boolean, enteringName: string, leavingName: string, direction: string) => { - // transition has successfully resolved this._trnsId = null; this._init = true; resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction); @@ -214,23 +214,22 @@ export class NavControllerBase extends Ion implements NavController { this._nextTrns(); }; - ti.reject = (rejectReason: any, trns: Transition) => { - // rut row raggy, something rejected this transition + // ti.reject() is called when the navigation transition fails. ie. it is rejected at some point. + ti.reject = (rejectReason: any, transition: Transition) => { this._trnsId = null; this._queue.length = 0; - while (trns) { - if (trns.enteringView && (trns.enteringView._state !== ViewState.LOADED)) { - // destroy the entering views and all of their hopes and dreams - this._destroyView(trns.enteringView); + // walk through the transition views so they are destroyed + while (transition) { + var enteringView = transition.enteringView; + if (enteringView && (enteringView._state === ViewState.ATTACHED)) { + this._destroyView(enteringView); } - if (!trns.parent) { + if (transition.isRoot()) { + this._trnsCtrl.destroy(transition.trnsId); break; } - } - - if (trns) { - this._trnsCtrl.destroy(trns.trnsId); + transition = transition.parent; } reject && reject(false, false, rejectReason); @@ -278,7 +277,19 @@ export class NavControllerBase extends Ion implements NavController { return false; } - // Get entering and leaving views + // ensure any of the inserted view are used + const insertViews = ti.insertViews; + if (insertViews) { + for (var i = 0; i < insertViews.length; i++) { + var nav = insertViews[i]._nav; + if (nav && nav !== this || insertViews[i]._state === ViewState.DESTROYED) { + ti.reject('leavingView and enteringView are null. stack is already empty'); + return false; + } + } + } + + // get entering and leaving views const leavingView = this.getActive(); const enteringView = this._getEnteringView(ti, leavingView); @@ -291,7 +302,7 @@ export class NavControllerBase extends Ion implements NavController { this.setTransitioning(true); // Initialize enteringView - if (enteringView && isBlank(enteringView._state)) { + if (enteringView && enteringView._state === ViewState.NEW) { // render the entering view, and all child navs and views // ******** DOM WRITE **************** this._viewInit(enteringView); @@ -303,7 +314,6 @@ export class NavControllerBase extends Ion implements NavController { // views have been initialized, now let's test // to see if the transition is even allowed or not return this._viewTest(enteringView, leavingView, ti); - } else { return this._postViewInit(enteringView, leavingView, ti); } @@ -419,7 +429,6 @@ export class NavControllerBase extends Ion implements NavController { // add the views to the for (i = 0; i < insertViews.length; i++) { view = insertViews[i]; - assert(view, 'view must be non null'); this._insertViewAt(view, ti.insertStart + i); } @@ -477,6 +486,9 @@ export class NavControllerBase extends Ion implements NavController { * DOM WRITE */ _viewInit(enteringView: ViewController) { + assert(enteringView, 'enteringView must be non null'); + assert(enteringView._state === ViewState.NEW, 'enteringView state must be NEW'); + // entering view has not been initialized yet const componentProviders = ReflectiveInjector.resolve([ { provide: NavController, useValue: this }, @@ -501,7 +513,7 @@ export class NavControllerBase extends Ion implements NavController { // render the component ref instance to the DOM // ******** DOM WRITE **************** viewport.insert(componentRef.hostView, viewport.length); - view._state = ViewState.PRE_RENDERED; + view._state = ViewState.ATTACHED; if (view._cssClass) { // the ElementRef of the actual ion-page created @@ -604,14 +616,12 @@ export class NavControllerBase extends Ion implements NavController { } }); - if (enteringView && enteringView._state === ViewState.INITIALIZED) { + if (enteringView && (enteringView._state === ViewState.INITIALIZED)) { // render the entering component in the DOM // this would also render new child navs/views // which may have their very own async canEnter/Leave tests // ******** DOM WRITE **************** this._viewAttachToDOM(enteringView, enteringView._cmp, this._viewport); - } else { - console.debug('enteringView state is not INITIALIZED', enteringView); } if (!transition.hasChildren) { @@ -762,9 +772,10 @@ export class NavControllerBase extends Ion implements NavController { if (existingIndex > -1) { // this view is already in the stack!! // move it to its new location + assert(view._nav === this, 'view is not part of the nav'); this._views.splice(index, 0, this._views.splice(existingIndex, 1)[0]); - } else { + assert(!view._nav, 'nav is used'); // this is a new view to add to the stack // create the new entering view view._setNav(this); @@ -781,6 +792,8 @@ export class NavControllerBase extends Ion implements NavController { } _removeView(view: ViewController) { + assert(view._state === ViewState.ATTACHED || view._state === ViewState.DESTROYED, 'view state should be loaded or destroyed'); + const views = this._views; const index = views.indexOf(view); assert(index > -1, 'view must be part of the stack'); @@ -1056,7 +1069,7 @@ export class NavControllerBase extends Ion implements NavController { dismissPageChangeViews() { for (let view of this._views) { if (view.data && view.data.dismissOnPageChange) { - view.dismiss(); + view.dismiss().catch(null); } } } diff --git a/src/navigation/nav-util.ts b/src/navigation/nav-util.ts index d5e42b2ff2..dffdd2f09b 100644 --- a/src/navigation/nav-util.ts +++ b/src/navigation/nav-util.ts @@ -193,9 +193,10 @@ export interface TransitionInstruction { } export enum ViewState { - INITIALIZED, - PRE_RENDERED, - LOADED, + NEW, // New created ViewController + INITIALIZED, // Initialized by the NavController + ATTACHED, // Loaded to the DOM + DESTROYED // Destroyed by the NavController } export const INIT_ZINDEX = 100; diff --git a/src/navigation/test/view-controller.spec.ts b/src/navigation/test/view-controller.spec.ts index e4ed040d47..5fccae6018 100644 --- a/src/navigation/test/view-controller.spec.ts +++ b/src/navigation/test/view-controller.spec.ts @@ -1,5 +1,5 @@ import { mockNavController, mockView, mockViews } from '../../util/mock-providers'; - +import { ViewState } from '../nav-util'; describe('ViewController', () => { @@ -16,6 +16,7 @@ describe('ViewController', () => { }); // act + viewController._state = ViewState.ATTACHED; viewController._willEnter(); }, 10000); }); @@ -33,6 +34,7 @@ describe('ViewController', () => { }); // act + viewController._state = ViewState.ATTACHED; viewController._didEnter(); }, 10000); }); @@ -50,6 +52,7 @@ describe('ViewController', () => { }); // act + viewController._state = ViewState.ATTACHED; viewController._willLeave(false); }, 10000); }); diff --git a/src/navigation/view-controller.ts b/src/navigation/view-controller.ts index 49a94746cc..5b98ac9025 100644 --- a/src/navigation/view-controller.ts +++ b/src/navigation/view-controller.ts @@ -1,7 +1,7 @@ import { ComponentRef, ElementRef, EventEmitter, Output, Renderer } from '@angular/core'; import { Footer, Header } from '../components/toolbar/toolbar'; -import { isPresent } from '../util/util'; +import { isPresent, assert } from '../util/util'; import { Navbar } from '../components/navbar/navbar'; import { NavController } from './nav-controller'; import { NavOptions, ViewState } from './nav-util'; @@ -46,7 +46,7 @@ export class ViewController { _cmp: ComponentRef; _nav: NavController; _zIndex: number; - _state: ViewState; + _state: ViewState = ViewState.NEW; _cssClass: string; /** @@ -227,7 +227,7 @@ export class ViewController { * @private */ get name(): string { - return this.component ? this.component.name : ''; + return (this.component ? this.component.name : ''); } /** @@ -261,14 +261,12 @@ export class ViewController { // _hidden value of '' means the hidden attribute will be added // _hidden value of null means the hidden attribute will be removed // doing checks to make sure we only update the DOM when actually needed - if (this._cmp) { - // if it should render, then the hidden attribute should not be on the element - if (shouldShow === this._isHidden) { - this._isHidden = !shouldShow; - let value = (shouldShow ? null : ''); - // ******** DOM WRITE **************** - renderer.setElementAttribute(this.pageRef().nativeElement, 'hidden', value); - } + // if it should render, then the hidden attribute should not be on the element + if (this._cmp && shouldShow === this._isHidden) { + this._isHidden = !shouldShow; + let value = (shouldShow ? null : ''); + // ******** DOM WRITE **************** + renderer.setElementAttribute(this.pageRef().nativeElement, 'hidden', value); } } @@ -411,6 +409,7 @@ export class ViewController { } _preLoad() { + assert(this._state === ViewState.INITIALIZED, 'view state must be INITIALIZED'); this._lifecycle('PreLoad'); } @@ -420,6 +419,7 @@ export class ViewController { * This event is fired before the component and his children have been initialized. */ _willLoad() { + assert(this._state === ViewState.INITIALIZED, 'view state must be INITIALIZED'); this._lifecycle('WillLoad'); } @@ -432,6 +432,7 @@ export class ViewController { * recommended method to use when a view becomes active. */ _didLoad() { + assert(this._state === ViewState.ATTACHED, 'view state must be ATTACHED'); this._lifecycle('DidLoad'); } @@ -440,6 +441,8 @@ export class ViewController { * The view is about to enter and become the active view. */ _willEnter() { + assert(this._state === ViewState.ATTACHED, 'view state must be ATTACHED'); + if (this._detached && this._cmp) { // ensure this has been re-attached to the change detector this._cmp.changeDetectorRef.reattach(); @@ -456,6 +459,8 @@ export class ViewController { * will fire, whether it was the first load or loaded from the cache. */ _didEnter() { + assert(this._state === ViewState.ATTACHED, 'view state must be ATTACHED'); + this._nb && this._nb.didEnter(); this.didEnter.emit(null); this._lifecycle('DidEnter'); @@ -510,6 +515,8 @@ export class ViewController { * DOM WRITE */ _destroy(renderer: Renderer) { + assert(this._state !== ViewState.DESTROYED, 'view state must be ATTACHED'); + if (this._cmp) { if (renderer) { // ensure the element is cleaned up for when the view pool reuses this element @@ -524,6 +531,7 @@ export class ViewController { } this._nav = this._cmp = this.instance = this._cntDir = this._cntRef = this._leavingOpts = this._hdrDir = this._ftrDir = this._nb = this._onDidDismiss = this._onWillDismiss = null; + this._state = ViewState.DESTROYED; } /** @@ -534,7 +542,7 @@ export class ViewController { const methodName = 'ionViewCan' + lifecycle; if (instance && instance[methodName]) { try { - let result = instance[methodName](); + var result = instance[methodName](); if (result === false) { return false; } else if (result instanceof Promise) { diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts index 00223ec668..1bd1d33c69 100644 --- a/src/util/mock-providers.ts +++ b/src/util/mock-providers.ts @@ -348,7 +348,9 @@ export function mockView(component?: any, data?: any) { export function mockViews(nav: NavControllerBase, views: ViewController[]) { nav._views = views; - views.forEach(v => v._setNav(nav)); + views.forEach(v => { + v._setNav(nav); + }); } export function mockComponentRef(): ComponentRef { diff --git a/src/util/util.ts b/src/util/util.ts index bfbe21c6f1..b2fb6488e8 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -170,7 +170,7 @@ function _runInDev(fn: Function) { /** @private */ -function _assert(actual: any, reason?: string) { +function _assert(actual: any, reason: string) { if (!actual && ASSERT_ENABLED === true) { let message = 'IONIC ASSERT: ' + reason; console.error(message); From c65bb4e64bd66c461c25ca3d903ecb484b3502dc Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 4 Mar 2017 19:21:21 +0100 Subject: [PATCH 06/13] style(menu): removes unused method --- src/components/menu/menu.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index a8c7a9383d..aa1503474f 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -570,10 +570,6 @@ export class Menu implements RootNode { return this._isEnabled && !this._isPane; } - _isSideContent(): boolean { - return true; - } - /** * @private */ From 930869476aa2295f1110f6babd51168303cfaa31 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sun, 5 Mar 2017 12:30:52 +0100 Subject: [PATCH 07/13] fix(refresher): events manager must not be destroyed fixes #10652 --- src/components/refresher/refresher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/refresher/refresher.ts b/src/components/refresher/refresher.ts index 3b82dc34c2..70a266b551 100644 --- a/src/components/refresher/refresher.ts +++ b/src/components/refresher/refresher.ts @@ -477,7 +477,7 @@ export class Refresher { } _setListeners(shouldListen: boolean) { - this._events.destroy(); + this._events.unlistenAll(); this._pointerEvents = null; if (shouldListen) { this._pointerEvents = this._events.pointerEvents({ From 1dd88830f9152b125b1f044d838cde28ea2273be Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sun, 5 Mar 2017 14:41:14 +0100 Subject: [PATCH 08/13] fix(refresher): events manager must not be destroyed (part 2) fixes #10652 --- src/components/refresher/refresher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/refresher/refresher.ts b/src/components/refresher/refresher.ts index 70a266b551..ee4ffce4c0 100644 --- a/src/components/refresher/refresher.ts +++ b/src/components/refresher/refresher.ts @@ -503,9 +503,9 @@ export class Refresher { * @private */ ngOnDestroy() { + this._setListeners(false); this._events.destroy(); this._gesture.destroy(); - this._setListeners(false); } } From 61a5317b25d889d859e309f79dbdb0b8d6b43b2e Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sun, 5 Mar 2017 16:00:47 +0100 Subject: [PATCH 09/13] fix(ViewController): nav must be set as soon as possible fixes regression in 5a4c6093a72059577544dafe372e06018ee1ed19 fixes #10654 --- src/components/app/app.ts | 4 ++++ src/components/loading/test/basic/app.module.ts | 15 +++++++++++++++ src/components/loading/test/basic/main.html | 1 + src/navigation/nav-controller-base.ts | 2 +- src/navigation/view-controller.ts | 1 + 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/app/app.ts b/src/components/app/app.ts index 5c3fad45f9..7aee7bcccd 100644 --- a/src/components/app/app.ts +++ b/src/components/app/app.ts @@ -220,6 +220,10 @@ export class App { present(enteringView: ViewController, opts: NavOptions, appPortal?: AppPortal): Promise { const portal = this._appRoot._getPortal(appPortal); + // Set Nav must be set here in order to dimiss() work synchnously. + // TODO: move _setNav() to the earlier stages of NavController. _queueTrns() + enteringView._setNav(portal); + opts.keyboardClose = false; opts.direction = DIRECTION_FORWARD; diff --git a/src/components/loading/test/basic/app.module.ts b/src/components/loading/test/basic/app.module.ts index f4e714156a..d1bfc53645 100644 --- a/src/components/loading/test/basic/app.module.ts +++ b/src/components/loading/test/basic/app.module.ts @@ -254,6 +254,21 @@ export class E2EPage { this.navCtrl.push(Page2); }, 500); } + + presentLoadingOpenDismiss() { + // debugger; + const loading = this.loadingCtrl.create({ + content: 'Loading 1' + }); + loading.present(); + loading.dismiss(); + + const loading2 = this.loadingCtrl.create({ + content: 'Loading 2' + }); + loading2.present(); + loading2.dismiss(); + } } @Component({ diff --git a/src/components/loading/test/basic/main.html b/src/components/loading/test/basic/main.html index 4f86675fe4..182d7cfd9a 100644 --- a/src/components/loading/test/basic/main.html +++ b/src/components/loading/test/basic/main.html @@ -22,6 +22,7 @@ + diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index c379f0a1bb..a9486e598a 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -775,7 +775,7 @@ export class NavControllerBase extends Ion implements NavController { assert(view._nav === this, 'view is not part of the nav'); this._views.splice(index, 0, this._views.splice(existingIndex, 1)[0]); } else { - assert(!view._nav, 'nav is used'); + assert(!view._nav || (this._isPortal && view._nav === this), 'nav is used'); // this is a new view to add to the stack // create the new entering view view._setNav(this); diff --git a/src/navigation/view-controller.ts b/src/navigation/view-controller.ts index 5b98ac9025..068e25d0f7 100644 --- a/src/navigation/view-controller.ts +++ b/src/navigation/view-controller.ts @@ -165,6 +165,7 @@ export class ViewController { */ dismiss(data?: any, role?: any, navOptions: NavOptions = {}): Promise { if (!this._nav) { + assert(this._state === ViewState.DESTROYED, 'ViewController does not have a valid _nav'); return Promise.resolve(false); } if (this.isOverlay && !navOptions.minClickBlockDuration) { From 8b3991cc78ef3c3a8f98fdccb01a3314f1ea0290 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 6 Mar 2017 17:23:07 +0100 Subject: [PATCH 10/13] fix(tabs): reverts 9e4c3a6e3e434674ba690a32802f4b4cdadeb795 --- src/components/tabs/tabs.ios.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tabs/tabs.ios.scss b/src/components/tabs/tabs.ios.scss index ac71ff5f97..b44d90e033 100644 --- a/src/components/tabs/tabs.ios.scss +++ b/src/components/tabs/tabs.ios.scss @@ -10,7 +10,7 @@ $tabs-ios-border: $hairlines-width solid $tabs-ios-border-colo $tabs-ios-tab-padding: 0 2px !default; /// @prop - Max width of the tab button -$tabs-ios-tab-max-width: 110px !default; +$tabs-ios-tab-max-width: 240px !default; /// @prop - Minimum height of the tab button $tabs-ios-tab-min-height: 49px !default; From e56bad99774e7906fb551197b465ee608c2b95f2 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 6 Mar 2017 17:58:12 +0100 Subject: [PATCH 11/13] fix(menu): menuToggle always visible outside navbar --- src/components/menu/menu-toggle.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/menu/menu-toggle.ts b/src/components/menu/menu-toggle.ts index 9c4266a500..ff1a62d36e 100644 --- a/src/components/menu/menu-toggle.ts +++ b/src/components/menu/menu-toggle.ts @@ -138,10 +138,11 @@ export class MenuToggle { */ get isHidden() { const menu = this._menu.get(this.menuToggle); - if (!menu || !menu._canOpen()) { - return true; - } if (this._inNavbar && this._viewCtrl) { + if (!menu || !menu._canOpen()) { + return true; + } + if (this._viewCtrl.isFirst()) { // this is the first view, so it should always show return false; From a4748fca69cd5a4646b0ed713242d1627b7e9c7b Mon Sep 17 00:00:00 2001 From: mhartington Date: Mon, 6 Mar 2017 13:24:07 -0500 Subject: [PATCH 12/13] docs(splitPane): add split-pane docs --- src/components/split-pane/split-pane.ts | 159 ++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/components/split-pane/split-pane.ts b/src/components/split-pane/split-pane.ts index 74d464ddb8..4c931470f7 100644 --- a/src/components/split-pane/split-pane.ts +++ b/src/components/split-pane/split-pane.ts @@ -24,6 +24,117 @@ export abstract class RootNode { /** * @name SplitPane + * + * @description + * SplitPane is a component that makes it possible to create multi-view layout. + * Similar to iPad apps, SplitPane allows UI elements, like Menus, to be + * displayed as the viewport increases. + * + * If the devices screen size is below a certain size, the SplitPane will + * collapse and the menu will become hidden again. This is especially useful when + * creating an app that will be served over a browser or deployed through the app + * store to phones and tablets. + * + * @usage + * To use SplitPane, simply add the component around your root component. + * In this example, we'll be using a sidemenu layout, similar to what is + * provided from the sidemenu starter template. + * + * ```html + * + * + * + * + * + * Menu + * + * + * + * + * + * + * + * ``` + * + * Here, SplitPane will look for the element with the `main` attribute and make + * that the central component on larger screens. The `main` component can be any + * Ionic component (`ion-nav` or `ion-tabs`) except `ion-menu`. + * + * ### Setting breakpoints + * + * By default, SplitPane will expand when the screen is larger than 768px. + * If you want to customize this, use the `when` input. The `when` input can + * accept any valid media query, as it uses `matchMedia()` underneath. + * + * ``` + * + * + * + * + * .... + * + * + * + * + * + * ``` + * + * SplitPane also provides some predefined media queries that can be used. + * + * ```html + * + * + * ... + * + * ``` + * + * + * | Size | Value | Description | + * |------|-----------------------|-----------------------------------------------------------------------| + * | `xs` | `(min-width: 0px)` | Show the split-pane when the min-width is 0px (meaning, always) | + * | `sm` | `(min-width: 576px)` | Show the split-pane when the min-width is 576px | + * | `md` | `(min-width: 768px)` | Show the split-pane when the min-width is 768px (default break point) | + * | `lg` | `(min-width: 992px)` | Show the split-pane when the min-width is 992px | + * | `xl` | `(min-width: 1200px)` | Show the split-pane when the min-width is 1200px | + * + * You can also pass in boolean values that will trigger SplitPane when the value + * or expression evaluates to true. + * + * + * ```html + * + * ... + * + * ``` + * + * ```ts + * class MyClass { + * public isLarge = false; + * constructor(){} + * } + * ``` + * + * Or + * + * ```html + * + * ... + * + * ``` + * + * ```ts + * class MyClass { + * constructor(){} + * shouldShow(){ + * if(conditionA){ + * return true + * } else { + * return false + * } + * } + * } + * ``` + * */ @Directive({ selector: 'ion-split-pane', @@ -37,9 +148,18 @@ export class SplitPane extends Ion implements RootNode { _mediaQuery: string | boolean = QUERY['md']; _children: RootNode[]; + /** + * @private + */ sideContent: RootNode = null; + /** + * @private + */ mainContent: RootNode = null; + /** + * @private + */ @ContentChildren(RootNode, { descendants: false }) set _setchildren(query: QueryList) { const children = this._children = query.filter((child => child !== this)); @@ -49,6 +169,11 @@ export class SplitPane extends Ion implements RootNode { }); } + /** + * @input {string | boolean} When the split-pane should be shown. + * Can be a CSS media query expression, or a shortcut expression. + * Can aslo be a boolean expression. + */ @Input() set when(query: string | boolean) { if (typeof query === 'boolean') { @@ -65,6 +190,9 @@ export class SplitPane extends Ion implements RootNode { return this._mediaQuery; } + /** + * @output {any} Expression to be called when the split-pane visibility has changed + */ @Output() ionChange: EventEmitter = new EventEmitter(); constructor( @@ -77,6 +205,10 @@ export class SplitPane extends Ion implements RootNode { super(config, elementRef, renderer, 'split-pane'); } + + /** + * @private + */ _register(node: RootNode, isMain: boolean, callback: Function): boolean { if (this.getElementRef().nativeElement !== node.getElementRef().nativeElement.parentNode) { return false; @@ -94,11 +226,17 @@ export class SplitPane extends Ion implements RootNode { return true; } + /** + * @private + */ ngAfterViewInit() { this._init = true; this._update(); } + /** + * @private + */ _update() { if (!this._init) { return; @@ -126,6 +264,9 @@ export class SplitPane extends Ion implements RootNode { } } + /** + * @private + */ _updateChildren() { this.mainContent = null; this.sideContent = null; @@ -133,6 +274,9 @@ export class SplitPane extends Ion implements RootNode { this._children.forEach(child => child.paneChanged && child.paneChanged(visible)); } + /** + * @private + */ _setVisible(visible: boolean) { if (this._visible === visible) { return; @@ -145,20 +289,32 @@ export class SplitPane extends Ion implements RootNode { }); } + /** + * @private + */ isVisible(): boolean { return this._visible; } + /** + * @private + */ setElementClass(className: string, add: boolean) { this._renderer.setElementClass(this._elementRef.nativeElement, className, add); } + /** + * @private + */ _setPaneCSSClass(elementRef: ElementRef, isMain: boolean) { const ele = elementRef.nativeElement; this._renderer.setElementClass(ele, 'split-pane-main', isMain); this._renderer.setElementClass(ele, 'split-pane-side', !isMain); } + /** + * @private + */ ngOnDestroy() { assert(this._rmListener, 'at this point _rmListerner should be valid'); @@ -166,6 +322,9 @@ export class SplitPane extends Ion implements RootNode { this._rmListener = null; } + /** + * @private + */ initPane(): boolean { return true; } From 88a7f3e1c5dcb849a084cb2212cea1f94d66c52a Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 6 Mar 2017 22:28:21 +0100 Subject: [PATCH 13/13] test(toolbar): fix model header --- src/components/alert/test/basic/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/alert/test/basic/app.module.ts b/src/components/alert/test/basic/app.module.ts index fd0a8d8cf7..d9e0d7a033 100644 --- a/src/components/alert/test/basic/app.module.ts +++ b/src/components/alert/test/basic/app.module.ts @@ -306,7 +306,7 @@ export class E2EPage { template: ` - + Modal