From 3596c24bff127c0cec35e5803adf697e0a830fe5 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Tue, 7 Feb 2017 21:11:47 +0100 Subject: [PATCH] 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 --- src/animations/animation.ts | 11 ++ src/components/menu/menu-controller.ts | 19 ++- src/components/menu/menu-toggle.ts | 7 +- src/components/menu/menu-types.ts | 4 +- src/components/menu/menu.ts | 135 +++++++++------ src/components/nav/nav.ts | 24 ++- src/components/nav/overlay-portal.ts | 4 + .../split-panel/split-panel.ios.scss | 12 ++ .../split-panel/split-panel.md.scss | 13 ++ src/components/split-panel/split-panel.scss | 69 ++++++++ src/components/split-panel/split-panel.ts | 160 ++++++++++++++++++ .../split-panel/split-panel.wp.scss | 12 ++ .../split-panel/test/basic/app.module.ts | 96 +++++++++++ src/components/split-panel/test/basic/e2e.ts | 1 + .../split-panel/test/basic/main.html | 7 + .../split-panel/test/nested/app.module.ts | 157 +++++++++++++++++ src/components/split-panel/test/nested/e2e.ts | 1 + .../split-panel/test/nested/main.html | 10 ++ .../split-panel/test/tabs/app.module.ts | 91 ++++++++++ src/components/split-panel/test/tabs/e2e.ts | 1 + .../split-panel/test/tabs/main.html | 8 + src/components/tabs/tab.ts | 2 +- src/components/tabs/tabs.ios.scss | 2 +- src/components/tabs/tabs.ts | 13 +- src/index.ts | 5 + src/navigation/nav-controller-base.ts | 14 +- src/navigation/nav-controller.ts | 5 + src/navigation/root-node.ts | 10 ++ src/themes/ionic.components.scss | 6 + 29 files changed, 837 insertions(+), 62 deletions(-) create mode 100644 src/components/split-panel/split-panel.ios.scss create mode 100644 src/components/split-panel/split-panel.md.scss create mode 100644 src/components/split-panel/split-panel.scss create mode 100644 src/components/split-panel/split-panel.ts create mode 100644 src/components/split-panel/split-panel.wp.scss create mode 100644 src/components/split-panel/test/basic/app.module.ts create mode 100644 src/components/split-panel/test/basic/e2e.ts create mode 100644 src/components/split-panel/test/basic/main.html create mode 100644 src/components/split-panel/test/nested/app.module.ts create mode 100644 src/components/split-panel/test/nested/e2e.ts create mode 100644 src/components/split-panel/test/nested/main.html create mode 100644 src/components/split-panel/test/tabs/app.module.ts create mode 100644 src/components/split-panel/test/tabs/e2e.ts create mode 100644 src/components/split-panel/test/tabs/main.html create mode 100644 src/navigation/root-node.ts diff --git a/src/animations/animation.ts b/src/animations/animation.ts index e6b0d175e50..3427ce92728 100644 --- a/src/animations/animation.ts +++ b/src/animations/animation.ts @@ -351,6 +351,17 @@ export class Animation { }); } + syncPlay() { + // If the animation was already invalidated (it did finish), do nothing + if (!this.plt) { + return; + } + this._isAsync = false; + this._hasDur = false; + this._clearAsync(); + this._playDomInspect({ duration: 0 }); + } + /** * @private * DOM WRITE diff --git a/src/components/menu/menu-controller.ts b/src/components/menu/menu-controller.ts index e84c9a6af7b..4fd015a500e 100644 --- a/src/components/menu/menu-controller.ts +++ b/src/components/menu/menu-controller.ts @@ -1,7 +1,7 @@ import { Menu } from './menu'; import { MenuType } from './menu-types'; import { Platform } from '../../platform/platform'; -import { removeArrayItem } from '../../util/util'; +import { removeArrayItem, assert } from '../../util/util'; /** @@ -307,6 +307,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 5b1569cf453..9c4266a500b 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 be991ada9c6..df522e44a5f 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 + const ani = this.ani .onFinish(done, 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 f91fa652d0c..d4fca68f74e 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'; @@ -13,6 +13,7 @@ import { MenuController } from './menu-controller'; import { MenuType } from './menu-types'; import { Platform } from '../../platform/platform'; import { UIEventManager } from '../../gestures/ui-event-manager'; +import { RootNode } from '../../navigation/root-node'; /** * @name Menu @@ -188,8 +189,9 @@ 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; @@ -201,6 +203,7 @@ export class Menu { private _init: boolean = false; private _events: UIEventManager; private _gestureBlocker: BlockerDelegate; + private _isPanel: boolean = false; /** * @private @@ -248,8 +251,8 @@ export class Menu { } set enabled(val: boolean) { - this._isEnabled = isTrueProperty(val); - this._setListeners(); + const isEnabled = isTrueProperty(val); + this.enable(isEnabled); } /** @@ -261,8 +264,8 @@ export class Menu { } set swipeEnabled(val: boolean) { - this._isSwipeEnabled = isTrueProperty(val); - this._setListeners(); + const isEnabled = isTrueProperty(val); + this.swipeEnable(isEnabled); } /** @@ -353,13 +356,12 @@ export class Menu { // auto-disable if another menu on the same side is already enabled this._isEnabled = false; } - this._setListeners(); - this._cntEle.classList.add('menu-content'); this._cntEle.classList.add('menu-content-' + this.type); // register this menu with the app's menu controller this._menuCtrl.register(this); + this._updateState(); } /** @@ -371,27 +373,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,7 +392,7 @@ 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); } @@ -425,13 +406,24 @@ export class Menu { }); } + _forceClosing() { + if (!this.isOpen) { + return; + } + + 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'); @@ -561,38 +554,77 @@ export class Menu { return this.setOpen(!this.isOpen); } + _canOpen(): boolean { + return this._isEnabled && !this._isPanel; + } + + _isSideContent(): boolean { + return true; + } + /** * @private */ - enable(shouldEnable: boolean): Menu { - this.enabled = shouldEnable; - if (!shouldEnable && this.isOpen) { + _updateState() { + const canOpen = this._canOpen(); + + // Close menu inmediately + if (!canOpen && this.isOpen) { // close if this menu is open, and should not be enabled - this.close(); + 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(); - 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); + } else if (gesture.isListening && (!canOpen || !this._isSwipeEnabled)) { + // should not listen, but is currently listening + console.debug('menu, gesture unlisten', this.side); + gesture.unlisten(); } - // TODO - // what happens if menu is disabled while swipping? + assert(!this._isAnimating, 'can not be animating'); + } + /** + * @private + */ + enable(shouldEnable: boolean): Menu { + this._isEnabled = shouldEnable; + this._updateState(); return this; } + /** + * @private + */ + _setIsPanel(isPanel: boolean) { + this._isPanel = isPanel; + this._updateState(); + + // Trigger resize() if menu becomes a split panel + if (isPanel && this.menuContent) { + this.menuContent.resize(); + } + } + /** * @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 +684,13 @@ export class Menu { this._renderer.setElementAttribute(this._elementRef.nativeElement, attributeName, value); } + /** + * @private + */ + getElementRef(): ElementRef { + return this._elementRef; + } + /** * @private */ diff --git a/src/components/nav/nav.ts b/src/components/nav/nav.ts index 6ccc896b5e3..92b841cf3c7 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'; @@ -10,6 +10,7 @@ import { Keyboard } from '../../platform/keyboard'; import { NavController } from '../../navigation/nav-controller'; import { NavControllerBase } from '../../navigation/nav-controller-base'; import { NavOptions } from '../../navigation/nav-util'; +import { RootNode } from '../../navigation/root-node'; import { Platform } from '../../platform/platform'; import { TransitionController } from '../../transitions/transition-controller'; import { ViewController } from '../../navigation/view-controller'; @@ -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,24 @@ export class Nav extends NavControllerBase implements AfterViewInit { /** * @private */ - destroy() { + ngOnDestroy() { this.destroy(); } + /** + * @private + */ + _setIsPanel(isPanel: boolean) { + if (isPanel) { + this.resize(); + } + } + + /** + * @private + */ + _isSideContent(): boolean { + return !this._elementRef.nativeElement.hasAttribute('main'); + } + } diff --git a/src/components/nav/overlay-portal.ts b/src/components/nav/overlay-portal.ts index 96ef72a9bae..ea1f119365f 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/split-panel/split-panel.ios.scss b/src/components/split-panel/split-panel.ios.scss new file mode 100644 index 00000000000..d5db3b04284 --- /dev/null +++ b/src/components/split-panel/split-panel.ios.scss @@ -0,0 +1,12 @@ + +@import "../../themes/ionic.globals.ios"; + +// Split Panel +// -------------------------------------------------- + +.split-panel-ios.split-panel-visible >.split-panel-side { + min-width: 270px; + max-width: 28%; + + border-right: $hairlines-width solid $list-ios-border-color; +} diff --git a/src/components/split-panel/split-panel.md.scss b/src/components/split-panel/split-panel.md.scss new file mode 100644 index 00000000000..91a9d04c1fb --- /dev/null +++ b/src/components/split-panel/split-panel.md.scss @@ -0,0 +1,13 @@ + +@import "../../themes/ionic.globals.md"; + +// Split Panel +// -------------------------------------------------- + +.split-panel-md.split-panel-visible >.split-panel-side { + min-width: 270px; + max-width: 28%; + + border-right: 1px solid $list-md-border-color; +} + diff --git a/src/components/split-panel/split-panel.scss b/src/components/split-panel/split-panel.scss new file mode 100644 index 00000000000..57f30bb821b --- /dev/null +++ b/src/components/split-panel/split-panel.scss @@ -0,0 +1,69 @@ + +@import "../../themes/ionic.globals"; + +// Split Panel +// -------------------------------------------------- + +ion-split-panel { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + display: flex; + + flex-wrap: nowrap; + + contain: strict; +} + +.split-panel-side:not(ion-menu) { + display: none; +} + +.split-panel-visible >.split-panel-side, +.split-panel-visible >.split-panel-main { + // scss-lint:disable ImportantRule + position: relative; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 0; + display: block; + + flex: 1; + + box-shadow: none !important; +} + +.split-panel-visible >ion-split-panel.split-panel-side, +.split-panel-visible >ion-split-panel.split-panel-main { + display: flex; +} + +.split-panel-visible >.split-panel-side { + flex: 1; + flex-shrink: 0; + + order: -1; +} + +.split-panel-visible >ion-menu { + >.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-panel/split-panel.ts b/src/components/split-panel/split-panel.ts new file mode 100644 index 00000000000..8f631cf2289 --- /dev/null +++ b/src/components/split-panel/split-panel.ts @@ -0,0 +1,160 @@ +import { ContentChildren, Component, ElementRef, EventEmitter, forwardRef, Input, QueryList, NgZone, Renderer } from '@angular/core'; +import { Ion } from '../ion'; +import { assert } from '../../util/util'; +import { Config } from '../../config/config'; +import { RootNode } from '../../navigation/root-node'; + +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: '' +}; + + +/** + * @name SplitPanel + */ +@Component({ + selector: 'ion-split-panel', + template: '', + providers: [{provide: RootNode, useExisting: forwardRef(() => SplitPanel) }] +}) +export class SplitPanel extends Ion implements RootNode { + + _rmListerner: any; + _visible: boolean = false; + _init: boolean = false; + _mediaQuery: string = QUERY['md']; + + sideContent: RootNode; + mainContent: RootNode; + + @ContentChildren(RootNode, {descendants: false}) + set _setChildren(query: QueryList) { + this.mainContent = null; + this.sideContent = null; + + if (query.length === 1) { + var node = query.first; + this.setPanelCSSClass(node.getElementRef(), false); + this.mainContent = node; + + } else if (query.length >= 2) { + query.forEach(child => { + if (child !== this) { + var isSide = child._isSideContent(); + if (isSide) { + this.sideContent = child; + } else { + this.mainContent = child; + } + this.setPanelCSSClass(child.getElementRef(), isSide); + } + }); + + if (!this.sideContent || !this.mainContent || this.mainContent === this.sideContent) { + console.error('split panel needs two valid roots'); + } + } + } + + @Input() + set when(query: string) { + const defaultQuery = QUERY[query]; + this._mediaQuery = (defaultQuery) + ? defaultQuery + : query; + + this._listen(); + } + get when(): string { + return this._mediaQuery; + } + + ionChange: EventEmitter = new EventEmitter(); + + constructor( + private _zone: NgZone, + config: Config, + elementRef: ElementRef, + renderer: Renderer, + ) { + super(config, elementRef, renderer, 'split-panel'); + } + + ngAfterViewInit() { + this._init = true; + this._listen(); + } + + _listen() { + if (!this._init) { + return; + } + // Unlisten + this._rmListerner && this._rmListerner(); + this._rmListerner = null; + + if (this._mediaQuery && this._mediaQuery.length > 0) { + // Listen + const callback = (query: MediaQueryList) => this.setVisible(query.matches); + const mediaList = window.matchMedia(this._mediaQuery); + mediaList.addListener(callback); + this.setVisible(mediaList.matches); + this._rmListerner = function () { + mediaList.removeListener(callback); + }; + } else { + this.setVisible(false); + } + } + + setVisible(visible: boolean) { + if (this._visible === visible) { + return; + } + this.setElementClass('split-panel-visible', visible); + + this.sideContent && this.sideContent._setIsPanel(visible); + this.mainContent && this.mainContent._setIsPanel(visible); + + this._visible = visible; + + 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); + } + + setPanelCSSClass(elementRef: ElementRef, isSide: boolean) { + let ele = elementRef.nativeElement; + this._renderer.setElementClass(ele, 'split-panel-side', isSide); + this._renderer.setElementClass(ele, 'split-panel-main', !isSide); + } + + ngOnDestroy() { + assert(this._rmListerner, 'at this point _rmListerner should be valid'); + + this._rmListerner && this._rmListerner(); + this._rmListerner = null; + } + + _setIsPanel(isPanel: boolean) { + // Conforms to RootNode abstract class + } + + _isSideContent(): boolean { + return false; + } + +} diff --git a/src/components/split-panel/split-panel.wp.scss b/src/components/split-panel/split-panel.wp.scss new file mode 100644 index 00000000000..7898c27d235 --- /dev/null +++ b/src/components/split-panel/split-panel.wp.scss @@ -0,0 +1,12 @@ + +@import "../../themes/ionic.globals.wp"; + +// Split Panel +// -------------------------------------------------- + +.split-panel-wp.split-panel-visible >.split-panel-side { + min-width: 270px; + max-width: 28%; + + border-right: 1px solid $list-wp-border-color; +} diff --git a/src/components/split-panel/test/basic/app.module.ts b/src/components/split-panel/test/basic/app.module.ts new file mode 100644 index 00000000000..1d282242ba9 --- /dev/null +++ b/src/components/split-panel/test/basic/app.module.ts @@ -0,0 +1,96 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, NavController, MenuController } from '../../../../../ionic-angular'; + + +@Component({ + template: ` + + Navigation + + + + Hola + Hola + Hola + + Hola + Hola + Hola + + + + ` +}) +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); + } + + menu() { + this.menuCtrl.open(); + } +} + + +@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-panel/test/basic/e2e.ts b/src/components/split-panel/test/basic/e2e.ts new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/src/components/split-panel/test/basic/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-panel/test/basic/main.html b/src/components/split-panel/test/basic/main.html new file mode 100644 index 00000000000..74c02429b38 --- /dev/null +++ b/src/components/split-panel/test/basic/main.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/split-panel/test/nested/app.module.ts b/src/components/split-panel/test/nested/app.module.ts new file mode 100644 index 00000000000..ec41200c8ab --- /dev/null +++ b/src/components/split-panel/test/nested/app.module.ts @@ -0,0 +1,157 @@ +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-panel/test/nested/e2e.ts b/src/components/split-panel/test/nested/e2e.ts new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/src/components/split-panel/test/nested/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-panel/test/nested/main.html b/src/components/split-panel/test/nested/main.html new file mode 100644 index 00000000000..e12db1d9de4 --- /dev/null +++ b/src/components/split-panel/test/nested/main.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/components/split-panel/test/tabs/app.module.ts b/src/components/split-panel/test/tabs/app.module.ts new file mode 100644 index 00000000000..e62f0967e1c --- /dev/null +++ b/src/components/split-panel/test/tabs/app.module.ts @@ -0,0 +1,91 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, NavController, MenuController } from '../../../../../ionic-angular'; + + +@Component({ + template: ` + + Navigation + + + + Hola + Hola + Hola + + Hola + Hola + Hola + + + + ` +}) +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-panel/test/tabs/e2e.ts b/src/components/split-panel/test/tabs/e2e.ts new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/src/components/split-panel/test/tabs/e2e.ts @@ -0,0 +1 @@ + diff --git a/src/components/split-panel/test/tabs/main.html b/src/components/split-panel/test/tabs/main.html new file mode 100644 index 00000000000..b622e415ace --- /dev/null +++ b/src/components/split-panel/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 efdd9fc995e..c719523ba7f 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -385,7 +385,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 b44d90e0332..ac71ff5f97a 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 97bebd24ab1..21ce8609cbd 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 } 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 '../../navigation/root-node'; 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 */ @@ -542,6 +544,13 @@ export class Tabs extends Ion implements AfterViewInit { } } + _setIsPanel(isPanel: boolean): void { + // Conforms to RootNode abstract class + } + + _isSideContent(): boolean { + return !this._elementRef.nativeElement.hasAttribute('main'); + } } let tabIds = -1; diff --git a/src/index.ts b/src/index.ts index 7bc83de2553..3bcdadd9a6e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -104,6 +104,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 { SplitPanel } from './components/split-panel/split-panel'; import { Tab } from './components/tabs/tab'; import { Tabs } from './components/tabs/tabs'; import { TabButton } from './components/tabs/tab-button'; @@ -187,6 +188,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 { SplitPanel } from './components/split-panel/split-panel'; export { Tab } from './components/tabs/tab'; export { TabButton } from './components/tabs/tab-button'; export { TabHighlight } from './components/tabs/tab-highlight'; @@ -215,6 +217,7 @@ export { NavController } from './navigation/nav-controller'; export { NavControllerBase } from './navigation/nav-controller-base'; export { NavParams } from './navigation/nav-params'; export { NavLink, NavOptions, DeepLink, DeepLinkConfig, DeepLinkMetadata, DeepLinkMetadataType } from './navigation/nav-util'; +export { RootNode } from './navigation/root-node'; export { UrlSerializer, DeepLinkConfigToken } from './navigation/url-serializer'; export { ViewController } from './navigation/view-controller'; export { ActionSheetCmp } from './components/action-sheet/action-sheet-component'; @@ -368,6 +371,7 @@ export { Transition } from './transitions/transition'; Slide, Slides, Spinner, + SplitPanel, Tab, Tabs, TabButton, @@ -462,6 +466,7 @@ export { Transition } from './transitions/transition'; Slide, Slides, Spinner, + SplitPanel, Tab, Tabs, TabButton, diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index 72f1e7f07c6..7cea9aa71f6 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -789,7 +789,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; @@ -1011,7 +1011,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 { @@ -1056,6 +1057,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 6d0441c8c0b..0fe7808ff81 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/navigation/root-node.ts b/src/navigation/root-node.ts new file mode 100644 index 00000000000..68121b3d578 --- /dev/null +++ b/src/navigation/root-node.ts @@ -0,0 +1,10 @@ +import { ElementRef } from '@angular/core'; + +/** + * @private + */ +export abstract class RootNode { + abstract getElementRef(): ElementRef; + abstract _setIsPanel(isPanel: boolean): void; + abstract _isSideContent(): boolean; +} diff --git a/src/themes/ionic.components.scss b/src/themes/ionic.components.scss index 47837c27cc7..c1bf0251c38 100644 --- a/src/themes/ionic.components.scss +++ b/src/themes/ionic.components.scss @@ -189,6 +189,12 @@ @import "../components/slides/slides"; +@import +"../components/split-panel/split-panel", +"../components/split-panel/split-panel.ios", +"../components/split-panel/split-panel.md", +"../components/split-panel/split-panel.wp"; + @import "../components/spinner/spinner", "../components/spinner/spinner.ios",