fix(nav): flickering

This commit is contained in:
Manu Mtz.-Almeida
2018-02-14 18:40:56 +01:00
parent 77cf37127e
commit 2fb9e4499f
14 changed files with 249 additions and 215 deletions

View File

@ -3059,6 +3059,7 @@ declare global {
} }
namespace JSXElements { namespace JSXElements {
export interface IonTabAttributes extends HTMLAttributes { export interface IonTabAttributes extends HTMLAttributes {
active?: boolean;
badge?: string; badge?: string;
badgeStyle?: string; badgeStyle?: string;
btnId?: string; btnId?: string;

View File

@ -22,6 +22,7 @@ export interface Animation {
beforeClearStyles(propertyNames: string[]): Animation; beforeClearStyles(propertyNames: string[]): Animation;
beforeAddRead(domReadFn: Function): Animation; beforeAddRead(domReadFn: Function): Animation;
beforeAddWrite(domWriteFn: Function): Animation; beforeAddWrite(domWriteFn: Function): Animation;
duringAddClass(className: string): Animation;
afterAddClass(className: string): Animation; afterAddClass(className: string): Animation;
afterRemoveClass(className: string): Animation; afterRemoveClass(className: string): Animation;
afterStyles(styles: { [property: string]: any; }): Animation; afterStyles(styles: { [property: string]: any; }): Animation;

View File

@ -231,6 +231,15 @@ export class Animator {
return this; return this;
} }
/**
* Sets a CSS class during the duration of the animation.
*/
duringAddClass(className: string): Animator {
this.beforeAddClass(className);
this.afterRemoveClass(className);
return this;
}
/** /**
* Set CSS inline styles to this animation's elements * Set CSS inline styles to this animation's elements
* before the animation begins. * before the animation begins.

View File

@ -8,6 +8,3 @@ $content-ios-font-family: $font-family-ios-base !default;
/// @prop - Background color of the outer content /// @prop - Background color of the outer content
$content-ios-outer-background: $background-ios-color-step-50 !default; $content-ios-outer-background: $background-ios-color-step-50 !default;
/// @prop - Background color of the content when making transition
$content-ios-transition-background: #000 !default;

View File

@ -68,7 +68,7 @@ export function canNavGoBack(nav: Nav, view?: ViewController) {
if (!nav) { if (!nav) {
return false; return false;
} }
return nav.getPrevious(view); return !!nav.getPrevious(view);
} }
export function transitionFactory(animation: Animation): Transition { export function transitionFactory(animation: Animation): Transition {
@ -127,20 +127,28 @@ export function destroyTransition(transitionId: number) {
} }
export function getHydratedTransition(name: string, config: Config, transitionId: number, emptyTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions, defaultTransitionFactory: TransitionBuilder): Promise<Transition> { export function getHydratedTransition(name: string, config: Config, transitionId: number, emptyTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions, defaultTransitionFactory: TransitionBuilder): Promise<Transition> {
// Let makes sure everything is hydrated and ready to animate
const componentReadyPromise: Promise<any>[] = [];
if (enteringView && (enteringView.element as any).componentOnReady) {
componentReadyPromise.push((enteringView.element as any).componentOnReady());
}
if (leavingView && (leavingView.element as any).componentOnReady) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}
const transitionFactory = config.get(name) as TransitionBuilder || defaultTransitionFactory; const transitionFactory = config.get(name) as TransitionBuilder || defaultTransitionFactory;
return Promise.all(componentReadyPromise)
return transitionFactory(emptyTransition, enteringView, leavingView, opts).then((hydratedTransition) => { .then(() => transitionFactory(emptyTransition, enteringView, leavingView, opts))
hydratedTransition.transitionId = transitionId; .then((hydratedTransition) => {
if (!activeTransitions.has(transitionId)) { hydratedTransition.transitionId = transitionId;
// sweet, this is the root transition if (!activeTransitions.has(transitionId)) {
activeTransitions.set(transitionId, hydratedTransition); // sweet, this is the root transition
} else { activeTransitions.set(transitionId, hydratedTransition);
// we've got a parent transition going } else {
// just append this transition to the existing one // we've got a parent transition going
activeTransitions.get(transitionId).add(hydratedTransition); // just append this transition to the existing one
} activeTransitions.get(transitionId).add(hydratedTransition);
return hydratedTransition; }
return hydratedTransition;
}); });
} }

View File

@ -1,7 +1,6 @@
// Util
// --------------------------------------------------
@import "../../themes/util"; @import "../../themes/util";
$navigation-ios-transition-background: #000 !default;
ion-nav { ion-nav {
@include position(0); @include position(0);
@ -13,10 +12,7 @@ ion-nav {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #000;
contain: layout size style; contain: layout size style;
} }
.ion-page { .ion-page {
@ -31,3 +27,24 @@ ion-nav {
contain: layout size style; contain: layout size style;
} }
.nav-decor {
display: none;
}
.show-decor > .nav-decor {
@include position(0, null, null, 0);
// when ios pages transition, the leaving page grays out
// this is the black square behind all pages so they gray out
position: absolute;
z-index: 0;
display: block;
width: 100%;
height: 100%;
background: $navigation-ios-transition-background;
pointer-events: none;
}

View File

@ -335,6 +335,9 @@ export class Nav implements PublicNav, NavOutlet {
attachTo='body' attachTo='body'
></ion-gesture>); ></ion-gesture>);
} }
if (this.mode === 'ios') {
dom.push(<div class='nav-decor'/>);
}
dom.push(<slot></slot>); dom.push(<slot></slot>);
return dom; return dom;
} }
@ -820,7 +823,8 @@ export function loadViewAndTransition(nav: Nav, enteringView: ViewController, le
const emptyTransition = transitionFactory(ti.animation); const emptyTransition = transitionFactory(ti.animation);
return getHydratedTransition(animationOpts.animation, nav.config, nav.transitionId, emptyTransition, enteringView, leavingView, animationOpts, getDefaultTransition(nav.config)).then((transition) => { return getHydratedTransition(animationOpts.animation, nav.config, nav.transitionId, emptyTransition, enteringView, leavingView, animationOpts, getDefaultTransition(nav.config))
.then((transition) => {
if (nav.sbTrns) { if (nav.sbTrns) {
nav.sbTrns.destroy(); nav.sbTrns.destroy();
@ -967,6 +971,7 @@ export function fireViewWillLifecycles(enteringView: ViewController, leavingView
export function attachViewToDom(nav: Nav, enteringView: ViewController, ti: TransitionInstruction) { export function attachViewToDom(nav: Nav, enteringView: ViewController, ti: TransitionInstruction) {
if (enteringView && enteringView.state === STATE_NEW) { if (enteringView && enteringView.state === STATE_NEW) {
return ti.delegate.attachViewToDom(nav.element, enteringView.component, enteringView.data, [], ti.escapeHatch).then((mountingData) => { return ti.delegate.attachViewToDom(nav.element, enteringView.component, enteringView.data, [], ti.escapeHatch).then((mountingData) => {
mountingData.element.classList.add('nav-page');
ti.mountingData = mountingData; ti.mountingData = mountingData;
Object.assign(enteringView, mountingData); Object.assign(enteringView, mountingData);
enteringView.state = STATE_ATTACHED; enteringView.state = STATE_ATTACHED;

View File

@ -12,197 +12,193 @@ const OFF_OPACITY = 0.8;
const SHOW_BACK_BTN_CSS = 'show-back-button'; const SHOW_BACK_BTN_CSS = 'show-back-button';
export function buildIOSTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> { export function buildIOSTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
const componentReadyPromise: Promise<any>[] = []; // Cool we're all hydrated, and can do deep selector
// Let makes sure everything is hydrated and ready to animate rootTransition.enteringView = enteringView;
if (enteringView && (enteringView.element as any).componentOnReady) { rootTransition.leavingView = leavingView;
componentReadyPromise.push((enteringView.element as any).componentOnReady());
} const isRTL = document.dir === 'rtl';
if (leavingView && (leavingView.element as any).componentOnReady) { const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
componentReadyPromise.push((leavingView.element as any).componentOnReady()); const OFF_LEFT = isRTL ? '33%' : '-33%';
rootTransition.duration(isDef(opts.duration) ? opts.duration : DURATION);
rootTransition.easing(isDef(opts.easing) ? opts.easing : EASING);
rootTransition.addElement(enteringView.element);
rootTransition.beforeRemoveClass('hide-page');
if (leavingView) {
const navEl = leavingView.element.closest('ion-nav');
if (navEl) {
const navDecor = rootTransition.create();
navDecor.addElement(navEl).duringAddClass('show-decor');
rootTransition.add(navDecor);
}
} }
return Promise.all(componentReadyPromise).then(() => { const backDirection = (opts.direction === 'back');
// Cool we're all hydrated, and can do deep selector // setting up enter view
rootTransition.enteringView = enteringView; if (enteringView) {
rootTransition.leavingView = leavingView;
const isRTL = document.dir === 'rtl'; const enteringContent = rootTransition.create();
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%'; enteringContent.addElement(enteringView.element.querySelector('ion-content'));
const OFF_LEFT = isRTL ? '33%' : '-33%'; enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
rootTransition.add(enteringContent);
rootTransition.duration(isDef(opts.duration) ? opts.duration : DURATION); if (backDirection) {
rootTransition.easing(isDef(opts.easing) ? opts.easing : EASING); enteringContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true)
.fromTo(OPACITY, OFF_OPACITY, 1, true);
} else {
// entering content, forward direction
enteringContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
}
rootTransition.addElement(enteringView.element); const enteringToolBarEle = enteringView.element.querySelector('ion-toolbar');
rootTransition.beforeRemoveClass('hide-page'); if (enteringToolBarEle) {
const enteringToolBar = rootTransition.create();
enteringToolBar.addElement(enteringToolBarEle);
rootTransition.add(enteringToolBar);
const backDirection = (opts.direction === 'back'); const enteringTitle = rootTransition.create();
enteringTitle.addElement(enteringToolBarEle.querySelector('ion-title'));
// setting up enter view const enteringToolBarItems = rootTransition.create();
if (enteringView) { enteringToolBarItems.addElement(enteringToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const enteringContent = rootTransition.create(); const enteringToolBarBg = rootTransition.create();
enteringContent.addElement(enteringView.element.querySelector('ion-content')); enteringToolBarBg.addElement(enteringToolBarEle.querySelector('.toolbar-background'));
enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
rootTransition.add(enteringContent); const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringToolBarEle.querySelector('.back-button'));
enteringToolBar
.add(enteringTitle)
.add(enteringToolBarItems)
.add(enteringToolBarBg)
.add(enteringBackButton);
enteringTitle.fromTo(OPACITY, 0.01, 1, true);
enteringToolBarItems.fromTo(OPACITY, 0.01, 1, true);
if (backDirection) { if (backDirection) {
enteringContent enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true) if (canNavGoBack(enteringView.nav, enteringView)) {
.fromTo(OPACITY, OFF_OPACITY, 1, true); // back direction, entering page has a back button
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
}
} else { } else {
// entering content, forward direction // entering toolbar, forward direction
enteringContent enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
enteringToolBarBg
.beforeClearStyles([OPACITY]) .beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); .fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
} if (canNavGoBack(enteringView.nav, enteringView)) {
const enteringToolBarEle = enteringView.element.querySelector('ion-toolbar'); // forward direction, entering page has a back button
if (enteringToolBarEle) { enteringBackButton
const enteringToolBar = rootTransition.create(); .beforeAddClass(SHOW_BACK_BTN_CSS)
enteringToolBar.addElement(enteringToolBarEle); .fromTo(OPACITY, 0.01, 1, true);
rootTransition.add(enteringToolBar);
const enteringTitle = rootTransition.create();
enteringTitle.addElement(enteringToolBarEle.querySelector('ion-title'));
const enteringToolBarItems = rootTransition.create(); const enteringBackBtnText = rootTransition.create();
enteringToolBarItems.addElement(enteringToolBarEle.querySelectorAll('ion-buttons,[menuToggle]')); enteringBackBtnText.addElement(enteringToolBarEle.querySelector('.back-button .button-text'));
const enteringToolBarBg = rootTransition.create(); enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px');
enteringToolBarBg.addElement(enteringToolBarEle.querySelector('.toolbar-background')); enteringToolBar.add(enteringBackBtnText);
const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringToolBarEle.querySelector('.back-button'));
enteringToolBar
.add(enteringTitle)
.add(enteringToolBarItems)
.add(enteringToolBarBg)
.add(enteringBackButton);
enteringTitle.fromTo(OPACITY, 0.01, 1, true);
enteringToolBarItems.fromTo(OPACITY, 0.01, 1, true);
if (backDirection) {
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
if (canNavGoBack(enteringView.nav, enteringView)) {
// back direction, entering page has a back button
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
}
} else { } else {
// entering toolbar, forward direction enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
enteringToolBarBg
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
if (canNavGoBack(enteringView.nav, enteringView)) {
// forward direction, entering page has a back button
enteringBackButton
.beforeAddClass(SHOW_BACK_BTN_CSS)
.fromTo(OPACITY, 0.01, 1, true);
const enteringBackBtnText = rootTransition.create();
enteringBackBtnText.addElement(enteringToolBarEle.querySelector('.back-button .button-text'));
enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px');
enteringToolBar.add(enteringBackBtnText);
} else {
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
}
} }
} }
} }
}
// setup leaving view // setup leaving view
if (leavingView) { if (leavingView) {
const leavingContent = rootTransition.create(); const leavingContent = rootTransition.create();
leavingContent.addElement(leavingView.element.querySelector('ion-content')); leavingContent.addElement(leavingView.element.querySelector('ion-content'));
leavingContent.addElement(leavingView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *')); leavingContent.addElement(leavingView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
rootTransition.add(leavingContent); rootTransition.add(leavingContent);
if (backDirection) {
// leaving content, back direction
leavingContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
} else {
// leaving content, forward direction
leavingContent
.fromTo(TRANSLATEX, CENTER, OFF_LEFT, true)
.fromTo(OPACITY, 1, OFF_OPACITY, true);
}
const leavingToolBarEle = leavingView.element.querySelector('ion-toolbar');
if (leavingToolBarEle) {
const leavingToolBar = rootTransition.create();
leavingToolBar.addElement(leavingToolBarEle);
const leavingTitle = rootTransition.create();
leavingTitle.addElement(leavingToolBarEle.querySelector('ion-title'));
const leavingToolBarItems = rootTransition.create();
leavingToolBarItems.addElement(leavingToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const leavingToolBarBg = rootTransition.create();
leavingToolBarBg.addElement(leavingToolBarEle.querySelector('.toolbar-background'));
const leavingBackButton = rootTransition.create();
leavingBackButton.addElement(leavingToolBarEle.querySelector('.back-button'));
leavingToolBar
.add(leavingTitle)
.add(leavingToolBarItems)
.add(leavingBackButton)
.add(leavingToolBarBg);
rootTransition.add(leavingToolBar);
// fade out leaving toolbar items
leavingBackButton.fromTo(OPACITY, 0.99, 0, true);
leavingTitle.fromTo(OPACITY, 0.99, 0, true);
leavingToolBarItems.fromTo(OPACITY, 0.99, 0, true);
if (backDirection) { if (backDirection) {
// leaving content, back direction // leaving toolbar, back direction
leavingContent leavingTitle.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
// leaving toolbar, back direction, and there's no entering toolbar
// should just slide out, no fading out
leavingToolBarBg
.beforeClearStyles([OPACITY]) .beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%')); .fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
const leavingBackBtnText = rootTransition.create();
leavingBackBtnText.addElement(leavingToolBarEle.querySelector('.back-button .button-text'));
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -300 : 300) + 'px');
leavingToolBar.add(leavingBackBtnText);
} else { } else {
// leaving content, forward direction // leaving toolbar, forward direction
leavingContent leavingTitle
.fromTo(TRANSLATEX, CENTER, OFF_LEFT, true) .fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.fromTo(OPACITY, 1, OFF_OPACITY, true); .afterClearStyles([TRANSFORM]);
}
const leavingToolBarEle = leavingView.element.querySelector('ion-toolbar'); leavingBackButton.afterClearStyles([OPACITY]);
if (leavingToolBarEle) { leavingTitle.afterClearStyles([OPACITY]);
const leavingToolBar = rootTransition.create(); leavingToolBarItems.afterClearStyles([OPACITY]);
leavingToolBar.addElement(leavingToolBarEle);
const leavingTitle = rootTransition.create();
leavingTitle.addElement(leavingToolBarEle.querySelector('ion-title'));
const leavingToolBarItems = rootTransition.create();
leavingToolBarItems.addElement(leavingToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const leavingToolBarBg = rootTransition.create();
leavingToolBarBg.addElement(leavingToolBarEle.querySelector('.toolbar-background'));
const leavingBackButton = rootTransition.create();
leavingBackButton.addElement(leavingToolBarEle.querySelector('.back-button'));
leavingToolBar
.add(leavingTitle)
.add(leavingToolBarItems)
.add(leavingBackButton)
.add(leavingToolBarBg);
rootTransition.add(leavingToolBar);
// fade out leaving toolbar items
leavingBackButton.fromTo(OPACITY, 0.99, 0, true);
leavingTitle.fromTo(OPACITY, 0.99, 0, true);
leavingToolBarItems.fromTo(OPACITY, 0.99, 0, true);
if (backDirection) {
// leaving toolbar, back direction
leavingTitle.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
// leaving toolbar, back direction, and there's no entering toolbar
// should just slide out, no fading out
leavingToolBarBg
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
const leavingBackBtnText = rootTransition.create();
leavingBackBtnText.addElement(leavingToolBarEle.querySelector('.back-button .button-text'));
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -300 : 300) + 'px');
leavingToolBar.add(leavingBackBtnText);
} else {
// leaving toolbar, forward direction
leavingTitle
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.afterClearStyles([TRANSFORM]);
leavingBackButton.afterClearStyles([OPACITY]);
leavingTitle.afterClearStyles([OPACITY]);
leavingToolBarItems.afterClearStyles([OPACITY]);
}
} }
} }
}
// Return the rootTransition promise // Return the rootTransition promise
return rootTransition; return Promise.resolve(rootTransition);
});
} }

View File

@ -9,15 +9,6 @@ const SHOW_BACK_BTN_CSS = 'show-back-button';
export function buildMdTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> { export function buildMdTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Promise<Transition> {
const componentReadyPromise: Promise<any>[] = [];
if (enteringView && (enteringView.element as any).componentOnReady) {
componentReadyPromise.push((enteringView.element as any).componentOnReady());
}
if (leavingView && (leavingView.element as any).componentOnReady) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}
return Promise.all(componentReadyPromise).then(() => {
rootTransition.enteringView = enteringView; rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView; rootTransition.leavingView = leavingView;
@ -68,10 +59,7 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
rootTransition.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0)); rootTransition.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0));
} }
return rootTransition; return Promise.resolve(rootTransition);
});
} }
function getIonPageElement(element: HTMLElement) { function getIonPageElement(element: HTMLElement) {

View File

@ -32,7 +32,7 @@ export class Router {
} }
@Listen('window:popstate') @Listen('window:popstate')
protected onURLHashChanged() { protected onPopState() {
if (window.history.state === null) { if (window.history.state === null) {
this.state++; this.state++;
window.history.replaceState(this.state, document.title, document.location.href); window.history.replaceState(this.state, document.title, document.location.href);

View File

@ -50,6 +50,11 @@ export class Tabs {
## Properties ## Properties
#### active
boolean
#### badge #### badge
string string
@ -131,6 +136,11 @@ The title of the tab button.
## Attributes ## Attributes
#### active
boolean
#### badge #### badge
string string
@ -222,7 +232,7 @@ Emitted when the current tab is selected.
#### getRouteId() #### getRouteId()
#### setActive() #### prepareActive()

View File

@ -12,7 +12,7 @@ export class Tab {
@Element() el: HTMLElement; @Element() el: HTMLElement;
@State() init = false; @State() init = false;
@State() active = false; @Prop() active = false;
/** /**
* Set the root page for this tab. * Set the root page for this tab.
@ -79,11 +79,7 @@ export class Tab {
@Event() ionSelect: EventEmitter<void>; @Event() ionSelect: EventEmitter<void>;
@Method() @Method()
setActive(active: boolean): Promise<any> { prepareActive(): Promise<any> {
this.active = active;
if (!active) {
return Promise.resolve();
}
if (this.loaded) { if (this.loaded) {
return this.configChildgNav(); return this.configChildgNav();
} }
@ -140,13 +136,13 @@ export class Tab {
} }
hostData() { hostData() {
const visible = this.active && this.selected; const hidden = !this.active || !this.selected;
return { return {
'aria-hidden': !visible, 'aria-hidden': hidden,
'aria-labelledby': this.btnId, 'aria-labelledby': this.btnId,
'role': 'tabpanel', 'role': 'tabpanel',
class: { class: {
'show-tab': visible 'show-tab': this.active
} }
}; };
} }

View File

@ -1,7 +1,7 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
import { Config, NavEventDetail, NavOutlet } from '../../index'; import { Config, NavEventDetail, NavOutlet } from '../../index';
import { ensureExternalRounterController } from '../../utils/helpers'; import { asyncRaf, ensureExternalRounterController } from '../../utils/helpers';
@Component({ @Component({
@ -121,15 +121,17 @@ export class Tabs implements NavOutlet {
const leavingTab = this.selectedTab; const leavingTab = this.selectedTab;
this.selectedTab = selectedTab; this.selectedTab = selectedTab;
let promise = selectedTab.setActive(true);
if (leavingTab && leavingTab !== selectedTab) {
promise = promise.then(() => leavingTab.setActive(false));
}
return promise.then(() => { return selectedTab.prepareActive()
this.ionChange.emit(selectedTab); .then(() => selectedTab.active = true)
this.ionNavChanged.emit({isPop: false}); .then(() => asyncRaf())
}); .then(() => {
if (leavingTab) {
leavingTab.active = false;
}
this.ionChange.emit(selectedTab);
this.ionNavChanged.emit({isPop: false});
});
} }
/** /**
@ -209,7 +211,7 @@ export class Tabs implements NavOutlet {
tab.selected = false; tab.selected = false;
} }
} }
const promise = selectedTab ? selectedTab.setActive(true) : Promise.resolve(); const promise = selectedTab ? selectedTab.prepareActive() : Promise.resolve();
return promise.then(() => { return promise.then(() => {
this.selectedTab = selectedTab; this.selectedTab = selectedTab;
if (selectedTab) { if (selectedTab) {

View File

@ -295,6 +295,10 @@ export function debounce(func: Function, wait = 0) {
}; };
} }
export function asyncRaf(): Promise<number> {
return new Promise(resolve => requestAnimationFrame(resolve));
}
export function getNavAsChildIfExists(element: HTMLElement): HTMLIonNavElement|null { export function getNavAsChildIfExists(element: HTMLElement): HTMLIonNavElement|null {
for (let i = 0; i < element.children.length; i++) { for (let i = 0; i < element.children.length; i++) {
if (element.children[i].tagName.toLowerCase() === 'ion-nav') { if (element.children[i].tagName.toLowerCase() === 'ion-nav') {