mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
feat(aside): reveal/overlay aside using Animation
This commit is contained in:
14
gulpfile.js
14
gulpfile.js
@ -46,7 +46,7 @@ var tscReporter = {
|
||||
};
|
||||
|
||||
var flagConfig = {
|
||||
string: ['port', 'version', 'ngVersion'],
|
||||
string: ['port', 'version', 'ngVersion', 'animations'],
|
||||
alias: {'p': 'port', 'v': 'version', 'a': 'ngVersion'},
|
||||
default: { port: 8000 }
|
||||
};
|
||||
@ -172,11 +172,21 @@ gulp.task('bundle.ionic', ['transpile'], function() {
|
||||
var insert = require('gulp-insert');
|
||||
var concat = require('gulp-concat');
|
||||
|
||||
var prepend = [];
|
||||
|
||||
// force the web animations api polyfill to kick in
|
||||
if (flags.animations == 'polyfill') {
|
||||
prepend.push('window.Element.prototype.animate=undefined;');
|
||||
}
|
||||
|
||||
// prepend correct system paths
|
||||
prepend.push('System.config({ "paths": { "ionic/*": "ionic/*", "rx": "rx" } });');
|
||||
|
||||
return gulp.src([
|
||||
'dist/src/es5/system/ionic/**/*.js'
|
||||
])
|
||||
.pipe(concat('ionic.js'))
|
||||
.pipe(insert.prepend('System.config({ "paths": { "ionic/*": "ionic/*", "rx": "rx" } });\n'))
|
||||
.pipe(insert.prepend(prepend.join('\n')))
|
||||
.pipe(gulp.dest('dist/js/'));
|
||||
//TODO minify + sourcemaps
|
||||
});
|
||||
|
@ -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;
|
||||
reverse() {
|
||||
return this.playbackRate(-1);
|
||||
}
|
||||
return this._fill || (this._parent && this._parent.fill());
|
||||
|
||||
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.ani.playbackRate = self.rate;
|
||||
}
|
||||
|
||||
self.player.onfinish = () => {
|
||||
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.player = null;
|
||||
self.ani = null;
|
||||
|
||||
self.resolve();
|
||||
callback && callback();
|
||||
};
|
||||
}
|
||||
|
||||
return self.promise;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@ -5,152 +5,53 @@
|
||||
$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-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;
|
||||
}
|
||||
}
|
||||
|
||||
ion-aside[side=right] {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
&[type=overlay] {
|
||||
z-index: $z-index-aside-overlay;
|
||||
|
||||
&:not(.open):not(.changing) {
|
||||
ion-aside backdrop {
|
||||
z-index: -1;
|
||||
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;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
background-color: rgb(0,0,0);
|
||||
}
|
||||
|
||||
.aside-content {
|
||||
transition: $aside-transition;
|
||||
transform: translate3d(0,0,0);
|
||||
|
||||
box-shadow: $aside-shadow;
|
||||
&.aside-open-left {
|
||||
transform: translate3d($aside-width,0,0);
|
||||
pointer-events: none;
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
}
|
||||
|
||||
&.aside-open-right {
|
||||
transform: translate3d(-$aside-width,0,0);
|
||||
.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.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);
|
||||
|
||||
let self = this;
|
||||
this.onContentClick = function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
self.close();
|
||||
};
|
||||
}
|
||||
|
||||
_initGesture() {
|
||||
switch(this.side) {
|
||||
case 'right':
|
||||
this._gesture = new gestures.RightAsideGesture(this);
|
||||
break;
|
||||
|
||||
this.gestureDelegate = this.getDelegate('gesture');
|
||||
this.typeDelegate = this.getDelegate('type');
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
this.contentElement.removeEventListener('click', this.contentClickFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ion-aside-backdrop',
|
||||
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
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
export class AsideTypeReveal extends AsideType {
|
||||
setSliding(sliding) {
|
||||
contentManipulator.setSliding.call(this, sliding);
|
||||
// the cloned animation should not use an easing curve during seek
|
||||
this.seek.easing('linear').progressStart();
|
||||
}
|
||||
setOpen(sliding) {
|
||||
asideManipulator.setOpen.call(this, sliding);
|
||||
contentManipulator.setOpen.call(this, sliding);
|
||||
|
||||
setProgess(value) {
|
||||
// adjust progress value depending if it opening or closing
|
||||
if (!this.isOpening) {
|
||||
value = 1 - value;
|
||||
}
|
||||
setTransform(t) {
|
||||
contentManipulator.setTransform.call(this, t);
|
||||
this.seek.progress(value);
|
||||
}
|
||||
setDoneTransforming(willOpen) {
|
||||
contentManipulator.setOpen.call(this, willOpen);
|
||||
contentManipulator.setTransform.call(this, null);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
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';
|
||||
|
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) {
|
||||
|
@ -127,9 +127,13 @@
|
||||
if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
|
||||
return;
|
||||
}
|
||||
if (property == 'playbackRate' && timingInput[property] !== 1 && shared.isDeprecated('AnimationEffectTiming.playbackRate', '2014-11-28', 'Use Animation.playbackRate instead.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IONIC HACK
|
||||
// NATIVE CHROME STILL USES THIS, SO DON'T HAVE THE POLYFILL THROW ERRORS
|
||||
// if (property == 'playbackRate' && timingInput[property] !== 1 && shared.isDeprecated('AnimationEffectTiming.playbackRate', '2014-11-28', 'Use Animation.playbackRate instead.')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
timing[property] = timingInput[property];
|
||||
}
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user