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
This commit is contained in:
Manu MA
2017-03-03 20:13:07 +01:00
committed by GitHub
parent 66fcaa7a63
commit 9e4c3a6e3e
36 changed files with 1121 additions and 100 deletions

View File

@@ -85,8 +85,9 @@ export class App {
runInDev(() => {
// During developement, navPop can be triggered by calling
if (!(<any>_plt.win())['HWBackButton']) {
(<any>_plt.win())['HWBackButton'] = () => {
const win = <any>_plt.win();
if (!win['HWBackButton']) {
win['HWBackButton'] = () => {
let p = this.goBack();
p && p.catch(() => console.debug('hardware go back cancelled'));
return p;

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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<boolean> {
// 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
*/

View File

@@ -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');
}
}

View File

@@ -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';
'<div #viewport nav-viewport></div>' +
'<div class="nav-decor"></div>',
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();
}
}
}

View File

@@ -49,5 +49,9 @@ export class OverlayPortal extends NavControllerBase {
this._zIndexOffset = (val || 0);
}
ngOnDestroy() {
this.destroy();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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<RootNode>) {
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<SplitPane> = new EventEmitter<SplitPane>();
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;
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,120 @@
import { Component, NgModule } from '@angular/core';
import { IonicApp, IonicModule, NavController, MenuController, SplitPane } from '../../../../../ionic-angular';
@Component({
template: `
<ion-header>
<ion-navbar><ion-title>Navigation</ion-title></ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>Hola 1</ion-item>
<ion-item>Hola 2</ion-item>
<ion-item>Hola 3</ion-item>
<button ion-item (click)="push()">Push</button>
<ion-item>Hola</ion-item>
<ion-item>Hola</ion-item>
<ion-item>Hola</ion-item>
</ion-list>
</ion-content>
`
})
export class SidePage {
constructor(public navCtrl: NavController) { }
push() {
this.navCtrl.push(SidePage);
}
}
@Component({
template: `
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Page 2</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h1>Page 2</h1>
</ion-content>
`
})
export class E2EPage2 {}
@Component({
template: `
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Navigation</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h1>Page 1</h1>
<button ion-button (click)="push()">Push</button>
<button ion-button (click)="menu()">Disable/enable menu</button>
<div f></div>
<div f></div>
<div f></div>
<div f></div>
</ion-content>
`
})
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 {}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,7 @@
<ion-split-pane (ionChange)="splitPaneChanged($event)">
<ion-menu [content]="content">
<ion-nav [root]="root2"></ion-nav>
</ion-menu>
<ion-nav [root]="root" main #content></ion-nav>
</ion-split-pane>

View File

@@ -0,0 +1,102 @@
import { Component, NgModule } from '@angular/core';
import { IonicApp, IonicModule, NavController, MenuController, SplitPane } from '../../../../../ionic-angular';
@Component({
template: `
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Page 2</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h1>Page 2</h1>
</ion-content>
`
})
export class E2EPage2 {}
@Component({
template: `
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Navigation</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<h1>Page 1</h1>
<button ion-button (click)="push()">Push</button>
<button ion-button (click)="menu1Active()">Enable menu 1</button>
<button ion-button (click)="menu2Active()">Enable menu 2</button>
<button ion-button (click)="menu3Active()">Enable menu 3</button>
<button ion-button (click)="disableAll()">Disable all</button>
<div f></div>
<div f></div>
<div f></div>
<div f></div>
</ion-content>
`
})
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 {}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,52 @@
<ion-split-pane (ionChange)="splitPaneChanged($event)">
<ion-menu [content]="content" id="menu1">
<ion-header>
<ion-toolbar color="secondary">
<ion-title>Menu 1</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>Example</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-menu [content]="content" id="menu2">
<ion-header>
<ion-toolbar color="danger">
<ion-title>Menu 2</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>Example</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-menu [content]="content" id="menu3">
<ion-header>
<ion-toolbar color="primary">
<ion-title>Menu 3</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>Example</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav [root]="root" main #content></ion-nav>
</ion-split-pane>

View File

@@ -0,0 +1,162 @@
import { Component, NgModule } from '@angular/core';
import { IonicApp, IonicModule, NavController } from '../../../../../ionic-angular';
@Component({
template: `
<ion-header>
<ion-navbar><ion-title>Nested 1</ion-title></ion-navbar>
</ion-header>
<ion-content padding>
<button ion-button (click)="push()">Push</button>
<div f></div>
<div f></div>
<div f></div>
<div f></div>
</ion-content>
`
})
export class E2ENested {
constructor(
public navCtrl: NavController,
) { }
push() {
this.navCtrl.push(E2ENested);
}
}
@Component({
template: `
<ion-header>
<ion-navbar><ion-title>Nested 2</ion-title></ion-navbar>
</ion-header>
<ion-content padding>
<button ion-button (click)="push()">Push</button>
<div f></div>
<div f></div>
<div f></div>
<div f></div>
</ion-content>
`
})
export class E2ENested2 {
constructor(
public navCtrl: NavController,
) { }
push() {
this.navCtrl.push(E2ENested2);
}
}
@Component({
template: `
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Nested 3</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<button ion-button (click)="push()">Push</button>
<div f></div>
<div f></div>
<div f></div>
<div f></div>
</ion-content>
`
})
export class E2ENested3 {
constructor(
public navCtrl: NavController,
) { }
push() {
this.navCtrl.push(E2ENested3);
}
}
@Component({
template: `
<ion-header>
<ion-navbar><ion-title>Navigation</ion-title></ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>Hola</ion-item>
<ion-item>Hola</ion-item>
<ion-item>Hola</ion-item>
<button ion-item (click)="push()">Push</button>
<ion-item>Hola</ion-item>
<ion-item>Hola</ion-item>
<ion-item>Hola</ion-item>
</ion-list>
</ion-content>
`
})
export class SidePage {
constructor(public navCtrl: NavController) { }
push() {
this.navCtrl.push(SidePage);
}
}
@Component({
template: `
<ion-split-pane>
<ion-nav [root]="root"></ion-nav>
<ion-split-pane when="lg" main >
<ion-nav [root]="root2"></ion-nav>
<ion-nav [root]="root3" main ></ion-nav>
</ion-split-pane>
</ion-split-pane>
`
})
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 {}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,10 @@
<ion-split-pane when="sm">
<ion-menu [content]="content">
<ion-nav [root]="root2"></ion-nav>
</ion-menu>
<ion-nav [root]="root" main #content></ion-nav>
</ion-split-pane>

View File

@@ -0,0 +1,98 @@
import { Component, NgModule } from '@angular/core';
import { IonicApp, IonicModule, NavController, MenuController } from '../../../../../ionic-angular';
@Component({
template: `
<ion-header>
<ion-navbar><ion-title>Navigation</ion-title></ion-navbar>
</ion-header>
<ion-content>
<ion-slides style="background: black"
pager="true"
effect="flip">
<ion-slide style="background: red; color: white;">
<h1>Slide 1</h1>
</ion-slide>
<ion-slide style="background: white; color: blue;">
<h1>Slide 2</h1>
</ion-slide>
<ion-slide style="background: blue; color: white;">
<h1>Slide 3</h1>
</ion-slide>
</ion-slides>
</ion-content>
`
})
export class SidePage {
constructor(public navCtrl: NavController) { }
push() {
this.navCtrl.push(SidePage);
}
}
@Component({
template: `
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Navigation</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<button ion-button (click)="push()">Push</button>
<div f></div>
<div f></div>
<div f></div>
<div f></div>
</ion-content>
`
})
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 {}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,8 @@
<ion-split-pane>
<ion-nav [root]="root2" #content main></ion-nav>
<ion-tabs>
<ion-tab [root]="root" tabTitle="Page1"></ion-tab>
<ion-tab [root]="root" tabTitle="Page2"></ion-tab>
</ion-tabs>
</ion-split-pane>

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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';
'<ng-content></ng-content>' +
'<div #portal tab-portal></div>',
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;