diff --git a/package.json b/package.json index 0f228c313f..2079544ff4 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "jasmine-core": "^2.2.0", "karma-chrome-launcher": "^0.1.7", "karma-jasmine": "^0.3.5", - "standard": "driftyco/standard#master", "systemjs": "~0.11.0", "through2": "^0.6.3", "traceur": "0.0.87", diff --git a/playground/basic-example/main.html b/playground/basic-example/main.html index 9eade14eaf..fb5ea450c9 100644 --- a/playground/basic-example/main.html +++ b/playground/basic-example/main.html @@ -1,40 +1,32 @@ - Hello ... -

...

-

...

+ LEFT +

...

+

...

+

...

+ Side menu! +
+ + RIGHT +

...

+

...

+

...

+ Side menu! +
+ + TOP +

...

+

...

+

...

+ Side menu! +
+ + BOTTOM

...

...

...

Side menu!
- - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/components/aside/aside.js b/src/components/aside/aside.js index c755d332c4..1250a9bdc4 100644 --- a/src/components/aside/aside.js +++ b/src/components/aside/aside.js @@ -1,7 +1,7 @@ import {Component, Template, Inject, Parent, NgElement} from 'angular2/angular2'; import {Ion} from '../ion'; import {IonConfig} from '../../config'; -import {DragGesture} from '../../core/gestures/drag-gesture'; +import {SlideEdgeGesture} from '../../core/gestures/slide-edge-gesture'; import * as util from '../../util'; export var asideConfig = new IonConfig('aside'); @@ -31,25 +31,24 @@ export class Aside { 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.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 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) { @@ -83,6 +82,9 @@ export class Aside { this.dragMethods.onEnd(this._drag, ev); this._drag = null; } + setSliding(isSliding) { + this.domElement.classList[isSliding ? 'add' : 'remove']('sliding'); + } setChanging(isChanging) { if (isChanging !== this.isChanging) { this.isChanging = isChanging; @@ -112,3 +114,87 @@ export class AsideParent { 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 + }; + } +} diff --git a/src/components/aside/aside.scss b/src/components/aside/aside.scss index 5689efabfe..2ca4d8f740 100644 --- a/src/components/aside/aside.scss +++ b/src/components/aside/aside.scss @@ -12,52 +12,52 @@ ion-aside { &:not(.open):not(.changing) { display: none; } - &.dragging { + &.sliding { transition-duration: 0s; } &.left { width: $aside-width; - left: 0; + left: -$aside-width; top: 0; bottom: 0; - transform: translate3d(-$aside-width, 0, 0); + transform: translate3d(0, 0, 0); &.open { - transform: translate3d(0,0,0); + transform: translate3d($aside-width,0,0); } } &.right { width: $aside-width; - right: 0; + left: 100%; top: 0; bottom: 0; - transform: translate3d($aside-width,0,0); + transform: translate3d(0,0,0); &.open { - transform: translate3d(0,0,0); + transform: translate3d(-$aside-width,0,0); } } &.top { height: $aside-width; - top: 0; + top: -$aside-width; left: 0; right: 0; - transform: translate3d(0,-$aside-width,0); + transform: translate3d(0,0,0); &.open { - transform: translate3d(0,0,0); + transform: translate3d(0,$aside-width,0); } } &.bottom { height: $aside-width; - bottom: 0; + top: 100%; left: 0; right: 0; - transform: translate3d(0,$aside-width,0); + transform: translate3d(0,0,0); &.open { - transform: translate3d(0,0,0); + transform: translate3d(0,-$aside-width,0); } } diff --git a/src/components/aside/behaviors/direction/direction.js b/src/components/aside/behaviors/direction/direction.js index db3d4592de..e9968d3545 100644 --- a/src/components/aside/behaviors/direction/direction.js +++ b/src/components/aside/behaviors/direction/direction.js @@ -2,6 +2,7 @@ import {asideConfig} from '../../aside'; import Hammer from 'hammer'; import * as util from '../../../../util'; +/* asideConfig .behavior(function() { if (this.side !== 'bottom') return; @@ -116,3 +117,4 @@ asideConfig } }); }); +*/ diff --git a/src/components/ion.js b/src/components/ion.js index 6ef2059c07..50dbd64457 100644 --- a/src/components/ion.js +++ b/src/components/ion.js @@ -1,7 +1,6 @@ import * as Platform from '../platform'; import * as util from '../util'; -var ILLEGAL_ASSIGN_FIELDS = {}; export class Ion { extend(...args) { diff --git a/src/core/gestures/drag-gesture.js b/src/core/gestures/drag-gesture.js index 1ae8729f31..53df2425c7 100644 --- a/src/core/gestures/drag-gesture.js +++ b/src/core/gestures/drag-gesture.js @@ -1,32 +1,36 @@ import {Gesture} from './gesture'; 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 { constructor(element, opts = {}) { - util.extend(this, { - onDrag: opts.onDrag, - onDragEnd: opts.onDragEnd, - onDragStart: opts.onDragStart - }); + util.defaults(opts, {}); super(element, opts); } listen() { super.listen(); this.hammertime.on('panstart', ev => { - if (this.onDragStart && this.onDragStart(ev) !== false) { + if (this.onDragStart(ev) !== false) { this.dragging = true; } }); this.hammertime.on('panmove', ev => { if (!this.dragging) return; - if (this.onDrag && this.onDrag(ev) === false) { + if (this.onDrag(ev) === false) { this.dragging = false; } }); this.hammertime.on('panend', ev => { if (!this.dragging) return; - this.onDragEnd && this.onDragEnd(ev); + this.onDragEnd(ev); this.dragging = false; }); } + onDrag() {} + onDragStart() {} + onDragEnd() {} } diff --git a/src/core/gestures/gesture.js b/src/core/gestures/gesture.js index 2e7deae6dd..05cdc278ee 100644 --- a/src/core/gestures/gesture.js +++ b/src/core/gestures/gesture.js @@ -3,10 +3,16 @@ import Hammer from 'hammer'; export class Gesture { constructor(element, opts = {}) { - util.defaults(opts, { - }); 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; + } options(opts = {}) { util.extend(this._options, opts); @@ -21,5 +27,6 @@ export class Gesture { } destroy() { this.hammertime.destroy(); + this.hammertime = null; } } diff --git a/src/core/gestures/slide-edge-gesture.js b/src/core/gestures/slide-edge-gesture.js new file mode 100644 index 0000000000..9b889ce7e5 --- /dev/null +++ b/src/core/gestures/slide-edge-gesture.js @@ -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; + } + +} diff --git a/src/core/gestures/slide-gesture.js b/src/core/gestures/slide-gesture.js new file mode 100644 index 0000000000..15a38ef088 --- /dev/null +++ b/src/core/gestures/slide-gesture.js @@ -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() {} +}