feat(animations): back button and view animationsa (#13989)

* feat(animations): back button and view animations

* chore(): remove unused css

* fix(): fix unsaved css

* fix(): hide back button when no history
This commit is contained in:
Mike Hartington
2018-02-09 13:03:26 -05:00
committed by Manu MA
parent d7e0f0076e
commit 77e16bbc3f
21 changed files with 356 additions and 292 deletions

View File

@ -312,7 +312,7 @@ declare global {
} }
namespace JSXElements { namespace JSXElements {
export interface IonBackButtonAttributes extends HTMLAttributes { export interface IonBackButtonAttributes extends HTMLAttributes {
mode?: 'ios' | 'md';
} }
} }
} }

View File

@ -1,5 +0,0 @@
@import "./back-button";
@import "./back-button.ios.vars";
// iOS Back Button
// --------------------------------------------------

View File

@ -1,4 +0,0 @@
@import "../../themes/ionic.globals.ios";
// iOS Back Button
// --------------------------------------------------

View File

@ -1,22 +0,0 @@
@import "./back-button";
@import "./back-button.md.vars";
// Material Design Back Button
// --------------------------------------------------
.back-button-md {
@include margin(0, 6px);
min-width: 44px;
box-shadow: none;
}
.back-button-icon-md {
@include margin(0);
@include padding(0, 6px);
@include text-align(start);
font-size: 2.4rem;
font-weight: normal;
}

View File

@ -1,4 +0,0 @@
@import "../../themes/ionic.globals.md";
// Material Design Back Button
// --------------------------------------------------

View File

@ -2,14 +2,12 @@
// Back Button // Back Button
// -------------------------------------------------- // --------------------------------------------------
// .back-button {
// display: none;
// }
// .back-button.show-back-button {
.back-button { .back-button {
display: inline-block; display: none;
}
.back-button.show-back-button {
display: inline-block;
} }
.back-button-text { .back-button-text {

View File

@ -1,53 +1,53 @@
import { Component, Prop } from '@stencil/core'; import { Component, Element, Prop, State } from '@stencil/core';
import { Config } from '../../index'; import { Config } from '../../index';
@Component({ @Component({
tag: 'ion-back-button', tag: 'ion-back-button',
styleUrls: { styleUrl: 'back-button.scss',
ios: 'back-button.ios.scss',
md: 'back-button.md.scss'
},
host: { host: {
theme: 'back-button' theme: 'back-button'
} }
}) })
export class BackButton { export class BackButton {
private mode: string;
@State() custom: boolean;
/**
* The mode determines which platform styles to use.
* Possible values are: `"ios"` or `"md"`.
* For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
*/
@Prop() mode: 'ios' | 'md';
@Prop({ context: 'config' }) config: Config; @Prop({ context: 'config' }) config: Config;
@Element() el: HTMLElement;
componentWillLoad() {
this.custom = this.el.childElementCount > 0;
}
render() { render() {
const iconName = this.config.get('backButtonIcon', this.mode + '-arrow-back'); const backButtonIcon = this.config.get('backButtonIcon', 'arrow-back');
const text = this.config.get('backButtonText', 'Back'); const backButtonText = this.config.get('backButtonText', 'Back');
const buttonColor = this.mode === 'ios' ? 'primary' : '';
const iconClass: any = { if (this.custom) {
'back-button-icon': true return (
}; <ion-nav-pop>
const textClass: any = { <slot />
'back-button-text': true </ion-nav-pop>
}; );
} else if (!this.custom) {
if (this.mode) { return (
iconClass['back-button-icon-' + this.mode] = true; <ion-nav-pop>
iconClass['back-button-text-' + this.mode] = true; <ion-button color={buttonColor}>
<ion-icon name={backButtonIcon} slot='start' />
{backButtonText}
</ion-button>
</ion-nav-pop>
);
} else {
return undefined;
} }
return (
<ion-nav-pop>
<button>
<span class={iconClass}>
<slot name='icon'>
<ion-icon name={iconName}></ion-icon>
</slot>
</span>
<span class={textClass}>
<slot name='text'>
{text}
</slot>
</span>
</button>
</ion-nav-pop>
);
} }
} }

View File

@ -1,9 +1,86 @@
# ion-back-button # ion-back-button
A back button is a component that allows you navigate back into app history. To
add a back button to your view, all you need is:
```html
<ion-page>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
</ion-page>
```
The back button component is smart enough to know what to render and what content to show.
If, however, you want more control over what is shown in the back button, you can pass your own button markup.
```html
<ion-page>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button>
<ion-button>
Button Text
<ion-icon name="add" slot="start"></ion-icon>
</ion-button>
</ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
</ion-page>
```
Or no button text at all:
```html
<ion-page>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button>
<ion-button>
<ion-icon name="add" slot="icon-only"></ion-icon>
</ion-button>
</ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
</ion-page>
```
<!-- Auto Generated Below --> <!-- Auto Generated Below -->
## Properties
#### mode
The mode determines which platform styles to use.
Possible values are: `"ios"` or `"md"`.
For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
## Attributes
#### mode
The mode determines which platform styles to use.
Possible values are: `"ios"` or `"md"`.
For more information, see [Platform Styles](/docs/theming/platform-specific-styles).
---------------------------------------------- ----------------------------------------------

View File

@ -35,6 +35,9 @@
firstPage.innerHTML = ` firstPage.innerHTML = `
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page One</ion-title> <ion-title>Page One</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@ -47,7 +50,7 @@
await nav.push(firstPage); await nav.push(firstPage);
// okay cool, we're in the DOM now // okay cool, we're in the DOM now
const button = firstPage.querySelector('ion-button'); const button = firstPage.querySelector('.next');
button.addEventListener('click', async () => { button.addEventListener('click', async () => {
await goToPageTwo(nav); await goToPageTwo(nav);
}); });
@ -60,14 +63,14 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-back-button> <ion-back-button></ion-back-button>
</ion-back-button>
</ion-buttons> </ion-buttons>
<ion-title>Page Two</ion-title> <ion-title>Page Two</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content padding>
<h1>Page Two</h1> <h1>Page Two</h1>
<p>Just an empty <code>ion-back-button</code></p>
<ion-button class="next">Go to Page Three</ion-button> <ion-button class="next">Go to Page Three</ion-button>
</ion-content> </ion-content>
`; `;
@ -89,6 +92,9 @@
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-back-button> <ion-back-button>
<ion-button>
<ion-icon name="add" slot="icon-only"></ion-icon>
</ion-button>
</ion-back-button> </ion-back-button>
</ion-buttons> </ion-buttons>
<ion-title>Page Three</ion-title> <ion-title>Page Three</ion-title>
@ -96,6 +102,7 @@
</ion-header> </ion-header>
<ion-content padding> <ion-content padding>
<h1>Page Three</h1> <h1>Page Three</h1>
<p>Custom back button</p>
</ion-content> </ion-content>
`; `;
@ -103,10 +110,10 @@
await nav.push(thirdPage); await nav.push(thirdPage);
} }
</script> </script>
<style> <!-- <style> -->
ion-back-button { <!-- ion&#45;back&#45;button { -->
height: 50px; <!-- height: 50px; -->
background-color: blue; <!-- background&#45;color: blue; -->
} <!-- } -->
</style> <!-- </style> -->
</html> </html>

View File

@ -134,7 +134,7 @@ export class Button {
<span class='button-inner'> <span class='button-inner'>
<slot name='icon-only'></slot> <slot name='icon-only'></slot>
<slot name='start'></slot> <slot name='start'></slot>
<slot></slot> <span class='button-text'><slot></slot></span>
<slot name='end'></slot> <slot name='end'></slot>
</span> </span>
{ this.mode === 'md' && <ion-ripple-effect useTapClick={true} /> } { this.mode === 'md' && <ion-ripple-effect useTapClick={true} /> }

View File

@ -2,13 +2,6 @@ import { Component, Element, Listen } from '@stencil/core';
@Component({ @Component({
tag: 'ion-nav-pop', tag: 'ion-nav-pop',
styleUrls: {
ios: 'nav-pop.ios.scss',
md: 'nav-pop.md.scss'
},
host: {
theme: 'nav-pop'
}
}) })
export class NavPop { export class NavPop {

View File

@ -70,17 +70,15 @@ export function updateZIndex(viewController: ViewController, newZIndex: number)
} }
} }
export function toggleHidden(element: HTMLElement, isVisible: Boolean, shouldBeVisible: boolean) { export function toggleHidden(element: HTMLElement, shouldBeHidden: boolean) {
if (isVisible !== shouldBeVisible) { element.hidden = shouldBeHidden;
element.hidden = shouldBeVisible;
}
} }
export function canNavGoBack(nav: Nav) { export function canNavGoBack(nav: Nav, view?: ViewController) {
if (!nav) { if (!nav) {
return false; return false;
} }
return !!nav.getPrevious(); return nav.getPrevious(view);
} }
export function transitionFactory(animation: Animation): Transition { export function transitionFactory(animation: Animation): Transition {

View File

@ -13,7 +13,10 @@ ion-nav {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: black;
contain: layout size style; contain: layout size style;
} }
.ion-page { .ion-page {

View File

@ -737,7 +737,6 @@ export function loadViewAndTransition(nav: Nav, enteringView: ViewController, le
}); });
} }
export function executeAsyncTransition(nav: Nav, transition: Transition, enteringView: ViewController, leavingView: ViewController, delegate: FrameworkDelegate, opts: NavOptions, configShouldAnimate: boolean): Promise<void> { export function executeAsyncTransition(nav: Nav, transition: Transition, enteringView: ViewController, leavingView: ViewController, delegate: FrameworkDelegate, opts: NavOptions, configShouldAnimate: boolean): Promise<void> {
assert(nav.transitioning, 'must be transitioning'); assert(nav.transitioning, 'must be transitioning');
nav.transitionId = null; nav.transitionId = null;
@ -746,11 +745,11 @@ export function executeAsyncTransition(nav: Nav, transition: Transition, enterin
// always ensure the entering view is viewable // always ensure the entering view is viewable
// ******** DOM WRITE **************** // ******** DOM WRITE ****************
// TODO, figure out where we want to read this data from // TODO, figure out where we want to read this data from
enteringView && toggleHidden(enteringView.element, true, true); enteringView && toggleHidden(enteringView.element, false);
// always ensure the leaving view is viewable // always ensure the leaving view is viewable
// ******** DOM WRITE **************** // ******** DOM WRITE ****************
leavingView && toggleHidden(leavingView.element, true, true); leavingView && toggleHidden(leavingView.element, false);
const isFirstPage = !nav.isViewInitialized && nav.views.length === 1; const isFirstPage = !nav.isViewInitialized && nav.views.length === 1;
const shouldNotAnimate = isFirstPage && !nav.isPortal; const shouldNotAnimate = isFirstPage && !nav.isPortal;
@ -834,15 +833,16 @@ export function transitionFinish(nav: Nav, transition: Transition, delegate: Fra
} }
export function cleanUpView(nav: Nav, delegate: FrameworkDelegate, activeViewController: ViewController): Promise<any> { export function cleanUpView(nav: Nav, delegate: FrameworkDelegate, activeViewController: ViewController): Promise<any> {
if (nav.destroyed) { if (nav.destroyed) {
return Promise.resolve(); return Promise.resolve();
} }
const activeIndex = nav.views.indexOf(activeViewController); const activeIndex = nav.views.indexOf(activeViewController);
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
for (let i = nav.views.length - 1; i >= 0; i--) { for (let i = nav.views.length - 1; i >= 0; i--) {
const inactiveViewController = nav.views[i]; const inactiveViewController = nav.views[i];
if (i > activeIndex) { if (i > activeIndex) {
// this view comes after the active view // this view comes after the active view
inactiveViewController.willUnload(); inactiveViewController.willUnload();
@ -850,14 +850,14 @@ export function cleanUpView(nav: Nav, delegate: FrameworkDelegate, activeViewCon
} else if ( i < activeIndex && !nav.isPortal) { } else if ( i < activeIndex && !nav.isPortal) {
// this view comes before the active view // this view comes before the active view
// and it is not a portal then ensure it is hidden // and it is not a portal then ensure it is hidden
toggleHidden(inactiveViewController.element, true, false); toggleHidden(inactiveViewController.element, true);
} }
// TODO - review existing z index code! // TODO - review existing z index code!
} }
return Promise.all(promises); return Promise.all(promises);
} }
export function fireViewWillLifecycles(enteringView: ViewController, leavingView: ViewController) { export function fireViewWillLifecycles(enteringView: ViewController, leavingView: ViewController) {
leavingView && leavingView.willLeave(!enteringView); leavingView && leavingView.willLeave(!enteringView);
enteringView && enteringView.willEnter(); enteringView && enteringView.willEnter();

View File

@ -11,174 +11,198 @@ const CENTER = '0%';
const OFF_OPACITY = 0.8; const OFF_OPACITY = 0.8;
const SHOW_BACK_BTN_CSS = 'show-back-button'; const SHOW_BACK_BTN_CSS = 'show-back-button';
// TODO - make sure this uses the `ion-page` logic from the md transition
// DO this later since the transition is broke as a joke anyway
// Dan B 1/9/2018
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>[] = [];
rootTransition.enteringView = enteringView; // Let makes sure everything is hydrated and ready to animate
rootTransition.leavingView = leavingView;
const isRTL = document.dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
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');
const backDirection = (opts.direction === 'back');
if (enteringView) { if (enteringView) {
const enteringContent = rootTransition.create(); componentReadyPromise.push((enteringView.element as any).componentOnReady());
enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *')); }
if (leavingView) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}
rootTransition.add(enteringContent); return Promise.all(componentReadyPromise).then(() => {
// Cool we're all hydrated, and can do deep selector
rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView;
if (backDirection) { const isRTL = document.dir === 'rtl';
enteringContent.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true).fromTo(OPACITY, OFF_OPACITY, 1, true); const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
} else { const OFF_LEFT = isRTL ? '33%' : '-33%';
// entering content, forward direction
enteringContent.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
}
const enteringToolBarEle = enteringView.element.querySelector('ion-toolbar'); rootTransition.duration(isDef(opts.duration) ? opts.duration : DURATION);
if (enteringToolBarEle) { rootTransition.easing(isDef(opts.easing) ? opts.easing : EASING);
const enteringToolBar = rootTransition.create();
enteringToolBar.addElement(enteringToolBarEle);
rootTransition.add(enteringToolBar);
const enteringTitle = rootTransition.create(); rootTransition.addElement(enteringView.element);
enteringTitle.addElement(enteringToolBarEle.querySelector('ion-title')); rootTransition.beforeRemoveClass('hide-page');
const enteringToolBarItems = rootTransition.create();
enteringToolBarItems.addElement(enteringToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const enteringToolBarBg = rootTransition.create();
enteringToolBarBg.addElement(enteringToolBarEle.querySelector('.toolbar-background'));
const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringToolBarEle.querySelector('.back-button'));
enteringToolBar const backDirection = (opts.direction === 'back');
.add(enteringTitle)
.add(enteringToolBarItems)
.add(enteringToolBarBg)
.add(enteringBackButton);
enteringTitle.fromTo(OPACITY, 0.01, 1, true); // setting up enter view
enteringToolBarItems.fromTo(OPACITY, 0.01, 1, true); if (enteringView) {
const enteringContent = rootTransition.create();
enteringContent.addElement(enteringView.element.querySelector('ion-content'));
enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
rootTransition.add(enteringContent);
if (backDirection) { if (backDirection) {
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true); enteringContent
.beforeClearStyles([OPACITY])
if (canNavGoBack(enteringView.nav)) { .fromTo(TRANSLATEX, OFF_LEFT, CENTER, true)
// back direction, entering page has a back button .fromTo(OPACITY, OFF_OPACITY, 1, true);
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
}
} else { } else {
// entering toolbar, forward direction // entering content, forward direction
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); enteringContent
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
enteringToolBarBg.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); }
if (canNavGoBack(enteringView.nav)) { const enteringToolBarEle = enteringView.element.querySelector('ion-toolbar');
// forward direction, entering page has a back button if (enteringToolBarEle) {
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true); const enteringToolBar = rootTransition.create();
enteringToolBar.addElement(enteringToolBarEle);
rootTransition.add(enteringToolBar);
const enteringTitle = rootTransition.create();
enteringTitle.addElement(enteringToolBarEle.querySelector('ion-title'));
const enteringBackBtnText = rootTransition.create(); const enteringToolBarItems = rootTransition.create();
enteringBackBtnText.addElement(enteringToolBarEle.querySelector('.back-button-text')); enteringToolBarItems.addElement(enteringToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px'); const enteringToolBarBg = rootTransition.create();
enteringToolBar.add(enteringBackBtnText); enteringToolBarBg.addElement(enteringToolBarEle.querySelector('.toolbar-background'));
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 {
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS); // entering toolbar, forward direction
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); 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)
.fromTo(OPACITY, 1, OFF_OPACITY)
.afterClearStyles([TRANSFORM, OPACITY]);
}
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);
leavingTitle.fromTo(OPACITY, 0.99, 0);
leavingToolBarItems.fromTo(OPACITY, 0.99, 0);
if (backDirection) { if (backDirection) {
// leaving toolbar, back direction // leaving content, back direction
leavingTitle.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%')); leavingContent
// 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-text'));
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -300 : 300) + 'px');
leavingToolBar.add(leavingBackBtnText);
} else { } else {
// leaving toolbar, forward direction // leaving content, forward direction
leavingTitle leavingContent
.fromTo(TRANSLATEX, CENTER, OFF_LEFT) .fromTo(TRANSLATEX, CENTER, OFF_LEFT, true)
.afterClearStyles([TRANSFORM]); .fromTo(OPACITY, 1, OFF_OPACITY, true);
}
leavingBackButton.afterClearStyles([OPACITY]); const leavingToolBarEle = leavingView.element.querySelector('ion-toolbar');
leavingTitle.afterClearStyles([OPACITY]); if (leavingToolBarEle) {
leavingToolBarItems.afterClearStyles([OPACITY]); 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) {
// 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 Promise.resolve(rootTransition); // Return the rootTransition promise
return rootTransition;
});
} }

View File

@ -9,6 +9,15 @@ 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) {
componentReadyPromise.push((enteringView.element as any).componentOnReady());
}
if (leavingView) {
componentReadyPromise.push((leavingView.element as any).componentOnReady());
}
return Promise.all(componentReadyPromise).then(() => {
rootTransition.enteringView = enteringView; rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView; rootTransition.leavingView = leavingView;
@ -19,9 +28,12 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
const backDirection = (opts.direction === 'back'); const backDirection = (opts.direction === 'back');
if (enteringView) { if (enteringView) {
// animate the component itself
if (backDirection) { if (backDirection) {
rootTransition.duration(isDef(opts.duration) ? opts.duration : 200).easing('cubic-bezier(0.47,0,0.745,0.715)'); rootTransition.duration(isDef(opts.duration) ? opts.duration : 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
} else { }
else {
rootTransition.duration(isDef(opts.duration) ? opts.duration : 280).easing('cubic-bezier(0.36,0.66,0.04,1)'); rootTransition.duration(isDef(opts.duration) ? opts.duration : 280).easing('cubic-bezier(0.36,0.66,0.04,1)');
rootTransition rootTransition
@ -29,8 +41,10 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
.fromTo('opacity', 0.01, 1, true); .fromTo('opacity', 0.01, 1, true);
} }
// Animate toolbar if it's there
const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar'); const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar');
if (enteringToolbarEle) { if (enteringToolbarEle) {
const enteringToolBar = rootTransition.create(); const enteringToolBar = rootTransition.create();
enteringToolBar.addElement(enteringToolbarEle); enteringToolBar.addElement(enteringToolbarEle);
rootTransition.add(enteringToolBar); rootTransition.add(enteringToolBar);
@ -38,8 +52,7 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
const enteringBackButton = rootTransition.create(); const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringToolbarEle.querySelector('.back-button')); enteringBackButton.addElement(enteringToolbarEle.querySelector('.back-button'));
rootTransition.add(enteringBackButton); rootTransition.add(enteringBackButton);
if (canNavGoBack(enteringView.nav, enteringView)) {
if (canNavGoBack(enteringView.nav)) {
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS); enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS);
} else { } else {
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS); enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
@ -56,7 +69,10 @@ 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 Promise.resolve(rootTransition); return rootTransition;
});
} }
function getIonPageElement(element: HTMLElement) { function getIonPageElement(element: HTMLElement) {

View File

@ -299,33 +299,33 @@
// iOS Toolbar Back Button // iOS Toolbar Back Button
// -------------------------------------------------- // --------------------------------------------------
.back-button-ios { // .back-button-ios {
@include margin(0); // @include margin(0);
//
z-index: $z-index-toolbar-buttons; // z-index: $z-index-toolbar-buttons;
overflow: visible; // overflow: visible;
//
order: map-get($toolbar-order-ios, back-button); // order: map-get($toolbar-order-ios, back-button);
//
min-height: 32px; // min-height: 32px;
//
line-height: 1; // line-height: 1;
transform: translateZ(0); // transform: translateZ(0);
} // }
//
.back-button-icon-ios { // .back-button-icon-ios {
@include margin(-1px, 0, 0, 0); // @include margin(-1px, 0, 0, 0);
//
display: inherit; // display: inherit;
//
min-width: 18px; // min-width: 18px;
//
font-size: 34px; // font-size: 34px;
} // }
//
.back-button-text-ios { // .back-button-text-ios {
letter-spacing: -.01em; // letter-spacing: -.01em;
} // }
// iOS Toolbar Menu Toggle // iOS Toolbar Menu Toggle

View File

@ -130,20 +130,3 @@ ion-buttons,
align-items: center; align-items: center;
} }
// Back Button
// --------------------------------------------------
.back-button {
display: none;
}
.back-button.show-back-button {
display: inline-block;
}
.back-button-text {
display: flex;
align-items: center;
}

View File

@ -1,5 +1,5 @@
export function isCordova(): boolean { export function isCordova(): boolean {
const win = window as any; const win = window as any;
return !!(win['cordova'] || win['PhoneGap'] || win['phonegap']); return !!(win['cordova'] || win['PhoneGap'] || win['phonegap'] || win['Capacitor']);
} }