+
+ Hello ...
+ ...
+ ...
+ ...
+ ...
+ ...
+ Side menu!
+
+
+
+
+
@@ -11,3 +22,11 @@
I'm a modal!
-->
+
+
+
+
diff --git a/playground/basic-example/main.js b/playground/basic-example/main.js
index d2ee150ae7..71095b590f 100644
--- a/playground/basic-example/main.js
+++ b/playground/basic-example/main.js
@@ -1,16 +1,16 @@
import {bootstrap} from 'angular2/core';
import {Component, Template} from 'angular2/angular2';
-import {Tabbar} from 'ionic/components/tabbar/tabbar';
import {Modal} from 'ionic/components/modal/modal';
import {SideMenu, SideMenuParent} from 'ionic/components/sidemenu/sidemenu';
import {Switch} from 'ionic/components/switch/switch';
+import {SideMenu, SideMenuParent} from 'ionic/components';
-import 'ionic/components/tabbar/mixins/android/android-tabbar';
+// import 'ionic/components/tabbar/mixins/android/android-tabbar';
@Component({ selector: '[playground-main]' })
@Template({
url: 'main.html',
- directives: [Tabbar, Modal, SideMenu, SideMenuParent, Switch]
+ directives: [SideMenu, SideMenuParent]
})
class PlaygroundMain {
constructor() {
diff --git a/src/components.js b/src/components.js
new file mode 100644
index 0000000000..1c87558555
--- /dev/null
+++ b/src/components.js
@@ -0,0 +1,3 @@
+export * from './components/sidemenu/sidemenu';
+
+import './components/sidemenu/behaviors/direction/direction';
diff --git a/src/components/ion.js b/src/components/ion.js
index ad1714d93f..6ef2059c07 100644
--- a/src/components/ion.js
+++ b/src/components/ion.js
@@ -1,31 +1,12 @@
import * as Platform from '../platform';
+import * as util from '../util';
var ILLEGAL_ASSIGN_FIELDS = {};
export class Ion {
- constructor() {
- var platformName = Platform.getPlatform();
- var platformConfig = this.$config._platforms[platformName];
- if (platformConfig) {
- for (var i = 0, ii = platformConfig._mixins.length; i < ii; i++) {
- platformConfig._mixins[i](this);
- }
- }
- }
-
- extend() {
- for (var i = 0, ii = arguments.length; i < ii; i++) {
- var obj = arguments[i];
- if (obj) {
- var keys = Object.keys(obj);
- for (var j = 0, jj = keys.length; j < jj; j++) {
- var key = keys[j];
- if (!ILLEGAL_ASSIGN_FIELDS[key]) {
- this[key] = obj[key];
- }
- }
- }
- }
+ extend(...args) {
+ args.unshift(this);
+ return util.extend.apply(null, args);
}
}
diff --git a/src/components/sidemenu/behaviors/direction/direction.js b/src/components/sidemenu/behaviors/direction/direction.js
new file mode 100644
index 0000000000..fc7e8838cb
--- /dev/null
+++ b/src/components/sidemenu/behaviors/direction/direction.js
@@ -0,0 +1,104 @@
+import {sideMenuConfig} from '../../sidemenu';
+import * as util from '../../../../util';
+
+sideMenuConfig.when(instance => instance.side === 'bottom')
+ .mixin(function() {
+
+ this.gesture.options({
+ direction: Hammer.DIRECTION_VERTICAL
+ });
+ this.domElement.classList.add('bottom');
+ util.extend(this.dragMethods, {
+ getMenuStart: (drag, ev) => {
+ return this.isOpen ? -drag.height : 0;
+ },
+ onDrag: (drag, ev) => {
+ drag.pos = util.clamp(
+ 0, -drag.menuStart + drag.pointerStart - ev.center.y, drag.height
+ );
+ this.domElement.style.transform = 'translate3d(0,' +
+ (drag.height - drag.pos) + 'px,0)';
+ },
+ onEnd: (drag, ev) => {
+ this.setOpen(drag.pos > drag.height / 2);
+ this.domElement.style.transform = '';
+ },
+ getEventPos: ev => {
+ return ev.center.y;
+ }
+ });
+
+ });
+
+sideMenuConfig.when(instance => instance.side === 'left')
+ .mixin(function() {
+
+ this.domElement.classList.add('left');
+ util.extend(this.dragMethods, {
+ canStart: (ev) => {
+ return this.isOpen || ev.center.x < this.dragThreshold;
+ },
+ getMenuStart: (drag, ev) => {
+ return this.isOpen ? drag.width : 0;
+ },
+ onDrag: (drag, ev) => {
+ drag.pos = util.clamp(
+ 0, drag.menuStart + ev.center.x - drag.pointerStart, drag.width
+ );
+ this.domElement.style.transform = 'translate3d(' + (-drag.width + drag.pos) + 'px, 0, 0)';
+ },
+ onEnd: (drag, ev) => {
+ this.setOpen(drag.pos > drag.width / 2);
+ this.domElement.style.transform = '';
+ }
+ });
+
+ });
+
+sideMenuConfig.when(instance => instance.side === 'right')
+ .mixin(function() {
+
+ this.domElement.classList.add('right');
+ util.extend(this.dragMethods, {
+ getMenuStart: (drag, ev) => {
+ return this.isOpen ? -drag.width : 0;
+ },
+ onDrag: (drag, ev) => {
+ drag.pos = util.clamp(
+ 0, -drag.menuStart + drag.pointerStart - ev.center.x, drag.height
+ );
+ this.domElement.style.transform = 'translate3d(' +
+ (drag.width - drag.pos) + 'px,0,0)';
+ },
+ onEnd: (drag, ev) => {
+ this.setOpen(drag.pos > drag.width / 2);
+ this.domElement.style.transform = '';
+ }
+ });
+
+ });
+
+sideMenuConfig.when(instance => instance.side === 'top')
+ .mixin(function() {
+
+ this.domElement.classList.add('top');
+ util.extend(this.dragMethods, {
+ getMenuStart: (drag, ev) => {
+ return this.isOpen ? drag.height : 0;
+ },
+ onDrag: (drag, ev) => {
+ drag.pos = util.clamp(
+ 0, drag.menuStart + ev.center.y - drag.pointerStart, drag.height
+ );
+ this.domElement.style.transform = 'translate3d(0, ' +
+ (-drag.height + drag.pos) + 'px, 0)';
+ },
+ onEnd: (drag, ev) => {
+ this.setOpen(drag.pos > drag.height / 2);
+ this.domElement.style.transform = '';
+ },
+ getEventPos: (ev) => {
+ return ev.center.y;
+ }
+ });
+ });
diff --git a/src/components/sidemenu/behaviors/direction/directions.scss b/src/components/sidemenu/behaviors/direction/directions.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/components/sidemenu/sidemenu.js b/src/components/sidemenu/sidemenu.js
index b6452d8ceb..725ed977c5 100644
--- a/src/components/sidemenu/sidemenu.js
+++ b/src/components/sidemenu/sidemenu.js
@@ -1,53 +1,99 @@
import {Component, Template, Inject, Parent, NgElement} from 'angular2/angular2';
import {Ion} from '../ion';
-// import {EdgeDragGesture} from '../../core/gestures/edge-drag-gesture';
+import {IonConfig} from '../../config';
+import {DragGesture} from '../../core/gestures/drag-gesture';
+import * as util from '../../util';
+
+export var sideMenuConfig = new IonConfig();
+
+sideMenuConfig.defaults({
+ side: 'left',
+ dragThreshold: '50'
+});
@Component({
- selector: 'ion-side-menu',
- bind: {
- side: 'side'
- }
+ selector: 'ion-side-menu'
+ // bind: {
+ // side: 'side',
+ // dragThreshold: 'dragThreshold'
+ // },
})
@Template({
inline: ``
})
export class SideMenu extends Ion {
constructor(
- @Parent() sideMenuParent: SideMenuParent,
+ @Parent() sideMenuParent: SideMenuParent,
@NgElement() element: NgElement
) {
- this.el = element;
- // this.gesture = new EdgeDragGesture(sideMenuParent.el.domElement, this);
+ debugger;
+ this.domElement = element.domElement;
+
this._drag = {};
- super();
+
+ this.gesture = new DragGesture(sideMenuParent.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; }
+ };
+ this.gesture.listen();
+
+ this.domElement.addEventListener('transitionend', ev => {
+ this.setChanging(false);
+ })
+
+ sideMenuConfig(this);
}
onDragStart(ev) {
- this._drag = {
- width: this.el.domElement.offsetWidth
- };
- this.el.domElement.classList.add('no-animate');
+ if (!this.dragMethods.canStart(ev)) {
+ return false;
+ }
+
+ this.setChanging(true);
+ this.domElement.classList.add('dragging');
+ requestAnimationFrame(() => {
+ this._drag = {
+ containerWidth: window.innerWidth,
+ containerHeight: window.innerHeight,
+ width: this.domElement.offsetWidth,
+ height: this.domElement.offsetHeight,
+ pointerStart: this.dragMethods.getEventPos(ev)
+ };
+ this._drag.menuStart = this.dragMethods.getMenuStart(this._drag, ev);
+ this._drag.started = true;
+ });
}
onDrag(ev) {
- var pos = this._drag.pos = Math.max(0, Math.min(ev.center.x, this._drag.width));
- this.el.domElement.style.transform = 'translate3d(0,' + pos + 'px,0)';
+ console.log('ondrag');
+ if (!this._drag) return;
+ this.dragMethods.onDrag(this._drag, ev);
}
onDragEnd(ev) {
+ if (!this._drag) return;
var { pos, width } = this._drag;
- this.el.domElement.style.transform = '';
- if (pos < width / 2) {
- this.close();
- } else if (pos > width / 2) {
- this.open();
- }
- this.el.domElement.style.transform = '';
- this.el.domElement.classList.remove('no-animate');
+
+ this.domElement.classList.remove('dragging');
+ this.dragMethods.onEnd(this._drag, ev);
this._drag = null;
}
- open() {
- this.el.domElement.classList.add('open');
+ setChanging(isChanging) {
+ if (isChanging !== this.isChanging) {
+ this.isChanging = isChanging;
+ this.domElement.classList[isChanging ? 'add' : 'remove']('changing');
+ }
}
- close() {
- this.el.domElement.classList.remove('open');
+ setOpen(isOpen) {
+ if (isOpen !== this.isOpen) {
+ this.isOpen = isOpen;
+ this.setChanging(true);
+ requestAnimationFrame(() => {
+ this.domElement.classList[isOpen ? 'add' : 'remove']('open');
+ })
+ }
}
}
@@ -57,9 +103,9 @@ export class SideMenu extends Ion {
@Template({
inline: ''
})
-export class SideMenuParent extends Ion {
+export class SideMenuParent {
constructor(@NgElement() element: NgElement) {
- this.el = element;
+ this.domElement = element.domElement;
super();
}
}
diff --git a/src/components/sidemenu/sidemenu.scss b/src/components/sidemenu/sidemenu.scss
index f1caecff70..353ba21d12 100644
--- a/src/components/sidemenu/sidemenu.scss
+++ b/src/components/sidemenu/sidemenu.scss
@@ -1,20 +1,74 @@
+$side-menu-width: 304px;
+$side-menu-height: 304px;
+
ion-side-menu {
+ display: block;
position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- width: 304px;
background: rgba(255,0,0,0.5);
border-right: 1px solid black;
transition: transform 0.3s;
-
- transform: translate3d(0, -304px, 0);
- &.open {
- transform: translate3d(0,0,0);
+ &:not(.open):not(.changing) {
+ display: none;
}
- &.no-animate {
+ &.dragging {
transition-duration: 0s;
}
+
+ &.left {
+ width: $side-menu-width;
+ left: 0;
+ top: 0;
+ bottom: 0;
+
+ transform: translate3d(-$side-menu-width, 0, 0);
+ &.open {
+ transform: translate3d(0,0,0);
+ }
+ }
+ &.right {
+ width: $side-menu-width;
+ right: 0;
+ top: 0;
+ bottom: 0;
+
+ transform: translate3d($side-menu-width,0,0);
+ &.open {
+ transform: translate3d(0,0,0);
+ }
+ }
+ &.top {
+ height: $side-menu-width;
+ top: 0;
+ left: 0;
+ right: 0;
+
+ transform: translate3d(0,-$side-menu-width,0);
+ &.open {
+ transform: translate3d(0,0,0);
+ }
+ }
+ &.bottom {
+ height: $side-menu-width;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ transform: translate3d(0,$side-menu-width,0);
+ &.open {
+ transform: translate3d(0,0,0);
+ }
+ }
+
+}
+
+ion-side-menu-parent {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: block;
+ overflow:hidden;
}
diff --git a/src/config.js b/src/config.js
index 067ab246ff..be45bfd33a 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,29 +1,42 @@
import * as Platform from './platform';
+import * as util from './util';
-var id = 0;
-export function IonConfigService() {
+export function IonConfig() {
- function Config() {
- this._platforms = {};
- for (var key in Config._platforms) {
- this._platforms[key] = Config._platforms[key]._clone();
+ // TODO automatically add platform class
+ // TODO do bindings/defaults have to be written twice?
+ // TODO maybe add config to IonicComponent annotation
+ function Config(instance) {
+ util.defaults(instance, Config._defaults || {});
+ var conditions = Config._conditions;
+ for (var i = 0, ii = conditions.length; i < ii; i++) {
+ if (conditions[i]._callback(instance)) {
+ for (var j = 0, jj = conditions[i]._mixins.length; j < jj; j++) {
+ conditions[i]._mixins[j].call(instance);
+ }
+ }
}
- this.id = id++;
}
- Config._platforms = {};
- Config.platform = platformFn.bind(Config);
- Config.prototype.platform = platformFn;
- function platformFn(name) {
- return this._platforms[name] || (this._platforms[name] = new SubConfig(name));
- }
+ Config._conditions = [];
+ Config.defaults = function(defaults) {
+ Config._defaults = defaults;
+ };
+ Config.when = function when(callback) {
+ var condition = new ConfigCondition(callback);
+ Config._conditions.push(condition);
+ return condition;
+ };
+ Config.platform = function platform(name) {
+ return Config.when(() => Platform.getPlatform() === name);
+ };
return Config;
}
-class SubConfig {
- constructor(name, mixins = [], template = '') {
- this._name = name;
+class ConfigCondition {
+ constructor(callback, mixins = [], template = '') {
+ this._callback = callback;
this._mixins = mixins;
this._template = template;
}
@@ -35,7 +48,4 @@ class SubConfig {
this._template = url;
return this;
}
- _clone() {
- return new SubConfig(this._name, this._mixins.slice(), this._template);
- }
}
diff --git a/src/core/gestures/drag-gesture.js b/src/core/gestures/drag-gesture.js
index bec31868a8..6283da8a3a 100644
--- a/src/core/gestures/drag-gesture.js
+++ b/src/core/gestures/drag-gesture.js
@@ -1,31 +1,36 @@
import {Gesture} from './gesture';
-var noop = function() {};
+import * as util from '../../util';
-class DragGesture extends Gesture {
- // constructor(element, opts = {}) {
- // super(element, opts);
- // this.onDrag = opts.onDrag;
- // this.onDragStart = opts.onDragStart;
- // this.onDragEnd = opts.onDragEnd;
- // }
- // listen() {
- // super.listen();
- // this.hammertime.on('dragstart', this._onDragStart.bind(this));
- // this.hammertime.on('drag', this._onDrag.bind(this));
- // this.hammertime.on('dragend', this._onDragEnd.bind(this));
- // }
- // unlisten() {
- // super.unlisten();
- // this.hammertime.destroy();
- // }
-
- // _onDragStart(ev) {
- // (this.onDragStart || noop)(ev);
- // }
- // _onDrag(ev) {
- // (this.onDrag || noop)(ev);
- // }
- // _onDragEnd(ev) {
- // (this.onDragEnd || noop)(ev);
- // }
+export class DragGesture extends Gesture {
+ constructor(element, opts = {}) {
+ util.extend(this, {
+ onDrag: opts.onDrag,
+ onDragEnd: opts.onDragEnd,
+ onDragStart: opts.onDragStart
+ });
+ super(element, opts);
+ }
+ listen() {
+ super.listen();
+ console.log('listening');
+ this.hammertime.on('panstart', ev => {
+ console.log('panstart');
+ if (this.onDragStart && this.onDragStart(ev) !== false) {
+ this.dragging = true;
+ }
+ });
+ this.hammertime.on('panmove', ev => {
+ console.log('panmove');
+ if (!this.dragging) return;
+ if (this.onDrag && this.onDrag(ev) === false) {
+ this.dragging = false;
+ }
+ });
+ this.hammertime.on('panend', ev => {
+ console.log('panend');
+ if (!this.dragging) return;
+ this.onDragEnd && this.onDragEnd(ev);
+ this.dragging = false;
+ });
+ }
}
diff --git a/src/core/gestures/edge-drag-gesture.js b/src/core/gestures/edge-drag-gesture.js
deleted file mode 100644
index 6d3efbf055..0000000000
--- a/src/core/gestures/edge-drag-gesture.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import {DragGesture} from './drag-gesture';
-
-class EdgeDragGesture extends DragGesture {
- // constructor(element, opts = { edge = 'left', buffer = 25 } = {}) {
- // super(element, opts);
- // }
-
- // _onDragStart(ev) {
- // var { buffer, edge } = this._options;
- // var { gesture } = ev;
- // if (edge === 'left' && gesture.center.x > buffer) return;
- // if (edge === 'top' && gesture.center.y > buffer) return;
- // if (edge === 'right' && gesture.center.y < window.innerWidth - buffer) return;
- // if (edge === 'bottom' && gesture.center.y < window.innerHeight - buffer) return;
- // super._onDragStart(ev);
- // }
-}
diff --git a/src/core/gestures/gesture.js b/src/core/gestures/gesture.js
index 6d26cfbcae..2aa6e6e4cc 100644
--- a/src/core/gestures/gesture.js
+++ b/src/core/gestures/gesture.js
@@ -1,37 +1,22 @@
+import * as util from '../../util';
export class Gesture {
- // constructor(element, opts = {}) {
- // this.element = element;
- // this._options = opts;
- // }
- // options(opts = {}) {
- // extend(this._options, opts);
- // }
+ constructor(element, opts = {}) {
+ this.element = element;
+ this._options = opts;
+ }
+ options(opts = {}) {
+ util.extend(this._options, opts);
+ }
- // listen() {
- // this.hammertime = Hammer(element, this._options);
- // }
- // unlisten() {
- // this.hammertime.destroy();
- // this.hammertime = null;
- // }
- // destroy() {
- // this.hammertime.destroy();
- // }
-}
-
-// TODO make a utils.js
-function extend() {
- for (var i = 0, ii = arguments.length; i < ii; i++) {
- var obj = arguments[i];
- if (obj) {
- var keys = Object.keys(obj);
- for (var j = 0, jj = keys.length; j < jj; j++) {
- var key = keys[j];
- if (!ILLEGAL_ASSIGN_FIELDS[key]) {
- this[key] = obj[key];
- }
- }
- }
+ listen() {
+ this.hammertime = Hammer(this.element, this._options);
+ }
+ unlisten() {
+ this.hammertime.destroy();
+ this.hammertime = null;
+ }
+ destroy() {
+ this.hammertime.destroy();
}
}
diff --git a/src/core/gestures/slide-gesture.js b/src/core/gestures/slide-gesture.js
new file mode 100644
index 0000000000..e3e17427e7
--- /dev/null
+++ b/src/core/gestures/slide-gesture.js
@@ -0,0 +1,38 @@
+import {DragGesture} from './drag-gesture';
+import * as util from '../../util';
+
+export class SlideGesture extends DragGesture {
+
+ // These getters are overridden by the implementation
+ getSlideRange() {
+ return [0, this.element.offsetWidth];
+ }
+ getEventPos(ev, slideRange) {
+ return -slideRange[1] + n;
+ }
+ getElementPos(ev, slideRange) {
+ return slideRange[0];
+ }
+
+ onDragStart(ev) {
+ var { direction } = this._options.direction;
+ var slideRange = this.getSlideRange(ev);
+ var dragStartPos = this.getEventPos(ev);
+ var elementStartpos = this.getElementPos(ev, distance);
+ this._state = { distance, dragStartPos, elementStartPos };
+
+ return this.onSlideStart && this.onSlideStart(this._state, ev);
+ }
+ onDrag(ev) {
+ var { distance, dragStartPos, elementStartPos } = this._state;
+ var pos = elementStartPos + this.getEventPos(ev) - dragStartPos;
+ this._state.position = util.clamp(slideRange[0], n, slideRange[1]);
+
+ return this.onSlide && this.onSlide(this._state, ev);
+ }
+ onDragEnd() {
+ var ret = this.onSlideEnd && this.onSlideEnd(this._state, ev);
+ this._state = null;
+ return ret;
+ }
+}
diff --git a/src/util.js b/src/util.js
new file mode 100644
index 0000000000..82ff937dea
--- /dev/null
+++ b/src/util.js
@@ -0,0 +1,26 @@
+export function noop() {}
+
+export function extend(dest) {
+ for (var i = 1, ii = arguments.length; i < ii; i++) {
+ var source = arguments[i] || {};
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
+ dest[key] = source[key];
+ }
+ }
+ }
+ return dest;
+}
+
+export function clamp(min, n, max) {
+ return Math.max(min, Math.min(n, max));
+}
+
+export function defaults(obj, src) {
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !obj.hasOwnProperty(key)) {
+ obj[key] = src[key];
+ }
+ }
+ return obj;
+}