feat(aside): reveal/overlay aside using Animation

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

View File

@ -46,7 +46,7 @@ var tscReporter = {
}; };
var flagConfig = { var flagConfig = {
string: ['port', 'version', 'ngVersion'], string: ['port', 'version', 'ngVersion', 'animations'],
alias: {'p': 'port', 'v': 'version', 'a': 'ngVersion'}, alias: {'p': 'port', 'v': 'version', 'a': 'ngVersion'},
default: { port: 8000 } default: { port: 8000 }
}; };
@ -172,11 +172,21 @@ gulp.task('bundle.ionic', ['transpile'], function() {
var insert = require('gulp-insert'); var insert = require('gulp-insert');
var concat = require('gulp-concat'); 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([ return gulp.src([
'dist/src/es5/system/ionic/**/*.js' 'dist/src/es5/system/ionic/**/*.js'
]) ])
.pipe(concat('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/')); .pipe(gulp.dest('dist/js/'));
//TODO minify + sourcemaps //TODO minify + sourcemaps
}); });

View File

@ -1,4 +1,5 @@
import {CSS} from '../util/dom'; import {CSS} from '../util/dom';
import {extend} from '../util/util';
const RENDER_DELAY = 36; const RENDER_DELAY = 36;
let AnimationRegistry = {}; let AnimationRegistry = {};
@ -123,15 +124,15 @@ export class Animation {
} }
return this; return this;
} }
return this._rate || (this._parent && this._parent.playbackRate()); return (typeof this._rate !== 'undefined' ? this._rate : this._parent && this._parent.playbackRate());
} }
fill(value) { reverse() {
if (arguments.length) { return this.playbackRate(-1);
this._fill = value; }
return this;
} forward() {
return this._fill || (this._parent && this._parent.fill()); return this.playbackRate(1);
} }
from(property, value) { from(property, value) {
@ -193,23 +194,22 @@ export class Animation {
play() { play() {
const self = this; 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 // the actual play() method which may or may not start async
function beginPlay() { function beginPlay() {
let i, l;
let promises = []; let promises = [];
for (i = 0, l = children.length; i < l; i++) { for (let i = 0, l = self._chld.length; i < l; i++) {
promises.push( children[i].play() ); promises.push( self._chld[i].play() );
} }
for (i = 0, l = animations.length; i < l; i++) { self._ani.forEach(animation => {
promises.push( animations[i].play() ); promises.push(
} new Promise(resolve => {
animation.play(resolve);
})
);
});
return Promise.all(promises); return Promise.all(promises);
} }
@ -290,8 +290,7 @@ export class Animation {
this._to, this._to,
this.duration(), this.duration(),
this.easing(), this.easing(),
this.playbackRate(), this.playbackRate() );
this.fill() );
if (animation.shouldAnimate) { if (animation.shouldAnimate) {
this._ani.push(animation); this._ani.push(animation);
@ -310,6 +309,7 @@ export class Animation {
// after the RENDER_DELAY // after the RENDER_DELAY
// before the animations have started // before the animations have started
let i; let i;
this._isFinished = false;
for (i = 0; i < this._chld.length; i++) { for (i = 0; i < this._chld.length; i++) {
this._chld[i]._onPlay(); this._chld[i]._onPlay();
@ -322,7 +322,7 @@ export class Animation {
_onFinish() { _onFinish() {
// after the animations have finished // after the animations have finished
if (!this._isFinished) { if (!this._isFinished && !this.isProgress) {
this._isFinished = true; this._isFinished = true;
let i, j, ele; let i, j, ele;
@ -367,8 +367,6 @@ export class Animation {
} }
pause() { pause() {
this._hasFinished = false;
let i; let i;
for (i = 0; i < this._chld.length; i++) { for (i = 0; i < this._chld.length; i++) {
this._chld[i].pause(); 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) { progress(value) {
this.isProgress = true;
let i; let i;
for (i = 0; i < this._chld.length; i++) { for (i = 0; i < this._chld.length; i++) {
this._chld[i].progress(value); this._chld[i].progress(value);
} }
if (!this._initProgress) {
this._initProgress = true;
this.play();
this.pause();
}
for (i = 0; i < this._ani.length; i++) { for (i = 0; i < this._ani.length; i++) {
this._ani[i].progress(value); 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); this._readys.push(fn);
return this;
} }
onPlay(fn) { onPlay(fn, clear) {
if (clear) {
this._plays = [];
}
this._plays.push(fn); this._plays.push(fn);
return this;
} }
onFinish(fn) { onFinish(fn, clear) {
if (clear) {
this._finishes = [];
}
this._finishes.push(fn); 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() { dispose() {
@ -444,7 +504,7 @@ export class Animation {
class Animate { class Animate {
constructor(ele, fromEffect, toEffect, duration, easingConfig, playbackRate, fill) { constructor(ele, fromEffect, toEffect, duration, easingConfig, playbackRate) {
// https://w3c.github.io/web-animations/ // https://w3c.github.io/web-animations/
// not using the direct API methods because they're still in flux // not using the direct API methods because they're still in flux
// however, element.animate() seems locked in and uses the latest // however, element.animate() seems locked in and uses the latest
@ -462,24 +522,21 @@ class Animate {
return inlineStyle(ele, this.toEffect); return inlineStyle(ele, this.toEffect);
} }
this.fill = fill;
this.ele = ele; this.ele = ele;
this.promise = new Promise(res => { this.resolve = res; });
// stage where the element will start from // stage where the element will start from
fromEffect = parseEffect(fromEffect); this.fromEffect = parseEffect(fromEffect);
inlineStyle(ele, fromEffect); inlineStyle(ele, this.fromEffect);
this.duration = duration; this.duration = duration;
this.rate = playbackRate; this.rate = (typeof playbackRate !== 'undefined' ? playbackRate : 1);
this.easing = easingConfig && easingConfig.name || 'linear'; this.easing = easingConfig && easingConfig.name || 'linear';
this.effects = [ convertProperties(fromEffect) ]; this.effects = [ convertProperties(this.fromEffect) ];
if (this.easing in EASING_FN) { 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) { } else if (this.easing in CUBIC_BEZIERS) {
this.easing = 'cubic-bezier(' + CUBIC_BEZIERS[this.easing] + ')'; this.easing = 'cubic-bezier(' + CUBIC_BEZIERS[this.easing] + ')';
@ -488,68 +545,72 @@ class Animate {
this.effects.push( convertProperties(this.toEffect) ); this.effects.push( convertProperties(this.toEffect) );
} }
play() { play(callback) {
const self = this; const self = this;
if (self.player) { if (self.ani) {
self.player.play(); self.ani.play();
} else { } 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, duration: self.duration || 0,
easing: self.easing, easing: self.easing,
playbackRate: self.rate || 1, playbackRate: self.rate // old way of setting playbackRate, but still necessary
fill: self.fill
}); });
self.ani.playbackRate = self.rate;
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();
};
} }
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() { pause() {
this.player && this.player.pause(); this.ani && this.ani.pause();
} }
progress(value) { progress(value) {
let player = this.player; let animation = this.ani;
if (player) { if (animation) {
// passed a number between 0 and 1 // passed a number between 0 and 1
value = Math.max(0, Math.min(1, value)); value = Math.max(0, Math.min(1, value));
if (value >= 1) { if (animation.playState !== 'paused') {
player.currentTime = (this.duration * 0.999); animation.pause();
return player.play();
} }
if (player.playState !== 'paused') { if (value < 0.999) {
player.pause(); 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) { playbackRate(value) {
this.rate = value; this.rate = value;
if (this.player) { if (this.ani) {
this.player.playbackRate = value; this.ani.playbackRate = value;
} }
} }
dispose() { 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) { for (property in inputEffect) {
val = inputEffect[property]; val = inputEffect[property];
r = val.toString().match(/(\d*\.?\d*)(.*)/); r = val.toString().match(/(^-?\d*\.?\d*)(.*)/);
num = parseFloat(r[1]); num = parseFloat(r[1]);
outputEffect[property] = { outputEffect[property] = {

View File

@ -3,6 +3,7 @@ export * from 'ionic/components/app/app'
export * from 'ionic/components/app/id' export * from 'ionic/components/app/id'
export * from 'ionic/components/action-menu/action-menu' export * from 'ionic/components/action-menu/action-menu'
export * from 'ionic/components/aside/aside' export * from 'ionic/components/aside/aside'
export * from 'ionic/components/aside/extensions/types'
export * from 'ionic/components/aside/aside-toggle' export * from 'ionic/components/aside/aside-toggle'
export * from 'ionic/components/button/button' export * from 'ionic/components/button/button'
export * from 'ionic/components/card/card' export * from 'ionic/components/card/card'

View File

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

View File

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

View File

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

View File

@ -1,13 +1,11 @@
import {forwardRef, Component, Host, View, EventEmitter, ElementRef} from 'angular2/angular2'; import {forwardRef, Directive, Host, View, EventEmitter, ElementRef} from 'angular2/angular2';
import {Ion} from '../ion'; import {Ion} from '../ion';
import {IonicApp} from '../app/app'; import {IonicApp} from '../app/app';
import {IonicConfig} from '../../config/config'; import {IonicConfig} from '../../config/config';
import {IonicComponent} from '../../config/annotations'; import {IonicComponent} from '../../config/annotations';
import * as types from './extensions/types' import * as gestures from './extensions/gestures';
import * as gestures from './extensions/gestures'
import * as util from 'ionic/util/util'
import {dom} from 'ionic/util'
/** /**
* Aside is a side-menu navigation that can be dragged out or toggled to show. Aside supports two * 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', 'side': 'left',
'type': 'reveal' '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'] events: ['opening']
}) })
@View({ @View({
template: '<ng-content></ng-content><ion-aside-backdrop></ion-aside-backdrop>', template: '<ng-content></ng-content><backdrop tappable></backdrop>',
directives: [forwardRef(() => AsideBackdrop)] directives: [forwardRef(() => AsideBackdrop)]
}) })
export class Aside extends Ion { export class Aside extends Ion {
/**
* TODO
* @param {IonicApp} app TODO
* @param {ElementRef} elementRef Reference to the element.
*/
constructor(app: IonicApp, elementRef: ElementRef, config: IonicConfig) { constructor(app: IonicApp, elementRef: ElementRef, config: IonicConfig) {
super(elementRef, config); super(elementRef, config);
this.app = app; this.app = app;
this.opening = new EventEmitter('opening'); this.opening = new EventEmitter('opening');
this.isOpen = false;
//this.animation = new Animation(element.querySelector('backdrop')); this._disableTime = 0;
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);
})
} }
/**
* TODO
*/
onDestroy() {
app.unregister(this);
}
/**
* TODO
*/
onInit() { onInit() {
super.onInit(); super.onInit();
this.contentElement = (this.content instanceof Node) ? this.content : this.content.getNativeElement(); 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 // Auto register
this.app.register('menu', this); this.app.register('menu', this);
} }
if(this.contentElement) { this._initGesture();
this.contentElement.addEventListener('transitionend', ev => { this._initType(this.type);
//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.contentElement.classList.add('aside-content');
this.contentElement.classList.add('aside-content-' + this.type);
this.gestureDelegate = this.getDelegate('gesture'); let self = this;
this.typeDelegate = this.getDelegate('type'); this.onContentClick = function(ev) {
ev.preventDefault();
ev.stopPropagation();
self.close();
};
} }
onDestroy() { _initGesture() {
this.contentElement.removeEventListener('click', this.contentClickFn); switch(this.side) {
} case 'right':
this._gesture = new gestures.RightAsideGesture(this);
break;
/** case 'left':
* TODO this._gesture = new gestures.LeftAsideGesture(this);
* @return {Element} The Aside's content element. break;
*/
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)
} }
} }
/** _initType(type) {
* TODO type = type && type.trim().toLowerCase() || FALLBACK_ASIDE_TYPE;
* @param {boolean} isChanging TODO
*/
setChanging(isChanging) {
// Stop any last changing end operations let asideTypeCls = asideTypes[type];
clearTimeout(this.setChangeTimeout);
if (isChanging !== this.isChanging) {
this.isChanging = isChanging
this.getNativeElement().classList[isChanging ? 'add' : 'remove']('changing');
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. * @param {boolean} isOpen If the Aside is open or not.
* @return {Promise} TODO * @return {Promise} TODO
*/ */
setOpen(isOpen) { setOpen(shouldOpen) {
if (isOpen !== this.isOpen) { // _isDisabled is used to prevent unwanted opening/closing after swiping open/close
this.isOpen = isOpen; // or swiping open the menu while pressing down on the aside-toggle button
this.setChanging(true); if (shouldOpen === this.isOpen || this._isDisabled()) {
return Promise.resolve();
// Set full or closed amount
this.setOpenAmt(isOpen ? 1 : 0);
return dom.rafPromise().then(() => {
this.typeDelegate.setOpen(isOpen)
})
} }
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); 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 * TODO
*/ */
@Component({ @Directive({
selector: 'ion-aside-backdrop', selector: 'backdrop',
host: { host: {
'[style.width]': 'width',
'[style.height]': 'height',
'[style.opacity]': 'opacity',
'(click)': 'clicked($event)' '(click)': 'clicked($event)'
} }
}) })
@View({ class AsideBackdrop {
template: ''
})
export class AsideBackdrop extends Ion {
/** /**
* TODO * TODO
* @param {ElementReg} elementRef TODO
* @param {IonicConfig} config TODO
* @param {Aside} aside TODO * @param {Aside} aside TODO
*/ */
constructor(elementRef: ElementRef, config: IonicConfig, @Host() aside: Aside) { constructor(@Host() aside: Aside, elementRef: ElementRef) {
super(elementRef, config);
aside.backdrop = this;
this.aside = aside; this.aside = aside;
this.elementRef = elementRef;
this.opacity = 0; aside.backdrop = this;
}
/**
* TODO
*/
onInit() {
let ww = window.innerWidth;
let wh = window.innerHeight;
this.width = ww + 'px';
this.height = wh + 'px';
} }
/** /**
* TODO * TODO
* @param {TODO} event TODO * @param {TODO} event TODO
*/ */
clicked(event) { clicked(ev) {
ev.preventDefault();
ev.stopPropagation();
this.aside.close(); this.aside.close();
} }
} }

View File

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

View File

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

View File

@ -1,124 +1,147 @@
import {Aside} from 'ionic/components/aside/aside'; import {Aside} from '../aside';
import {Animtion} from 'ionic/aside/aside'; import {Animation} from 'ionic/animations/animation';
import {CSS} from 'ionic/util/dom'
// 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 { export class AsideType {
constructor(aside: Aside) { constructor(aside: Aside) {
this.aside = aside; this.open = new Animation();
this.close = new Animation();
setTimeout(() => {
aside.contentElement.classList.add('aside-content')
})
} }
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) { * Aside Reveal Type
asideManipulator.setSliding.call(this, sliding); * The content slides over to reveal the aside underneath.
contentManipulator.setSliding.call(this, sliding); * The aside menu itself, which is under the content, does not move.
} */
setOpen(open) { class AsideRevealType extends AsideType {
asideManipulator.setOpen.call(this, open); constructor(aside) {
contentManipulator.setOpen.call(this, open); super();
}
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);
}
}
export class AsideTypeReveal extends AsideType { let easing = 'ease';
setSliding(sliding) { let duration = 250;
contentManipulator.setSliding.call(this, sliding);
} let openedX = (aside.width() * (aside.side == 'right' ? -1 : 1)) + 'px';
setOpen(sliding) {
asideManipulator.setOpen.call(this, sliding); this.open.easing(easing).duration(duration);
contentManipulator.setOpen.call(this, sliding); this.close.easing(easing).duration(duration);
}
setTransform(t) { let contentOpen = new Animation(aside.getContentElement());
contentManipulator.setTransform.call(this, t); contentOpen.fromTo(TRANSLATE_X, CENTER, openedX);
} this.open.add(contentOpen);
setDoneTransforming(willOpen) {
contentManipulator.setOpen.call(this, willOpen); let contentClose = new Animation(aside.getContentElement());
contentManipulator.setTransform.call(this, null); contentClose.fromTo(TRANSLATE_X, openedX, CENTER);
this.close.add(contentClose);
} }
} }
Aside.register('reveal', AsideRevealType);
/**
* Aside Overlay Type
* The aside menu slides over the content. The content
* itself, which is under the aside, does not move.
*/
class AsideOverlayType extends AsideType {
constructor(aside) {
super();
let easing = 'ease';
let duration = 250;
let backdropOpacity = 0.5;
let closedX = (aside.width() * (aside.side == 'right' ? 1 : -1)) + 'px';
this.open.easing(easing).duration(duration);
this.close.easing(easing).duration(duration);
let asideOpen = new Animation(aside.getAsideElement());
asideOpen.fromTo(TRANSLATE_X, closedX, CENTER);
this.open.add(asideOpen);
let backdropOpen = new Animation(aside.getBackdropElement());
backdropOpen.fromTo(OPACITY, 0.01, backdropOpacity);
this.open.add(backdropOpen);
let asideClose = new Animation(aside.getAsideElement());
asideClose.fromTo(TRANSLATE_X, CENTER, closedX);
this.close.add(asideClose);
let backdropClose = new Animation(aside.getBackdropElement());
backdropClose.fromTo(OPACITY, backdropOpacity, 0.01);
this.close.add(backdropClose);
}
}
Aside.register('overlay', AsideOverlayType);
const OPACITY = 'opacity';
const TRANSLATE_X = 'translateX';
const CENTER = '0px';

View File

@ -20,6 +20,6 @@
<button id="e2eContentToggleAside" aside-toggle>Toggle Aside</button> <button id="e2eContentToggleAside" aside-toggle>Toggle Aside</button>
</p> </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> </ion-content>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@
"components/toolbar/toolbar", "components/toolbar/toolbar",
"components/action-menu/action-menu", "components/action-menu/action-menu",
"components/aside/aside", "components/aside/aside",
"components/aside/extensions/types",
"components/badge/badge", "components/badge/badge",
"components/button/button", "components/button/button",
"components/button/button-clear", "components/button/button-clear",

View File

@ -14,6 +14,8 @@ export class PlatformCtrl {
this._registry = {}; this._registry = {};
this._default = null; this._default = null;
this._onResizes = []; this._onResizes = [];
this._dimensions = {};
this._dimIds = 0;
this._readyPromise = new Promise(res => { this._readyResolve = res; } ); this._readyPromise = new Promise(res => { this._readyResolve = res; } );
} }
@ -173,11 +175,11 @@ export class PlatformCtrl {
} }
winResize() { winResize() {
Platform._w = Platform._h = 0;
clearTimeout(Platform._resizeTimer); clearTimeout(Platform._resizeTimer);
Platform._resizeTimer = setTimeout(() => { Platform._resizeTimer = setTimeout(() => {
Platform.flushDimensions();
for (let i = 0; i < Platform._onResizes.length; i++) { for (let i = 0; i < Platform._onResizes.length; i++) {
try { try {
Platform._onResizes[i](); Platform._onResizes[i]();
@ -193,6 +195,35 @@ export class PlatformCtrl {
this._onResizes.push(cb); 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 // Registry
// ********************************************** // **********************************************
@ -237,8 +268,8 @@ export class PlatformCtrl {
* @returns {boolean} TODO * @returns {boolean} TODO
*/ */
testUserAgent(userAgentExpression) { testUserAgent(userAgentExpression) {
let rx = new RegExp(userAgentExpression, 'i'); let rgx = new RegExp(userAgentExpression, 'i');
return rx.test(this._ua); return rgx.test(this._ua);
} }
/** /**

View File

@ -43,7 +43,7 @@ export class Activator {
bindDom('touchcancel', function(ev) { bindDom('touchcancel', function(ev) {
self.isTouch = true; self.isTouch = true;
self.touchCancel(ev); self.pointerCancel(ev);
}); });
bindDom('mousedown', function(ev) { bindDom('mousedown', function(ev) {

View File

@ -127,9 +127,13 @@
if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) { if ((property == 'direction') && (directions.indexOf(timingInput[property]) == -1)) {
return; 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]; 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