mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(aside): reveal/overlay aside using Animation
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {CSS} from '../util/dom';
|
||||
import {extend} from '../util/util';
|
||||
|
||||
const RENDER_DELAY = 36;
|
||||
let AnimationRegistry = {};
|
||||
@@ -123,15 +124,15 @@ export class Animation {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
return this._rate || (this._parent && this._parent.playbackRate());
|
||||
return (typeof this._rate !== 'undefined' ? this._rate : this._parent && this._parent.playbackRate());
|
||||
}
|
||||
|
||||
fill(value) {
|
||||
if (arguments.length) {
|
||||
this._fill = value;
|
||||
return this;
|
||||
}
|
||||
return this._fill || (this._parent && this._parent.fill());
|
||||
reverse() {
|
||||
return this.playbackRate(-1);
|
||||
}
|
||||
|
||||
forward() {
|
||||
return this.playbackRate(1);
|
||||
}
|
||||
|
||||
from(property, value) {
|
||||
@@ -193,23 +194,22 @@ export class Animation {
|
||||
|
||||
play() {
|
||||
const self = this;
|
||||
const animations = self._ani;
|
||||
const children = self._chld;
|
||||
let promises = [];
|
||||
let i, l;
|
||||
|
||||
// the actual play() method which may or may not start async
|
||||
function beginPlay() {
|
||||
let i, l;
|
||||
let promises = [];
|
||||
|
||||
for (i = 0, l = children.length; i < l; i++) {
|
||||
promises.push( children[i].play() );
|
||||
for (let i = 0, l = self._chld.length; i < l; i++) {
|
||||
promises.push( self._chld[i].play() );
|
||||
}
|
||||
|
||||
for (i = 0, l = animations.length; i < l; i++) {
|
||||
promises.push( animations[i].play() );
|
||||
}
|
||||
self._ani.forEach(animation => {
|
||||
promises.push(
|
||||
new Promise(resolve => {
|
||||
animation.play(resolve);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
@@ -290,8 +290,7 @@ export class Animation {
|
||||
this._to,
|
||||
this.duration(),
|
||||
this.easing(),
|
||||
this.playbackRate(),
|
||||
this.fill() );
|
||||
this.playbackRate() );
|
||||
|
||||
if (animation.shouldAnimate) {
|
||||
this._ani.push(animation);
|
||||
@@ -310,6 +309,7 @@ export class Animation {
|
||||
// after the RENDER_DELAY
|
||||
// before the animations have started
|
||||
let i;
|
||||
this._isFinished = false;
|
||||
|
||||
for (i = 0; i < this._chld.length; i++) {
|
||||
this._chld[i]._onPlay();
|
||||
@@ -322,7 +322,7 @@ export class Animation {
|
||||
|
||||
_onFinish() {
|
||||
// after the animations have finished
|
||||
if (!this._isFinished) {
|
||||
if (!this._isFinished && !this.isProgress) {
|
||||
this._isFinished = true;
|
||||
|
||||
let i, j, ele;
|
||||
@@ -367,8 +367,6 @@ export class Animation {
|
||||
}
|
||||
|
||||
pause() {
|
||||
this._hasFinished = false;
|
||||
|
||||
let i;
|
||||
for (i = 0; i < this._chld.length; i++) {
|
||||
this._chld[i].pause();
|
||||
@@ -379,34 +377,96 @@ export class Animation {
|
||||
}
|
||||
}
|
||||
|
||||
progressStart() {
|
||||
this.isProgress = true;
|
||||
for (let i = 0; i < this._chld.length; i++) {
|
||||
this._chld[i].progressStart();
|
||||
}
|
||||
|
||||
this.play();
|
||||
this.pause();
|
||||
}
|
||||
|
||||
progress(value) {
|
||||
this.isProgress = true;
|
||||
let i;
|
||||
|
||||
for (i = 0; i < this._chld.length; i++) {
|
||||
this._chld[i].progress(value);
|
||||
}
|
||||
|
||||
if (!this._initProgress) {
|
||||
this._initProgress = true;
|
||||
this.play();
|
||||
this.pause();
|
||||
}
|
||||
|
||||
for (i = 0; i < this._ani.length; i++) {
|
||||
this._ani[i].progress(value);
|
||||
}
|
||||
}
|
||||
|
||||
onReady(fn) {
|
||||
progressFinish(shouldComplete, rate=1) {
|
||||
let promises = [];
|
||||
|
||||
this.isProgress = false;
|
||||
for (let i = 0; i < this._chld.length; i++) {
|
||||
promises.push( this._chld[i].progressFinish(shouldComplete) );
|
||||
}
|
||||
|
||||
this._ani.forEach(animation => {
|
||||
if (shouldComplete) {
|
||||
animation.playbackRate(rate);
|
||||
} else {
|
||||
animation.playbackRate(rate * -1);
|
||||
}
|
||||
|
||||
promises.push(
|
||||
new Promise(resolve => {
|
||||
animation.play(resolve);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
onReady(fn, clear) {
|
||||
if (clear) {
|
||||
this._readys = [];
|
||||
}
|
||||
this._readys.push(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
onPlay(fn) {
|
||||
onPlay(fn, clear) {
|
||||
if (clear) {
|
||||
this._plays = [];
|
||||
}
|
||||
this._plays.push(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
onFinish(fn) {
|
||||
onFinish(fn, clear) {
|
||||
if (clear) {
|
||||
this._finishes = [];
|
||||
}
|
||||
this._finishes.push(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
clone() {
|
||||
|
||||
function copy(dest, src) {
|
||||
// undo what stage() may have already done
|
||||
extend(dest, src);
|
||||
|
||||
dest._isFinished = dest._isStaged = dest.isProgress = false;
|
||||
dest._chld = [];
|
||||
dest._ani = [];
|
||||
|
||||
for (let i = 0; i < src._chld.length; i++) {
|
||||
dest.add( copy(new Animation(), src._chld[i]) );
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
return copy(new Animation(), this);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -444,7 +504,7 @@ export class Animation {
|
||||
|
||||
class Animate {
|
||||
|
||||
constructor(ele, fromEffect, toEffect, duration, easingConfig, playbackRate, fill) {
|
||||
constructor(ele, fromEffect, toEffect, duration, easingConfig, playbackRate) {
|
||||
// https://w3c.github.io/web-animations/
|
||||
// not using the direct API methods because they're still in flux
|
||||
// however, element.animate() seems locked in and uses the latest
|
||||
@@ -462,24 +522,21 @@ class Animate {
|
||||
return inlineStyle(ele, this.toEffect);
|
||||
}
|
||||
|
||||
this.fill = fill;
|
||||
|
||||
this.ele = ele;
|
||||
this.promise = new Promise(res => { this.resolve = res; });
|
||||
|
||||
// stage where the element will start from
|
||||
fromEffect = parseEffect(fromEffect);
|
||||
inlineStyle(ele, fromEffect);
|
||||
this.fromEffect = parseEffect(fromEffect);
|
||||
inlineStyle(ele, this.fromEffect);
|
||||
|
||||
this.duration = duration;
|
||||
this.rate = playbackRate;
|
||||
this.rate = (typeof playbackRate !== 'undefined' ? playbackRate : 1);
|
||||
|
||||
this.easing = easingConfig && easingConfig.name || 'linear';
|
||||
|
||||
this.effects = [ convertProperties(fromEffect) ];
|
||||
this.effects = [ convertProperties(this.fromEffect) ];
|
||||
|
||||
if (this.easing in EASING_FN) {
|
||||
insertEffects(this.effects, fromEffect, this.toEffect, easingConfig);
|
||||
insertEffects(this.effects, this.fromEffect, this.toEffect, easingConfig);
|
||||
|
||||
} else if (this.easing in CUBIC_BEZIERS) {
|
||||
this.easing = 'cubic-bezier(' + CUBIC_BEZIERS[this.easing] + ')';
|
||||
@@ -488,68 +545,72 @@ class Animate {
|
||||
this.effects.push( convertProperties(this.toEffect) );
|
||||
}
|
||||
|
||||
play() {
|
||||
play(callback) {
|
||||
const self = this;
|
||||
|
||||
if (self.player) {
|
||||
self.player.play();
|
||||
if (self.ani) {
|
||||
self.ani.play();
|
||||
|
||||
} else {
|
||||
self.player = self.ele.animate(self.effects, {
|
||||
// https://developers.google.com/web/updates/2014/05/Web-Animations---element-animate-is-now-in-Chrome-36
|
||||
// https://w3c.github.io/web-animations/
|
||||
// Future versions will use "new window.Animation" rather than "element.animate()"
|
||||
|
||||
self.ani = self.ele.animate(self.effects, {
|
||||
duration: self.duration || 0,
|
||||
easing: self.easing,
|
||||
playbackRate: self.rate || 1,
|
||||
fill: self.fill
|
||||
playbackRate: self.rate // old way of setting playbackRate, but still necessary
|
||||
});
|
||||
|
||||
self.player.onfinish = () => {
|
||||
// lock in where the element will stop at
|
||||
// if the playbackRate is negative then it needs to return
|
||||
// to its "from" effects
|
||||
inlineStyle(self.ele, self.rate < 0 ? self.fromEffect : self.toEffect);
|
||||
|
||||
self.player = null;
|
||||
|
||||
self.resolve();
|
||||
};
|
||||
self.ani.playbackRate = self.rate;
|
||||
}
|
||||
|
||||
return self.promise;
|
||||
self.ani.onfinish = () => {
|
||||
// lock in where the element will stop at
|
||||
// if the playbackRate is negative then it needs to return
|
||||
// to its "from" effects
|
||||
inlineStyle(self.ele, self.rate < 0 ? self.fromEffect : self.toEffect);
|
||||
|
||||
self.ani = null;
|
||||
|
||||
callback && callback();
|
||||
};
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.player && this.player.pause();
|
||||
this.ani && this.ani.pause();
|
||||
}
|
||||
|
||||
progress(value) {
|
||||
let player = this.player;
|
||||
let animation = this.ani;
|
||||
|
||||
if (player) {
|
||||
if (animation) {
|
||||
// passed a number between 0 and 1
|
||||
value = Math.max(0, Math.min(1, value));
|
||||
|
||||
if (value >= 1) {
|
||||
player.currentTime = (this.duration * 0.999);
|
||||
return player.play();
|
||||
if (animation.playState !== 'paused') {
|
||||
animation.pause();
|
||||
}
|
||||
|
||||
if (player.playState !== 'paused') {
|
||||
player.pause();
|
||||
if (value < 0.999) {
|
||||
animation.currentTime = (this.duration * value);
|
||||
|
||||
} else {
|
||||
// don't let the progress finish the animation
|
||||
animation.currentTime = (this.duration * 0.999);
|
||||
}
|
||||
|
||||
player.currentTime = (this.duration * value);
|
||||
}
|
||||
}
|
||||
|
||||
playbackRate(value) {
|
||||
this.rate = value;
|
||||
if (this.player) {
|
||||
this.player.playbackRate = value;
|
||||
if (this.ani) {
|
||||
this.ani.playbackRate = value;
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.ele = this.player = this.effects = this.toEffect = null;
|
||||
this.ele = this.ani = this.effects = this.toEffect = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -595,7 +656,7 @@ function parseEffect(inputEffect) {
|
||||
|
||||
for (property in inputEffect) {
|
||||
val = inputEffect[property];
|
||||
r = val.toString().match(/(\d*\.?\d*)(.*)/);
|
||||
r = val.toString().match(/(^-?\d*\.?\d*)(.*)/);
|
||||
num = parseFloat(r[1]);
|
||||
|
||||
outputEffect[property] = {
|
||||
|
||||
@@ -3,6 +3,7 @@ export * from 'ionic/components/app/app'
|
||||
export * from 'ionic/components/app/id'
|
||||
export * from 'ionic/components/action-menu/action-menu'
|
||||
export * from 'ionic/components/aside/aside'
|
||||
export * from 'ionic/components/aside/extensions/types'
|
||||
export * from 'ionic/components/aside/aside-toggle'
|
||||
export * from 'ionic/components/button/button'
|
||||
export * from 'ionic/components/card/card'
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,5 @@ export class AsideToggle {
|
||||
*/
|
||||
toggle(event) {
|
||||
this.aside && this.aside.toggle();
|
||||
console.log('Aside toggle');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
41
ionic/components/aside/extensions/types.scss
Normal file
41
ionic/components/aside/extensions/types.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
27
ionic/components/aside/test/overlay/index.ts
Normal file
27
ionic/components/aside/test/overlay/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
41
ionic/components/aside/test/overlay/main.html
Normal file
41
ionic/components/aside/test/overlay/main.html
Normal 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>
|
||||
29
ionic/components/aside/test/overlay/page1.html
Normal file
29
ionic/components/aside/test/overlay/page1.html
Normal 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>
|
||||
27
ionic/components/aside/test/reveal/index.ts
Normal file
27
ionic/components/aside/test/reveal/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
41
ionic/components/aside/test/reveal/main.html
Normal file
41
ionic/components/aside/test/reveal/main.html
Normal 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>
|
||||
29
ionic/components/aside/test/reveal/page1.html
Normal file
29
ionic/components/aside/test/reveal/page1.html
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"components/toolbar/toolbar",
|
||||
"components/action-menu/action-menu",
|
||||
"components/aside/aside",
|
||||
"components/aside/extensions/types",
|
||||
"components/badge/badge",
|
||||
"components/button/button",
|
||||
"components/button/button-clear",
|
||||
|
||||
@@ -14,6 +14,8 @@ export class PlatformCtrl {
|
||||
this._registry = {};
|
||||
this._default = null;
|
||||
this._onResizes = [];
|
||||
this._dimensions = {};
|
||||
this._dimIds = 0;
|
||||
|
||||
this._readyPromise = new Promise(res => { this._readyResolve = res; } );
|
||||
}
|
||||
@@ -173,11 +175,11 @@ export class PlatformCtrl {
|
||||
}
|
||||
|
||||
winResize() {
|
||||
Platform._w = Platform._h = 0;
|
||||
|
||||
clearTimeout(Platform._resizeTimer);
|
||||
|
||||
Platform._resizeTimer = setTimeout(() => {
|
||||
Platform.flushDimensions();
|
||||
|
||||
for (let i = 0; i < Platform._onResizes.length; i++) {
|
||||
try {
|
||||
Platform._onResizes[i]();
|
||||
@@ -193,6 +195,35 @@ export class PlatformCtrl {
|
||||
this._onResizes.push(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element offsetWidth and offsetHeight. Values are cached to
|
||||
* reduce DOM reads, and reset on a window resize.
|
||||
* @param {TODO} platformConfig TODO
|
||||
*/
|
||||
getDimensions(component) {
|
||||
// cache
|
||||
if (!component._dimId) {
|
||||
component._dimId = ++this._dimIds;
|
||||
}
|
||||
|
||||
let dimensions = this._dimensions[component._dimId];
|
||||
if (!dimensions) {
|
||||
let ele = component.getNativeElement();
|
||||
|
||||
dimensions = this._dimensions[component._dimId] = {
|
||||
w: ele.offsetWidth,
|
||||
h: ele.offsetHeight
|
||||
};
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
flushDimensions() {
|
||||
this._dimensions = {};
|
||||
this._w = this._h = 0;
|
||||
}
|
||||
|
||||
|
||||
// Registry
|
||||
// **********************************************
|
||||
@@ -237,8 +268,8 @@ export class PlatformCtrl {
|
||||
* @returns {boolean} TODO
|
||||
*/
|
||||
testUserAgent(userAgentExpression) {
|
||||
let rx = new RegExp(userAgentExpression, 'i');
|
||||
return rx.test(this._ua);
|
||||
let rgx = new RegExp(userAgentExpression, 'i');
|
||||
return rgx.test(this._ua);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ export class Activator {
|
||||
|
||||
bindDom('touchcancel', function(ev) {
|
||||
self.isTouch = true;
|
||||
self.touchCancel(ev);
|
||||
self.pointerCancel(ev);
|
||||
});
|
||||
|
||||
bindDom('mousedown', function(ev) {
|
||||
|
||||
Reference in New Issue
Block a user