feat(aside): reveal/overlay aside using Animation

This commit is contained in:
Adam Bradley
2015-09-10 20:54:40 -05:00
parent 33665668f8
commit b31ab1b0be
27 changed files with 791 additions and 583 deletions

View File

@@ -39,7 +39,7 @@ export class IonicApp {
*/
constructor() {
this.overlays = [];
this._isTransitioning = false;
this._transTime = 0;
// Our component registry map
this.components = {};
@@ -82,7 +82,7 @@ export class IonicApp {
* @param {bool} isTransitioning
*/
setTransitioning(isTransitioning) {
this._isTransitioning = !!isTransitioning;
this._transTime = (isTransitioning ? Date.now() : 0);
}
/**
@@ -90,7 +90,7 @@ export class IonicApp {
* @return {bool}
*/
isTransitioning() {
return this._isTransitioning;
return (this._transTime + 800 > Date.now());
}
/**

View File

@@ -33,6 +33,5 @@ export class AsideToggle {
*/
toggle(event) {
this.aside && this.aside.toggle();
console.log('Aside toggle');
}
}

View File

@@ -2,155 +2,56 @@
// Aside
// --------------------------------------------------
$aside-width: 304px !default;
$aside-small-width: $aside-width - 40px !default;
$aside-width: 304px !default;
$aside-small-width: $aside-width - 40px !default;
$aside-transition: 0.2s ease transform !default;
$aside-backdrop-transition: 0.2s ease background-color !default;
$aside-background: $background-color !default;
$aside-shadow: -1px 0px 8px rgba(0, 0, 0, 0.2) !default;
$aside-background: $background-color !default;
$aside-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25) !default;
.aside {
ion-aside {
position: absolute;
top: 0;
right: auto;
bottom: 0;
left: 0;
left: -$aside-width;
width: $aside-width;
display: flex;
flex-direction: column;
background: $aside-background;
transform: translate3d(0, 0, 0);
transition: $aside-transition;
&.no-transition {
ion-aside-backdrop {
transition: none;
}
}
&[type=overlay] {
z-index: $z-index-aside-overlay;
&:not(.open):not(.changing) {
display: none;
}
}
&[type=reveal] {
left: 0;
&:not(.open):not(.changing) {
display: none;
}
}
&.open, {
&[type=reveal],
&[type=push] {
left: 0;
}
&[type=overlay] {
transform: translate3d($aside-width,0,0);
box-shadow: 1px 2px 16px rgba(0, 0, 0, 0.3);
ion-aside-backdrop {
opacity: 0.5;
}
}
}
&[side=right] {
width: $aside-width;
left: 100%;
top: 0;
bottom: 0;
transform: translate3d(0,0,0);
&.open,
&[type=reveal] {
transform: translate3d(-$aside-width,0,0);
}
}
&[side=top] {
height: $aside-width;
top: -$aside-width;
left: 0;
right: 0;
transform: translate3d(0,0,0);
&.open,
&[type=reveal] {
transform: translate3d(0,$aside-width,0);
}
}
&[side=bottom] {
height: $aside-width;
top: 100%;
left: 0;
right: 0;
transform: translate3d(0,0,0);
&.open,
&.type-reveal {
transform: translate3d(0,-$aside-width,0);
}
}
}
ion-aside-backdrop {
z-index: $z-index-aside-backdrop;
transition: $aside-backdrop-transition;
transform: translateX($aside-width);
top: 0;
ion-aside[side=right] {
right: 0;
left: 0;
bottom: 0;
position: fixed;
background-color: rgb(0,0,0);
left: auto;
}
ion-aside backdrop {
z-index: -1;
display: none;
}
.aside-content {
transition: $aside-transition;
transform: translate3d(0,0,0);
transform: translate3d(0px, 0px, 0px);
}
box-shadow: $aside-shadow;
&.aside-open-left {
transform: translate3d($aside-width,0,0);
pointer-events: none;
}
&.aside-open-right {
transform: translate3d(-$aside-width,0,0);
pointer-events: none;
}
.aside-content-open ion-pane,
.aside-content-open ion-content,
.aside-content-open .toolbar {
// the containing element itself should be clickable but
// everything inside of it should not clickable when aside is open
pointer-events: none;
}
@media (max-width: 340px) {
.aside {
left: -$aside-small-width;
ion-aside {
width: $aside-small-width;
}
.aside-content {
&.aside-open-left {
transform: translate3d($aside-small-width, 0, 0);
}
&.aside-open-right {
transform: translate3d(-$aside-small-width, 0, 0);
}
}
}

View File

@@ -1,13 +1,11 @@
import {forwardRef, Component, Host, View, EventEmitter, ElementRef} from 'angular2/angular2';
import {forwardRef, Directive, Host, View, EventEmitter, ElementRef} from 'angular2/angular2';
import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {IonicConfig} from '../../config/config';
import {IonicComponent} from '../../config/annotations';
import * as types from './extensions/types'
import * as gestures from './extensions/gestures'
import * as util from 'ionic/util/util'
import {dom} from 'ionic/util'
import * as gestures from './extensions/gestures';
/**
* Aside is a side-menu navigation that can be dragged out or toggled to show. Aside supports two
@@ -25,152 +23,74 @@ import {dom} from 'ionic/util'
'side': 'left',
'type': 'reveal'
},
delegates: {
gesture: [
//[instance => instance.side == 'top', gestures.TopAsideGesture],
//[instance => instance.side == 'bottom', gestures.BottomAsideGesture],
[instance => instance.side == 'right', gestures.RightAsideGesture],
[instance => instance.side == 'left', gestures.LeftAsideGesture],
],
type: [
[instance => instance.type == 'overlay', types.AsideTypeOverlay],
[instance => instance.type == 'reveal', types.AsideTypeReveal],
//[instance => instance.type == 'push', types.AsideTypePush],
]
},
events: ['opening']
})
@View({
template: '<ng-content></ng-content><ion-aside-backdrop></ion-aside-backdrop>',
template: '<ng-content></ng-content><backdrop tappable></backdrop>',
directives: [forwardRef(() => AsideBackdrop)]
})
export class Aside extends Ion {
/**
* TODO
* @param {IonicApp} app TODO
* @param {ElementRef} elementRef Reference to the element.
*/
constructor(app: IonicApp, elementRef: ElementRef, config: IonicConfig) {
super(elementRef, config);
this.app = app;
this.opening = new EventEmitter('opening');
//this.animation = new Animation(element.querySelector('backdrop'));
this.contentClickFn = (e) => {
if(!this.isOpen || this.isChanging) { return; }
this.close();
};
this.finishChanging = util.debounce(() => {
this.setChanging(false);
});
// TODO: Use Animation Class
this.getNativeElement().addEventListener('transitionend', ev => {
//this.setChanging(false)
clearTimeout(this.setChangeTimeout);
this.setChangeTimeout = setInterval(this.finishChanging, 400);
})
this.isOpen = false;
this._disableTime = 0;
}
/**
* TODO
*/
onDestroy() {
app.unregister(this);
}
/**
* TODO
*/
onInit() {
super.onInit();
this.contentElement = (this.content instanceof Node) ? this.content : this.content.getNativeElement();
if(!this.id) {
if (!this.contentElement) {
return console.error('Aside: must have a [content] element to listen for drag events on. Example:\n\n<ion-aside [content]="content"></ion-aside>\n\n<ion-content #content></ion-content>');
}
if (!this.id) {
// Auto register
this.app.register('menu', this);
}
if(this.contentElement) {
this.contentElement.addEventListener('transitionend', ev => {
//this.setChanging(false)
clearTimeout(this.setChangeTimeout);
this.setChangeTimeout = setInterval(this.finishChanging, 400);
})
this.contentElement.addEventListener('click', this.contentClickFn);
} else {
console.error('Aside: must have a [content] element to listen for drag events on. Supply one like this:\n\n<ion-aside [content]="content"></ion-aside>\n\n<ion-content #content>');
}
this._initGesture();
this._initType(this.type);
this.contentElement.classList.add('aside-content');
this.contentElement.classList.add('aside-content-' + this.type);
this.gestureDelegate = this.getDelegate('gesture');
this.typeDelegate = this.getDelegate('type');
let self = this;
this.onContentClick = function(ev) {
ev.preventDefault();
ev.stopPropagation();
self.close();
};
}
onDestroy() {
this.contentElement.removeEventListener('click', this.contentClickFn);
}
_initGesture() {
switch(this.side) {
case 'right':
this._gesture = new gestures.RightAsideGesture(this);
break;
/**
* TODO
* @return {Element} The Aside's content element.
*/
getContentElement() {
return this.contentElement;
}
/**
* TODO
* @param {TODO} v TODO
*/
setOpenAmt(v) {
this.opening.next(v);
}
/**
* TODO
* @param {boolean} willOpen TODO
*/
setDoneTransforming(willOpen) {
this.typeDelegate.setDoneTransforming(willOpen);
}
/**
* TODO
* @param {TODO} transform TODO
*/
setTransform(transform) {
this.typeDelegate.setTransform(transform)
}
/**
* TODO
* @param {boolean} isSliding TODO
*/
setSliding(isSliding) {
if (isSliding !== this.isSliding) {
this.typeDelegate.setSliding(isSliding)
case 'left':
this._gesture = new gestures.LeftAsideGesture(this);
break;
}
}
/**
* TODO
* @param {boolean} isChanging TODO
*/
setChanging(isChanging) {
_initType(type) {
type = type && type.trim().toLowerCase() || FALLBACK_ASIDE_TYPE;
// Stop any last changing end operations
clearTimeout(this.setChangeTimeout);
if (isChanging !== this.isChanging) {
this.isChanging = isChanging
this.getNativeElement().classList[isChanging ? 'add' : 'remove']('changing');
let asideTypeCls = asideTypes[type];
if (!asideTypeCls) {
type = FALLBACK_ASIDE_TYPE;
asideTypeCls = asideTypes[type];
}
this._type = new asideTypeCls(this);
this.type = type;
}
/**
@@ -178,18 +98,79 @@ export class Aside extends Ion {
* @param {boolean} isOpen If the Aside is open or not.
* @return {Promise} TODO
*/
setOpen(isOpen) {
if (isOpen !== this.isOpen) {
this.isOpen = isOpen;
this.setChanging(true);
// Set full or closed amount
this.setOpenAmt(isOpen ? 1 : 0);
return dom.rafPromise().then(() => {
this.typeDelegate.setOpen(isOpen)
})
setOpen(shouldOpen) {
// _isDisabled is used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the aside-toggle button
if (shouldOpen === this.isOpen || this._isDisabled()) {
return Promise.resolve();
}
this._before();
return this._type.setOpen(shouldOpen).then(() => {
this._after(shouldOpen);
});
}
setProgressStart() {
// user started swiping the aside open/close
if (this._isDisabled()) return;
this._before();
this._type.setProgressStart(this.isOpen);
}
setProgess(value) {
// user actively dragging the menu
this._disable();
this._type.setProgess(value);
}
setProgressFinish(shouldComplete) {
// user has finished dragging the menu
this._disable();
this._type.setProgressFinish(shouldComplete).then(isOpen => {
this._after(isOpen);
});
}
_before() {
// this places the aside into the correct location before it animates in
// this css class doesn't actually kick off any animations
this.getNativeElement().classList.add('show-aside');
this.getBackdropElement().classList.add('show-backdrop');
this._disable();
this.app.setTransitioning(true);
}
_after(isOpen) {
this._disable();
this.isOpen = isOpen;
this.contentElement.classList[isOpen ? 'add' : 'remove']('aside-content-open');
this.contentElement.removeEventListener('click', this.onContentClick);
if (isOpen) {
this.contentElement.addEventListener('click', this.onContentClick);
} else {
this.getNativeElement().classList.remove('show-aside');
this.getBackdropElement().classList.remove('show-backdrop');
}
this.app.setTransitioning(false);
}
_disable() {
// used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the aside-toggle
this._disableTime = Date.now();
}
_isDisabled() {
return this._disableTime + 300 > Date.now();
}
/**
@@ -216,55 +197,75 @@ export class Aside extends Ion {
return this.setOpen(!this.isOpen);
}
/**
* TODO
* @return {Element} The Aside element.
*/
getAsideElement() {
return this.getNativeElement();
}
/**
* TODO
* @return {Element} The Aside's associated content element.
*/
getContentElement() {
return this.contentElement;
}
/**
* TODO
* @return {Element} The Aside's associated content element.
*/
getBackdropElement() {
return this.backdrop.elementRef.nativeElement;
}
static register(name, cls) {
asideTypes[name] = cls;
}
onDestroy() {
this.app.unregister(this);
this._type && this._type.onDestroy();
this.contentElement = null;
}
}
let asideTypes = {};
const FALLBACK_ASIDE_TYPE = 'reveal';
/**
* TODO
*/
@Component({
selector: 'ion-aside-backdrop',
@Directive({
selector: 'backdrop',
host: {
'[style.width]': 'width',
'[style.height]': 'height',
'[style.opacity]': 'opacity',
'(click)': 'clicked($event)'
}
})
@View({
template: ''
})
export class AsideBackdrop extends Ion {
class AsideBackdrop {
/**
* TODO
* @param {ElementReg} elementRef TODO
* @param {IonicConfig} config TODO
* @param {Aside} aside TODO
*/
constructor(elementRef: ElementRef, config: IonicConfig, @Host() aside: Aside) {
super(elementRef, config);
aside.backdrop = this;
constructor(@Host() aside: Aside, elementRef: ElementRef) {
this.aside = aside;
this.opacity = 0;
}
/**
* TODO
*/
onInit() {
let ww = window.innerWidth;
let wh = window.innerHeight;
this.width = ww + 'px';
this.height = wh + 'px';
this.elementRef = elementRef;
aside.backdrop = this;
}
/**
* TODO
* @param {TODO} event TODO
*/
clicked(event) {
clicked(ev) {
ev.preventDefault();
ev.stopPropagation();
this.aside.close();
}
}

View File

@@ -1,47 +1,37 @@
import {Aside} from 'ionic/components/aside/aside';
//TODO: figure out way to get rid of all the ../../../../
import {Aside} from '../aside';
import {SlideEdgeGesture} from 'ionic/gestures/slide-edge-gesture';
class AsideTargetGesture extends SlideEdgeGesture {
constructor(aside: Aside) {
let asideElement = aside.getNativeElement();
super(asideElement, {
class AsideGenericGestureHandler extends SlideEdgeGesture {
constructor(aside: Aside, targetElement, threshold) {
super(targetElement, {
direction: (aside.side === 'left' || aside.side === 'right') ? 'x' : 'y',
edge: aside.side,
threshold: 0
threshold: threshold
});
this.aside = aside;
this.listen();
}
canStart(ev) {
return this.aside.isOpen;
}
// Set CSS, then wait one frame for it to apply before sliding starts
onSlideBeforeStart(slide, ev) {
this.aside.setSliding(true);
this.aside.setChanging(true);
return new Promise(resolve => {
requestAnimationFrame(resolve);
});
this.aside.setProgressStart();
}
onSlide(slide, ev) {
this.aside.setOpenAmt(slide.distance / slide.max);
this.aside.setTransform(slide.distance);
this.aside.setProgess(slide.distance / slide.max);
}
onSlideEnd(slide, ev) {
this.aside.setSliding(false);
if (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5) {
this.aside.setOpen(!this.aside.isOpen);
this.aside.setDoneTransforming(!this.aside.isOpen);
} else {
this.aside.setDoneTransforming(this.aside.isOpen);
}
let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
this.aside.setProgressFinish(shouldComplete);
}
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.max : slide.min;
}
getSlideBoundaries() {
return {
min: 0,
@@ -50,64 +40,26 @@ class AsideTargetGesture extends SlideEdgeGesture {
}
}
class AsideGesture extends SlideEdgeGesture {
export class AsideContentGesture extends AsideGenericGestureHandler {
constructor(aside: Aside) {
// TODO figure out the sliding element, dont just use the parent
let contentElement = aside.getContentElement();
super(contentElement, {
direction: (aside.side === 'left' || aside.side === 'right') ? 'x' : 'y',
edge: aside.side,
threshold: 75
});
this.aside = aside;
this.slideElement = contentElement;
this.listen();
let contentGesture = new AsideTargetGesture(aside);
contentGesture.listen();
super(aside, aside.getContentElement(), 75);
}
canStart(ev) {
// Only restrict edges if the aside is closed
return this.aside.isOpen ? true : super.canStart(ev);
}
}
// Set CSS, then wait one frame for it to apply before sliding starts
onSlideBeforeStart(slide, ev) {
this.aside.setSliding(true);
this.aside.setChanging(true);
return new Promise(resolve => {
requestAnimationFrame(resolve);
});
}
onSlide(slide, ev) {
this.aside.setOpenAmt(slide.distance / slide.max);
this.aside.setTransform(slide.distance);
}
onSlideEnd(slide, ev) {
this.aside.setSliding(false);
if (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5) {
this.aside.setOpen(!this.aside.isOpen);
this.aside.setDoneTransforming(!this.aside.isOpen);
} else {
this.aside.setDoneTransforming(false);
}
}
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.max : slide.min;
}
getSlideBoundaries() {
return {
min: 0,
max: this.aside.width()
};
export class LeftAsideGesture extends AsideContentGesture {
constructor(aside: Aside) {
super(aside);
}
}
export class LeftAsideGesture extends AsideGesture {}
export class RightAsideGesture extends LeftAsideGesture {
constructor(aside: Aside) {
super(aside);
}
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}

View File

@@ -0,0 +1,41 @@
// Aside Reveal
// --------------------------------------------------
// The content slides over to reveal the aside underneath.
// The aside menu itself, which is under the content, does not move.
ion-aside[type=reveal] {
transform: translate3d(-9999px, 0px, 0px);
&.show-aside {
transform: translate3d(0px, 0px, 0px);
}
}
.aside-content-reveal {
box-shadow: $aside-shadow;
}
// Aside Overlay
// --------------------------------------------------
// The aside menu slides over the content. The content
// itself, which is under the aside, does not move.
ion-aside[type=overlay] {
z-index: $z-index-aside-overlay;
box-shadow: $aside-shadow;
transform: translate3d(-9999px, 0px, 0px);
backdrop {
display: block;
transform: translate3d(-9999px, 0px, 0px);
opacity: 0.01;
width: 3000px;
&.show-backdrop {
transform: translate3d(0px, 0px, 0px);
}
}
}

View File

@@ -1,124 +1,147 @@
import {Aside} from 'ionic/components/aside/aside';
import {Animtion} from 'ionic/aside/aside';
import {CSS} from 'ionic/util/dom'
import {Aside} from '../aside';
import {Animation} from 'ionic/animations/animation';
// TODO use setters instead of direct dom manipulation
const asideManipulator = {
setSliding(sliding) {
this.aside.getNativeElement().classList[sliding ? 'add' : 'remove']('no-transition');
},
setOpen(open) {
this.aside.getNativeElement().classList[open ? 'add' : 'remove']('open');
},
setTransform(t) {
if(t === null) {
this.aside.getNativeElement().style[CSS.transform] = '';
} else {
this.aside.getNativeElement().style[CSS.transform] = 'translate3d(' + t + 'px,0,0)';
}
}
}
const contentManipulator = {
setSliding(sliding) {
this.aside.contentElement.classList[sliding ? 'add' : 'remove']('no-transition');
},
setOpen(open) {
this.aside.contentElement.classList[open ? 'add' : 'remove'](
`aside-open-${this.aside.side}`
)
},
setTransform(t) {
if(t === null) {
this.aside.contentElement.style[CSS.transform] = '';
} else {
this.aside.contentElement.style[CSS.transform] = 'translate3d(' + t + 'px,0,0)';
}
}
}
const backdropManipulator = {
setSliding(sliding) {
this.aside.backdrop.isTransitioning = sliding;
//.classList[sliding ? 'add' : 'remove']('no-transition');
},
setOpen(open) {
let amt = open ? 0.5 : 0;
this.aside.backdrop.opacity = amt;
},
setTransform(t) {
if(t === null) {
t = this.aside.width();
}
let fade = 0.5 * t / this.aside.width();
this.aside.backdrop.opacity = fade;
}
}
/**
* Aside Type
* Base class which is extended by the various types. Each
* type will provide their own animations for open and close
* and registers itself with Aside.
*/
export class AsideType {
constructor(aside: Aside) {
this.aside = aside;
setTimeout(() => {
aside.contentElement.classList.add('aside-content')
})
this.open = new Animation();
this.close = new Animation();
}
setOpen(shouldOpen) {
return new Promise(resolve => {
if (shouldOpen) {
this.open.playbackRate(1).onFinish(resolve, true).play();
} else {
this.close.playbackRate(1).onFinish(resolve, true).play();
}
});
}
setProgressStart(isOpen) {
this.isOpening = !isOpen;
this.seek && this.seek.dispose();
// clone the correct animation depending on open/close
if (this.isOpening) {
this.seek = this.open.clone();
} else {
this.seek = this.close.clone();
}
// the cloned animation should not use an easing curve during seek
this.seek.easing('linear').progressStart();
}
setProgess(value) {
// adjust progress value depending if it opening or closing
if (!this.isOpening) {
value = 1 - value;
}
this.seek.progress(value);
}
setProgressFinish(shouldComplete) {
let resolve;
let promise = new Promise(res => { resolve = res });
let isOpen = (this.isOpening && shouldComplete);
if (!this.isOpening && !shouldComplete) {
isOpen = true;
}
this.seek.progressFinish(shouldComplete).then(() => {
this.isOpening = false;
resolve(isOpen);
});
return promise;
}
onDestory() {
this.open && this.open.dispose();
this.close && this.close.dispose();
this.seek && this.seek.dispose();
}
}
export class AsideTypeOverlay extends AsideType {
setSliding(sliding) {
asideManipulator.setSliding.call(this, sliding);
backdropManipulator.setSliding.call(this, sliding);
}
setOpen(open) {
asideManipulator.setOpen.call(this, open);
backdropManipulator.setOpen.call(this, open);
}
setTransform(t) {
asideManipulator.setTransform.call(this, t);
backdropManipulator.setTransform.call(this, t);
}
setDoneTransforming(willOpen) {
asideManipulator.setTransform.call(this, null);
backdropManipulator.setTransform.call(this, null);
asideManipulator.setOpen.call(this, willOpen);
backdropManipulator.setOpen.call(this, willOpen);
}
}
export class AsideTypePush extends AsideType {
setSliding(sliding) {
asideManipulator.setSliding.call(this, sliding);
contentManipulator.setSliding.call(this, sliding);
}
setOpen(open) {
asideManipulator.setOpen.call(this, open);
contentManipulator.setOpen.call(this, open);
}
setTransform(t) {
asideManipulator.setTransform.call(this, t);
contentManipulator.setTransform.call(this, t);
}
setDoneTransforming(willOpen) {
asideManipulator.setOpen.call(this, willOpen);
asideManipulator.setTransform.call(this, null);
contentManipulator.setOpen.call(this, willOpen);
contentManipulator.setTransform.call(this, null);
}
}
/**
* Aside Reveal Type
* The content slides over to reveal the aside underneath.
* The aside menu itself, which is under the content, does not move.
*/
class AsideRevealType extends AsideType {
constructor(aside) {
super();
export class AsideTypeReveal extends AsideType {
setSliding(sliding) {
contentManipulator.setSliding.call(this, sliding);
}
setOpen(sliding) {
asideManipulator.setOpen.call(this, sliding);
contentManipulator.setOpen.call(this, sliding);
}
setTransform(t) {
contentManipulator.setTransform.call(this, t);
}
setDoneTransforming(willOpen) {
contentManipulator.setOpen.call(this, willOpen);
contentManipulator.setTransform.call(this, null);
let easing = 'ease';
let duration = 250;
let openedX = (aside.width() * (aside.side == 'right' ? -1 : 1)) + 'px';
this.open.easing(easing).duration(duration);
this.close.easing(easing).duration(duration);
let contentOpen = new Animation(aside.getContentElement());
contentOpen.fromTo(TRANSLATE_X, CENTER, openedX);
this.open.add(contentOpen);
let contentClose = new Animation(aside.getContentElement());
contentClose.fromTo(TRANSLATE_X, openedX, CENTER);
this.close.add(contentClose);
}
}
Aside.register('reveal', AsideRevealType);
/**
* Aside Overlay Type
* The aside menu slides over the content. The content
* itself, which is under the aside, does not move.
*/
class AsideOverlayType extends AsideType {
constructor(aside) {
super();
let easing = 'ease';
let duration = 250;
let backdropOpacity = 0.5;
let closedX = (aside.width() * (aside.side == 'right' ? 1 : -1)) + 'px';
this.open.easing(easing).duration(duration);
this.close.easing(easing).duration(duration);
let asideOpen = new Animation(aside.getAsideElement());
asideOpen.fromTo(TRANSLATE_X, closedX, CENTER);
this.open.add(asideOpen);
let backdropOpen = new Animation(aside.getBackdropElement());
backdropOpen.fromTo(OPACITY, 0.01, backdropOpacity);
this.open.add(backdropOpen);
let asideClose = new Animation(aside.getAsideElement());
asideClose.fromTo(TRANSLATE_X, CENTER, closedX);
this.close.add(asideClose);
let backdropClose = new Animation(aside.getBackdropElement());
backdropClose.fromTo(OPACITY, backdropOpacity, 0.01);
this.close.add(backdropClose);
}
}
Aside.register('overlay', AsideOverlayType);
const OPACITY = 'opacity';
const TRANSLATE_X = 'translateX';
const CENTER = '0px';

View File

@@ -20,6 +20,6 @@
<button id="e2eContentToggleAside" aside-toggle>Toggle Aside</button>
</p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
</ion-content>

View File

@@ -0,0 +1,27 @@
import {App, IonicApp, IonicView} from 'ionic/ionic';
@IonicView({templateUrl: 'page1.html'})
class Page1 {}
@App({
templateUrl: 'main.html'
})
class E2EApp {
constructor(app: IonicApp) {
this.app = app;
this.rootView = Page1;
}
openPage(aside, page) {
// close the menu when clicking a link from the aside
aside.close();
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
let nav = this.app.getComponent('nav');
nav.setRoot(page.component);
}
}

View File

@@ -0,0 +1,41 @@
<ion-aside [content]="content" id="leftMenu" type="overlay" side="left">
<ion-toolbar secondary>
<ion-title>Left Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item aside-toggle="leftMenu">
Close Left Menu
</button>
</ion-list>
</ion-content>
</ion-aside>
<!-- <ion-aside [content]="content" id="rightMenu" type="reveal" side="right">
<ion-toolbar secondary>
<ion-title>Right Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item aside-toggle="rightMenu">
Close Right Menu
</button>
</ion-list>
</ion-content>
</ion-aside> -->
<ion-nav id="nav" [root]="rootView" #content swipe-back-enabled="false"></ion-nav>

View File

@@ -0,0 +1,29 @@
<ion-navbar *navbar>
<button aside-toggle="leftMenu">
<icon menu></icon>
</button>
<ion-title>
Overlay Aside
</ion-title>
</ion-navbar>
<ion-content #content padding>
<h3>Content</h3>
<p>
<button aside-toggle="leftMenu">Toggle Left Aside</button>
</p>
<p>
<button aside-toggle="rightMenu">Toggle Right Aside</button>
</p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
</ion-content>

View File

@@ -0,0 +1,27 @@
import {App, IonicApp, IonicView} from 'ionic/ionic';
@IonicView({templateUrl: 'page1.html'})
class Page1 {}
@App({
templateUrl: 'main.html'
})
class E2EApp {
constructor(app: IonicApp) {
this.app = app;
this.rootView = Page1;
}
openPage(aside, page) {
// close the menu when clicking a link from the aside
aside.close();
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
let nav = this.app.getComponent('nav');
nav.setRoot(page.component);
}
}

View File

@@ -0,0 +1,41 @@
<ion-aside [content]="content" id="leftMenu" type="reveal" side="left">
<ion-toolbar secondary>
<ion-title>Left Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item aside-toggle="leftMenu">
Close Left Menu
</button>
</ion-list>
</ion-content>
</ion-aside>
<!-- <ion-aside [content]="content" id="rightMenu" type="reveal" side="right">
<ion-toolbar secondary>
<ion-title>Right Menu</ion-title>
</ion-toolbar>
<ion-content>
<ion-list>
<button ion-item aside-toggle="rightMenu">
Close Right Menu
</button>
</ion-list>
</ion-content>
</ion-aside> -->
<ion-nav id="nav" [root]="rootView" #content swipe-back-enabled="false"></ion-nav>

View File

@@ -0,0 +1,29 @@
<ion-navbar *navbar>
<button aside-toggle="leftMenu">
<icon menu></icon>
</button>
<ion-title>
Reveal Aside
</ion-title>
</ion-navbar>
<ion-content #content padding>
<h3>Content</h3>
<p>
<button aside-toggle="leftMenu">Toggle Left Aside</button>
</p>
<p>
<button aside-toggle="rightMenu">Toggle Right Aside</button>
</p>
<f></f><f></f><f></f><f></f><f></f><f></f><f></f><f></f>
</ion-content>

View File

@@ -1,4 +1,5 @@
import {IonicConfig} from '../config/config';
import {Platform} from '../platform/platform';
import * as util from 'ionic/util';
@@ -76,11 +77,11 @@ export class Ion {
}
width() {
return this.getNativeElement().offsetWidth;
return Platform.getDimensions(this).w;
}
height() {
return this.getNativeElement().offsetHeight;
return Platform.getDimensions(this).h;
}
}

View File

@@ -45,7 +45,7 @@ export class SwipeHandle {
self.onDragHorizontal(ev);
}
gesture.on('panend', gestureEv => { self.onDragEnd(gestureEv.gesture); });
gesture.on('panend', gesture => { self.onDragEnd(gesture); });
gesture.on('panleft', dragHorizontal);
gesture.on('panright', dragHorizontal);
});
@@ -85,16 +85,14 @@ export class SwipeHandle {
}
this.zone.run(() => {
this.viewCtrl.swipeBackEnd(completeSwipeBack, progress, playbackRate);
this.viewCtrl.swipeBackFinish(completeSwipeBack, playbackRate);
});
this.startX = null;
}
onDragHorizontal(gestureEv) {
onDragHorizontal(gesture) {
this.zone.run(() => {
let gesture = gestureEv.gesture;
if (this.startX === null) {
// starting drag
gesture.srcEvent.preventDefault();

View File

@@ -338,6 +338,8 @@ export class ViewController extends Ion {
enteringItem.shouldCache = false;
enteringItem.willEnter();
this.app.setTransitioning(true);
// wait for the new item to complete setup
enteringItem.stage(() => {
@@ -348,8 +350,7 @@ export class ViewController extends Ion {
// init the transition animation
this.sbTransition = Transition.create(this, opts);
this.sbTransition.easing('linear');
this.sbTransition.stage();
this.sbTransition.easing('linear').progressStart();
let swipeBackPromise = new Promise(res => { this.sbResolve = res; });
@@ -408,27 +409,16 @@ export class ViewController extends Ion {
* @param {TODO} progress TODO
* @param {TODO} playbackRate TODO
*/
swipeBackEnd(completeSwipeBack, progress, playbackRate) {
swipeBackFinish(completeSwipeBack, playbackRate) {
// to reverse the animation use a negative playbackRate
if (this.sbTransition && this.sbActive) {
this.sbActive = false;
if (progress <= 0) {
this.swipeBackProgress(0.0001);
} else if (progress >= 1) {
this.swipeBackProgress(0.9999);
}
if (!completeSwipeBack) {
playbackRate = playbackRate * -1;
}
this.sbTransition.playbackRate(playbackRate);
this.sbTransition.play().then(() => {
this.sbTransition.progressFinish(completeSwipeBack, playbackRate).then(() => {
this.sbResolve && this.sbResolve(completeSwipeBack);
this.sbTransition && this.sbTransition.dispose();
this.sbResolve = this.sbTransition = null;
this.app.setTransitioning(false);
});
}
}