mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 04:14:21 +08:00
feat: initial nav
This commit is contained in:
@ -1,16 +1,41 @@
|
|||||||
.nav-view {
|
.nav-view {
|
||||||
opacity: 0;
|
|
||||||
transition: 0.25s;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-view.in {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.nav-view.out {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
&.shown {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
&[animate] {
|
||||||
|
display: block;
|
||||||
|
transition: 1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[animate=push-enter] {
|
||||||
|
transform: translate3d(100%,0,0);
|
||||||
|
&.start {
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[animate=pop-enter] {
|
||||||
|
transform: translate3d(-100%,0,0);
|
||||||
|
&.start {
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[animate=push-leave] {
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
&.start {
|
||||||
|
transform: translate3d(-100%,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[animate=pop-leave] {
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
&.start {
|
||||||
|
transform: translate3d(100%,0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Component, Template, For} from 'angular2/angular2'
|
import {Component, Template, For} from 'angular2/angular2'
|
||||||
import {ComponentConfig} from 'ionic2/config/component-config'
|
import {ComponentConfig} from 'ionic2/config/component-config'
|
||||||
import {NavView} from 'ionic2/components/nav-view/nav-view'
|
import {NavView} from 'ionic2/components/nav-view/nav-view'
|
||||||
import * as util from 'ionic2/util'
|
import {array as arrayUtil, dom as domUtil} from 'ionic2/util'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-nav-viewport',
|
selector: 'ion-nav-viewport',
|
||||||
@ -11,15 +11,15 @@ import * as util from 'ionic2/util'
|
|||||||
})
|
})
|
||||||
@Template({
|
@Template({
|
||||||
inline: `
|
inline: `
|
||||||
<section class="nav-view" *for="#item of stack.rawItems()" [item]="item">
|
<section class="nav-view" *for="#item of creator._array" [item]="item">
|
||||||
</section>
|
</section>
|
||||||
`,
|
`,
|
||||||
directives: [NavView, For]
|
directives: [NavView, For]
|
||||||
})
|
})
|
||||||
export class NavViewport {
|
export class NavViewport {
|
||||||
constructor(
|
constructor() {
|
||||||
) {
|
this.stack = []
|
||||||
this.stack = new NavStack();
|
this.creator = new ItemCreator()
|
||||||
}
|
}
|
||||||
|
|
||||||
set initial(Class) {
|
set initial(Class) {
|
||||||
@ -39,9 +39,18 @@ export class NavViewport {
|
|||||||
* @param view the new view
|
* @param view the new view
|
||||||
* @param shouldAnimate whether to animate
|
* @param shouldAnimate whether to animate
|
||||||
*/
|
*/
|
||||||
|
// TODO only animate if state hasn't changed
|
||||||
|
// TODO make sure the timing is together
|
||||||
|
// TODO allow starting an animation in the middle (eg gestures). Leave
|
||||||
|
// most of this up to the animation's implementation.
|
||||||
push(Class, opts = {}) {
|
push(Class, opts = {}) {
|
||||||
//TODO animation
|
let item = new NavItem(Class, opts)
|
||||||
return this.stack.push(Class)
|
this.stack.push(item)
|
||||||
|
return this.creator.instantiate(item).then(() => {
|
||||||
|
let current = this.getPrevious(item)
|
||||||
|
current && current.pushLeave()
|
||||||
|
return item.pushEnter()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,8 +59,16 @@ export class NavViewport {
|
|||||||
* @param shouldAnimate whether to animate
|
* @param shouldAnimate whether to animate
|
||||||
*/
|
*/
|
||||||
pop() {
|
pop() {
|
||||||
// TODO animation
|
let current = this.stack.pop()
|
||||||
return this.stack.pop()
|
let previous = this.stack[this.stack.length - 1]
|
||||||
|
previous.popEnter()
|
||||||
|
current.popLeave().then(() => {
|
||||||
|
this.creator.destroy(current)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrevious(item) {
|
||||||
|
return this.stack[ this.stack.indexOf(item) - 1 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate a new view *in*
|
// Animate a new view *in*
|
||||||
@ -65,79 +82,70 @@ export class NavViewport {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NavStack {
|
class ItemCreator {
|
||||||
constructor(navView) {
|
constructor() {
|
||||||
// array to communicate with angular for loop. When an element is in this array, it really
|
|
||||||
// exists in the DOM.
|
|
||||||
this._array = []
|
this._array = []
|
||||||
}
|
}
|
||||||
rawItems() {
|
instantiate(navItem) {
|
||||||
return this._array
|
this._array.push(navItem)
|
||||||
|
return navItem.waitForSetup()
|
||||||
}
|
}
|
||||||
|
destroy(navItem) {
|
||||||
push(ComponentClass) {
|
return arrayUtil.remove(this._array, navItem)
|
||||||
const item = new NavStackItem(ComponentClass)
|
|
||||||
let last = this._array[this._array.length - 1]
|
|
||||||
|
|
||||||
this._array.push(item)
|
|
||||||
return item.setupPromise.then(() => {
|
|
||||||
// Once the item is successfully instantiated, add it to our public history
|
|
||||||
item.animateIn()
|
|
||||||
last && last.animateOut()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pop() {
|
|
||||||
// public registry: instantly remove to make the removal seem 'synchronous' for our data
|
|
||||||
const current = this._array[this._array.length - 1]
|
|
||||||
const previous = this._array[this._array.length - 2]
|
|
||||||
current.animateOut().then(() => {
|
|
||||||
util.array.remove(current)
|
|
||||||
})
|
|
||||||
return previous && previous.animateIn()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NavStackItem {
|
class NavItem {
|
||||||
constructor(ComponentClass) {
|
constructor(ComponentClass) {
|
||||||
this.setupPromise = new Promise((resolve) => {
|
|
||||||
this.finishSetup = (navView, componentInstance) => {
|
|
||||||
this.navView = navView
|
|
||||||
this.instance = componentInstance
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.Class = ComponentClass
|
this.Class = ComponentClass
|
||||||
|
this._setupPromise = new Promise((resolve) => {
|
||||||
|
this._resolveSetupPromise = resolve
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setInstance(instance) {
|
waitForSetup() {
|
||||||
this.instance = instance
|
return this._setupPromise
|
||||||
}
|
}
|
||||||
setNavView(navView) {
|
finishSetup(navView, componentInstance) {
|
||||||
this.navView = navView
|
this.navView = navView
|
||||||
|
this.instance = componentInstance
|
||||||
|
this._resolveSetupPromise()
|
||||||
}
|
}
|
||||||
|
setAnimation(state) {
|
||||||
animateIn() {
|
if (!state) {
|
||||||
this.navView.domElement.classList.remove('out')
|
this.navView.domElement.removeAttribute('animate')
|
||||||
this.navView.domElement.classList.add('in')
|
this.navView.domElement.classList.remove('start')
|
||||||
return new Promise(resolve => {
|
} else {
|
||||||
this.navView.domElement.addEventListener('transitionend', (ev) => {
|
this.navView.domElement.setAttribute('animate', state)
|
||||||
if (ev.target !== this.navView.domElement) {
|
}
|
||||||
return
|
}
|
||||||
}
|
setShown(isShown) {
|
||||||
resolve()
|
this.navView.domElement.classList[isShown?'add':'remove']('shown')
|
||||||
|
}
|
||||||
|
startAnimation() {
|
||||||
|
this.navView.domElement.classList.add('start')
|
||||||
|
}
|
||||||
|
_animate({ isShown, animation }) {
|
||||||
|
this.setAnimation(animation)
|
||||||
|
this.setShown(isShown)
|
||||||
|
// We have to wait two rafs for the element to show. Yawn.
|
||||||
|
return domUtil.rafPromise().then(domUtil.rafPromise).then(() => {
|
||||||
|
this.startAnimation()
|
||||||
|
return domUtil.transitionEndPromise(this.navView.domElement).then(() => {
|
||||||
|
this.setAnimation(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
animateOut() {
|
pushEnter() {
|
||||||
this.navView.domElement.classList.add('out')
|
return this._animate({ isShown: true, animation: 'push-enter' })
|
||||||
this.navView.domElement.classList.remove('in')
|
}
|
||||||
return new Promise(resolve => {
|
popEnter() {
|
||||||
this.navView.domElement.addEventListener('transitionend', (ev) => {
|
return this._animate({ isShown: true, animation: 'pop-enter' })
|
||||||
if (ev.target !== this.navView.domElement) {
|
}
|
||||||
return
|
pushLeave() {
|
||||||
}
|
return this._animate({ isShown: false, animation: 'push-leave' })
|
||||||
resolve()
|
}
|
||||||
})
|
popLeave() {
|
||||||
})
|
return this._animate({ isShown: false, animation: 'pop-leave' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<ion-view nav-title="Second Page">
|
<ion-view nav-title="Second Page">
|
||||||
<button (click)="prevPage()">Pop This Page</button>
|
<button (click)="pop()">Pop (Go back)</button>
|
||||||
</ion-view>
|
</ion-view>
|
||||||
|
@ -15,7 +15,7 @@ export class SecondPage {
|
|||||||
) {
|
) {
|
||||||
this.viewport = viewport
|
this.viewport = viewport
|
||||||
}
|
}
|
||||||
prevPage() {
|
pop() {
|
||||||
this.viewport.pop()
|
this.viewport.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,3 +32,21 @@ if (window.ontransitionend === undefined && window.onwebkittransitionend !== und
|
|||||||
css.transition = 'transition'
|
css.transition = 'transition'
|
||||||
css.transitionEnd = 'tranistionend'
|
css.transitionEnd = 'tranistionend'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transitionEndPromise(el:Element) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
css.transitionEnd.split(' ').forEach(eventName => {
|
||||||
|
el.addEventListener(css.transitionEnd, onTransitionEnd)
|
||||||
|
})
|
||||||
|
function onTransitionEnd(ev) {
|
||||||
|
// Don't allow bubbled transitionend events
|
||||||
|
if (ev.target !== el) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
css.transitionEnd.split(' ').forEach(eventName => {
|
||||||
|
el.removeEventListener(css.transitionEnd, onTransitionEnd)
|
||||||
|
})
|
||||||
|
resolve(ev)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user