SlideGesture

This commit is contained in:
Andrew
2015-03-22 11:05:37 -06:00
parent 4144fc7038
commit 51bbcaea78
10 changed files with 269 additions and 69 deletions

View File

@ -31,7 +31,6 @@
"jasmine-core": "^2.2.0", "jasmine-core": "^2.2.0",
"karma-chrome-launcher": "^0.1.7", "karma-chrome-launcher": "^0.1.7",
"karma-jasmine": "^0.3.5", "karma-jasmine": "^0.3.5",
"standard": "driftyco/standard#master",
"systemjs": "~0.11.0", "systemjs": "~0.11.0",
"through2": "^0.6.3", "through2": "^0.6.3",
"traceur": "0.0.87", "traceur": "0.0.87",

View File

@ -1,40 +1,32 @@
<ion-aside-parent> <ion-aside-parent>
<ion-aside side="left"> <ion-aside side="left">
Hello ... LEFT
<p>...</p> <p>...</p>
<p>...</p> <p>...</p>
<p>...</p>
Side menu!
</ion-aside>
<ion-aside side="right">
RIGHT
<p>...</p>
<p>...</p>
<p>...</p>
Side menu!
</ion-aside>
<ion-aside side="top">
TOP
<p>...</p>
<p>...</p>
<p>...</p>
Side menu!
</ion-aside>
<ion-aside side="bottom">
BOTTOM
<p>...</p> <p>...</p>
<p>...</p> <p>...</p>
<p>...</p> <p>...</p>
Side menu! Side menu!
</ion-aside> </ion-aside>
<!-- <ion-aside side="bottom"> -->
<!-- Hello ... -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- Side menu! -->
<!-- </ion-aside> -->
<!-- <ion-aside side="top"> -->
<!-- Hello ... -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- Side menu! -->
<!-- </ion-aside> -->
<!-- <ion-aside side="left"> -->
<!-- Hello ... -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- <p>...</p> -->
<!-- Side menu! -->
<!-- </ion-aside> -->
<div class="content"> <div class="content">
<button (click)="showModal()">Show Modal</button> <button (click)="showModal()">Show Modal</button>

View File

@ -1,7 +1,7 @@
import {Component, Template, Inject, Parent, NgElement} from 'angular2/angular2'; import {Component, Template, Inject, Parent, NgElement} from 'angular2/angular2';
import {Ion} from '../ion'; import {Ion} from '../ion';
import {IonConfig} from '../../config'; import {IonConfig} from '../../config';
import {DragGesture} from '../../core/gestures/drag-gesture'; import {SlideEdgeGesture} from '../../core/gestures/slide-edge-gesture';
import * as util from '../../util'; import * as util from '../../util';
export var asideConfig = new IonConfig('aside'); export var asideConfig = new IonConfig('aside');
@ -31,25 +31,24 @@ export class Aside {
this._drag = {}; this._drag = {};
this.gesture = new DragGesture(asideParent.domElement, {
onDrag: this.onDrag.bind(this),
onDragStart: this.onDragStart.bind(this),
onDragEnd: this.onDragEnd.bind(this)
});
this.dragMethods = {
getMenuStart() { return 0; },
getEventPos(ev) { return ev.center.x; },
canStart() { return true; }
};
this.gesture.listen();
this.domElement.addEventListener('transitionend', ev => { this.domElement.addEventListener('transitionend', ev => {
this.setChanging(false); this.setChanging(false);
}) })
let gestureConstructor = {
left: LeftAsideSlideGesture,
top: TopAsideSlideGesture,
bottom: BottomAsideSlideGesture,
right: RightAsideSlideGesture
};
// TODO: remove this. setTimeout has to be done so the bindings can be applied // TODO: remove this. setTimeout has to be done so the bindings can be applied
setTimeout(() => { setTimeout(() => {
asideConfig.invoke(this); // asideConfig.invoke(this);
this.domElement.classList.add(this.side);
this.gesture = new gestureConstructor[this.side](this, asideParent.domElement);
this.gesture.listen();
}); });
} }
onDragStart(ev) { onDragStart(ev) {
@ -83,6 +82,9 @@ export class Aside {
this.dragMethods.onEnd(this._drag, ev); this.dragMethods.onEnd(this._drag, ev);
this._drag = null; this._drag = null;
} }
setSliding(isSliding) {
this.domElement.classList[isSliding ? 'add' : 'remove']('sliding');
}
setChanging(isChanging) { setChanging(isChanging) {
if (isChanging !== this.isChanging) { if (isChanging !== this.isChanging) {
this.isChanging = isChanging; this.isChanging = isChanging;
@ -112,3 +114,87 @@ export class AsideParent {
super(); super();
} }
} }
class AsideSlideGesture extends SlideEdgeGesture {
constructor(aside: Aside, slideElement: Element) {
this.aside = aside;
super(slideElement, {
direction: (aside.side === 'left' || aside.side === 'right') ? 'x' : 'y',
edge: aside.side || 'left',
threshold: aside.dragThreshold || 100
});
}
canStart(ev) {
// Only restrict edges if the aside is closed
return this.aside.isOpen ? true : super.canStart(ev);
}
onSlideBeforeStart(slide, ev) {
this.aside.setSliding(true);
this.aside.setChanging(true);
return new Promise(resolve => {
requestAnimationFrame(resolve);
});
}
onSlide(slide, ev) {
this.aside.domElement.style.transform = 'translate3d(' + slide.distance + 'px,0,0)';
}
onSlideEnd(slide, ev) {
this.aside.domElement.style.transform = '';
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);
}
}
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.max : slide.min;
}
getSlideBoundaries() {
return {
min: 0,
max: this.aside.domElement.offsetWidth
};
}
}
class LeftAsideSlideGesture extends AsideSlideGesture {}
class RightAsideSlideGesture extends LeftAsideSlideGesture {
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.aside.domElement.offsetWidth,
max: 0
};
}
}
import Hammer from 'hammer';
class TopAsideSlideGesture extends AsideSlideGesture {
onSlide(slide, ev) {
this.aside.domElement.style.transform = 'translate3d(0,' + slide.distance + 'px,0)';
}
getSlideBoundaries() {
return {
min: 0,
max: this.aside.domElement.offsetHeight
};
}
}
class BottomAsideSlideGesture extends TopAsideSlideGesture {
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.aside.domElement.offsetHeight,
max: 0
};
}
}

View File

@ -12,52 +12,52 @@ ion-aside {
&:not(.open):not(.changing) { &:not(.open):not(.changing) {
display: none; display: none;
} }
&.dragging { &.sliding {
transition-duration: 0s; transition-duration: 0s;
} }
&.left { &.left {
width: $aside-width; width: $aside-width;
left: 0; left: -$aside-width;
top: 0; top: 0;
bottom: 0; bottom: 0;
transform: translate3d(-$aside-width, 0, 0); transform: translate3d(0, 0, 0);
&.open { &.open {
transform: translate3d(0,0,0); transform: translate3d($aside-width,0,0);
} }
} }
&.right { &.right {
width: $aside-width; width: $aside-width;
right: 0; left: 100%;
top: 0; top: 0;
bottom: 0; bottom: 0;
transform: translate3d($aside-width,0,0);
&.open {
transform: translate3d(0,0,0); transform: translate3d(0,0,0);
&.open {
transform: translate3d(-$aside-width,0,0);
} }
} }
&.top { &.top {
height: $aside-width; height: $aside-width;
top: 0; top: -$aside-width;
left: 0; left: 0;
right: 0; right: 0;
transform: translate3d(0,-$aside-width,0);
&.open {
transform: translate3d(0,0,0); transform: translate3d(0,0,0);
&.open {
transform: translate3d(0,$aside-width,0);
} }
} }
&.bottom { &.bottom {
height: $aside-width; height: $aside-width;
bottom: 0; top: 100%;
left: 0; left: 0;
right: 0; right: 0;
transform: translate3d(0,$aside-width,0);
&.open {
transform: translate3d(0,0,0); transform: translate3d(0,0,0);
&.open {
transform: translate3d(0,-$aside-width,0);
} }
} }

View File

@ -2,6 +2,7 @@ import {asideConfig} from '../../aside';
import Hammer from 'hammer'; import Hammer from 'hammer';
import * as util from '../../../../util'; import * as util from '../../../../util';
/*
asideConfig asideConfig
.behavior(function() { .behavior(function() {
if (this.side !== 'bottom') return; if (this.side !== 'bottom') return;
@ -116,3 +117,4 @@ asideConfig
} }
}); });
}); });
*/

View File

@ -1,7 +1,6 @@
import * as Platform from '../platform'; import * as Platform from '../platform';
import * as util from '../util'; import * as util from '../util';
var ILLEGAL_ASSIGN_FIELDS = {};
export class Ion { export class Ion {
extend(...args) { extend(...args) {

View File

@ -1,32 +1,36 @@
import {Gesture} from './gesture'; import {Gesture} from './gesture';
import * as util from '../../util'; import * as util from '../../util';
import Hammer from 'hammer';
/*
* BUG(ajoslin): HammerJS 2.x does not have an alternative to HammerJS 1.x's
* dragLockToAxis, so a vertical and horizontal gesture can happen at the same time.
*/
export class DragGesture extends Gesture { export class DragGesture extends Gesture {
constructor(element, opts = {}) { constructor(element, opts = {}) {
util.extend(this, { util.defaults(opts, {});
onDrag: opts.onDrag,
onDragEnd: opts.onDragEnd,
onDragStart: opts.onDragStart
});
super(element, opts); super(element, opts);
} }
listen() { listen() {
super.listen(); super.listen();
this.hammertime.on('panstart', ev => { this.hammertime.on('panstart', ev => {
if (this.onDragStart && this.onDragStart(ev) !== false) { if (this.onDragStart(ev) !== false) {
this.dragging = true; this.dragging = true;
} }
}); });
this.hammertime.on('panmove', ev => { this.hammertime.on('panmove', ev => {
if (!this.dragging) return; if (!this.dragging) return;
if (this.onDrag && this.onDrag(ev) === false) { if (this.onDrag(ev) === false) {
this.dragging = false; this.dragging = false;
} }
}); });
this.hammertime.on('panend', ev => { this.hammertime.on('panend', ev => {
if (!this.dragging) return; if (!this.dragging) return;
this.onDragEnd && this.onDragEnd(ev); this.onDragEnd(ev);
this.dragging = false; this.dragging = false;
}); });
} }
onDrag() {}
onDragStart() {}
onDragEnd() {}
} }

View File

@ -3,10 +3,16 @@ import Hammer from 'hammer';
export class Gesture { export class Gesture {
constructor(element, opts = {}) { constructor(element, opts = {}) {
util.defaults(opts, {
});
this.element = element; this.element = element;
// Map 'x' or 'y' string to hammerjs opts
this.direction = opts.direction || 'x';
opts.direction = this.direction === 'x' ?
Hammer.DIRECTION_HORIZONTAL :
Hammer.DIRECTION_VERTICAL;
this._options = opts; this._options = opts;
} }
options(opts = {}) { options(opts = {}) {
util.extend(this._options, opts); util.extend(this._options, opts);
@ -21,5 +27,6 @@ export class Gesture {
} }
destroy() { destroy() {
this.hammertime.destroy(); this.hammertime.destroy();
this.hammertime = null;
} }
} }

View File

@ -0,0 +1,40 @@
import {SlideGesture} from './slide-gesture';
import * as util from '../../util';
export class SlideEdgeGesture extends SlideGesture {
constructor(element: Element, opts: Object = {}) {
util.defaults(opts, {
edge: 'left',
threshold: 50
});
// Can check corners through use of eg 'left top'
this.edges = opts.edge.split(' ');
this.threshold = opts.threshold;
super(element, opts);
}
canStart(ev) {
this._containerRect = this.getContainerDimensions();
return this.edges.every(edge => this._checkEdge(edge, ev.center));
}
getContainerDimensions() {
return {
left: 0,
top: 0,
width: window.innerWidth,
height: window.innerHeight
};
}
_checkEdge(edge, pos) {
if ((edge === 'left' && pos.x > this._containerRect.left + this.threshold) ||
(edge === 'right' && pos.x < this._containerRect.width - this.threshold) ||
(edge === 'top' && pos.y > this._containerRect.top + this.threshold) ||
(edge === 'bottom' && pos.y < this._containerRect.height - this.threshold)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,71 @@
import {DragGesture} from './drag-gesture';
import * as util from '../../util';
export class SlideGesture extends DragGesture {
constructor(element, opts = {}) {
this.element = element;
super(element, opts);
}
/*
* Get the min and max for the slide. pageX/pageY.
* Only called on dragstart.
*/
getSlideBoundaries(slide, ev) {
return {
min: 0,
max: this.element.offsetWidth
};
}
/*
* Get the element's pos when the drag starts.
* For example, an open side menu starts at 100% and a closed
* sidemenu starts at 0%.
*/
getElementStartPos(slide, ev) {
return 0;
}
canStart() {
return true;
}
onDragStart(ev) {
if (!this.canStart(ev)) return false;
this.slide = {};
var promise = this.onSlideBeforeStart(this.slide, ev) || Promise.resolve();
promise.then(() => {
var {min, max} = this.getSlideBoundaries(this.slide, ev);
this.slide.min = min;
this.slide.max = max;
this.slide.elementStartPos = this.getElementStartPos(this.slide, ev);
this.slide.pointerStartPos = ev.center[this.direction];
this.slide.started = true;
this.onSlideStart(this.slide, ev);
}).catch(() => {
this.slide = null;
});
}
onDrag(ev) {
if (!this.slide || !this.slide.started) return;
this.slide.pos = ev.center[this.direction];
this.slide.distance = util.clamp(
this.slide.min,
this.slide.pos - this.slide.pointerStartPos + this.slide.elementStartPos,
this.slide.max
);
this.slide.delta = this.slide.pos - this.slide.pointerStartPos;
this.onSlide(this.slide, ev);
}
onDragEnd(ev) {
if (!this.slide || !this.slide.started) return;
this.onSlideEnd(this.slide, ev);
this.slide = null;
}
onSlideBeforeStart() {}
onSlideStart() {}
onSlide() {}
onSlideEnd() {}
}