aside(): initial types work

This commit is contained in:
Andrew
2015-03-25 15:53:56 -06:00
parent acd6046f63
commit 2561ab1be2
20 changed files with 495 additions and 270 deletions

View File

@ -1,4 +1,5 @@
{
"esnext": true,
"expr": true
"expr": true,
"asi": true
}

View File

@ -55,7 +55,7 @@ gulp.task('clean', function(done) {
del([buildConfig.dist], done);
});
gulp.task('e2e', function() {
gulp.task('e2e', ['sass'], function() {
var e2eSrc = path.join(__dirname, 'src/components/**/test/**/*');
var templateSrc = path.join(__dirname, 'scripts/e2e/index.template.html');
var e2eDest = path.join(__dirname, 'dist/e2e/');
@ -93,15 +93,8 @@ gulp.task('ng2-rename', function(done) {
gulp.task('ng2', ['ng2-rename'], function() {
var builder = new SystemJsBuilder();
builder.config({
traceurOptions: {
'sourceMaps': true,
'annotations': true,
'types': true,
'script': false,
'memberVariables': true,
'modules': 'instantiate'
},
baseURL: 'dist/lib',
traceurOptions: buildConfig.traceurOptions,
map: {
rx: __dirname + '/node_modules/rx'
}

View File

@ -7,5 +7,13 @@ module.exports = {
html: 'src/**/*.html',
scss: 'src/components/**/*.scss',
},
traceurOptions: {
'sourceMaps': true,
'annotations': true,
'types': true,
'script': false,
'memberVariables': true,
'modules': 'instantiate'
},
protractorPort: 8876
};

View File

@ -15,6 +15,7 @@ html {
.pane {
position: relative;
height: 100%;
width: 100%;
@include flex-display();

View File

@ -1,4 +1,8 @@
.notransition {
transition: none !important;
}
.hide.hide.hide {
display: none;
}

View File

@ -24,12 +24,14 @@
@import
"../aside/aside",
"../button/button",
"../content/content",
"../item/item",
"../list/list",
"../modal/modal",
"../switch/switch",
"../tabs/tabs",
"../toolbar/toolbar";
"../toolbar/toolbar",
"../view/view";
// Android Components

View File

@ -0,0 +1,88 @@
import {AsideConfig, Aside} from './aside';
import {SlideEdgeGesture} from '../../core/gestures/slide-edge-gesture';
class AsideGesture extends SlideEdgeGesture {
constructor(aside: Aside) {
this.aside = aside;
this.slideElement = aside.domElement.parentNode;
super(this.slideElement, {
direction: (aside.side === 'left' || aside.side === 'right') ? 'x' : 'y',
edge: aside.side,
threshold: 75
});
this.listen();
}
canStart(ev) {
// Only restrict edges if the aside is closed
return this.aside.isOpen ? true : super.canStart(ev);
}
onSlideBeforeStart(slide, ev) {
console.log('beforestart');
this.aside.setSliding(true);
this.aside.setChanging(true);
return new Promise(resolve => {
requestAnimationFrame(resolve);
});
}
onSlide(slide, ev) {
this.aside.setTransform('translate3d(' + slide.distance + 'px,0,0)');
}
onSlideEnd(slide, ev) {
this.aside.setTransform('');
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
};
}
}
export class LeftAsideGesture extends AsideGesture {}
export class RightAsideGesture extends LeftAsideGesture {
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.aside.domElement.offsetWidth,
max: 0
};
}
}
export class TopAsideGesture extends AsideGesture {
onSlide(slide, ev) {
this.aside.setTransform('translate3d(0,' + slide.distance + 'px,0)');
}
getSlideBoundaries() {
return {
min: 0,
max: this.aside.domElement.offsetHeight
};
}
}
export class BottomAsideGesture extends TopAsideGesture {
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.aside.domElement.offsetHeight,
max: 0
};
}
}

View File

@ -1,158 +1,81 @@
import {Component, Template, Inject, Parent, NgElement} from 'angular2/angular2';
import {Ion} from '../ion';
import {Config} from '../../core/config/config';
import {SlideEdgeGesture} from '../../core/gestures/slide-edge-gesture';
import * as util from '../../util';
import {Component, Template, Inject, Parent, NgElement, PropertySetter} from 'angular2/angular2'
import {ComponentConfig} from '../../core/config/config'
import * as types from './extensions/types/types'
import * as gestures from './extensions/gestures/gestures';
// AsideParent is just a temporary directive
@Component({
selector: 'ion-aside-parent'
})
@Template({
inline: '<content></content>'
})
export class AsideParent {
constructor(@NgElement() element: NgElement) {
this.domElement = element.domElement;
super();
}
}
export let AsideConfig = new ComponentConfig()
AsideConfig.property('side')
.when('left', gestures.LeftAsideGesture)
.when('right', gestures.RightAsideGesture)
.when('top', gestures.TopAsideGesture)
.when('bottom', gestures.BottomAsideGesture)
AsideConfig.property('type')
.when('overlay', types.AsideOverlayType)
.when('push', types.AsidePushType)
.when('reveal', types.AsideRevealType)
@Component({
selector: 'ion-aside',
bind: {
content: 'content',
side: 'side',
dragThreshold: 'dragThreshold'
}
},
services: [AsideConfig]
})
@Template({
inline: `<content></content>`
})
export class Aside {
constructor(
@Parent() asideParent: AsideParent,
@NgElement() element: NgElement
@NgElement() element: NgElement,
@PropertySetter('style.transform') transformSetter: Function,
@PropertySetter('class.notransition') noTransitionSetter: Function,
config: AsideConfig
) {
this.domElement = element.domElement;
this.domElement = element.domElement
this.transformSetter = transformSetter
// this.noTransitionSetter = noTransitionSetter
this.noTransitionSetter = is => { this.domElement.classList[is?'add':'remove']('notransition') }
this.openSetter = is => { this.domElement.classList[is?'add':'remove']('open') }
// TODO inject constant instead of using domElement.getAttribute
// TODO let config / platform handle defaults transparently
let side = this.side = this.domElement.getAttribute('side') || 'left'
let type = this.type = this.domElement.getAttribute('type') || 'overlay'
this.delegates = config.invoke(this, { side, type })
this.domElement.classList.add(side)
this.domElement.addEventListener('transitionend', ev => {
this.setChanging(false);
this.setChanging(false)
})
}
// TODO: remove this. setTimeout has to be done so the bindings can be applied
setTimeout(() => {
let GestureConstructor = {
left: LeftAsideSlideGesture,
top: TopAsideSlideGesture,
bottom: BottomAsideSlideGesture,
right: RightAsideSlideGesture
}[this.side];
this.domElement.classList.add(this.side);
this.gesture = new GestureConstructor(this, asideParent.domElement);
this.gesture.listen();
});
setTransform(transform) {
this.delegates.type.setTransform(transform)
}
setSliding(isSliding) {
if (isSliding !== this.isSliding) {
this.domElement.classList[isSliding ? 'add' : 'remove']('sliding');
this.delegates.type.setSliding(isSliding)
}
}
setChanging(isChanging) {
if (isChanging !== this.isChanging) {
this.isChanging = isChanging;
this.domElement.classList[isChanging ? 'add' : 'remove']('changing');
this.isChanging = isChanging
this.domElement.classList[isChanging ? 'add' : 'remove']('changing')
}
}
setOpen(isOpen) {
if (isOpen !== this.isOpen) {
this.isOpen = isOpen;
this.setChanging(true);
this.isOpen = isOpen
this.setChanging(true)
requestAnimationFrame(() => {
this.domElement.classList[isOpen ? 'add' : 'remove']('open');
this.delegates.type.setOpen(isOpen)
})
}
}
}
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 || */150
});
}
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
};
}
}
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

@ -1,20 +1,21 @@
$aside-width: 304px;
$aside-height: 304px;
$aside-transition: 0.3s linear transform;
ion-aside {
display: block;
position: absolute;
background: rgba(255,0,0,0.5);
border-right: 1px solid black;
background: #eee;
transition: transform 0.3s;
&.type-overlay {
z-index: 10;
}
transition: $aside-transition;
&:not(.open):not(.changing) {
display: none;
}
&.sliding {
transition-duration: 0s;
}
&.left {
width: $aside-width;
@ -23,7 +24,8 @@ ion-aside {
bottom: 0;
transform: translate3d(0, 0, 0);
&.open {
&.open,
&.type-reveal {
transform: translate3d($aside-width,0,0);
}
}
@ -34,7 +36,8 @@ ion-aside {
bottom: 0;
transform: translate3d(0,0,0);
&.open {
&.open,
&.type-reveal {
transform: translate3d(-$aside-width,0,0);
}
}
@ -45,7 +48,8 @@ ion-aside {
right: 0;
transform: translate3d(0,0,0);
&.open {
&.open,
&.type-reveal {
transform: translate3d(0,$aside-width,0);
}
}
@ -56,13 +60,25 @@ ion-aside {
right: 0;
transform: translate3d(0,0,0);
&.open {
&.open,
&.type-reveal {
transform: translate3d(0,-$aside-width,0);
}
}
}
.aside-content {
transition: $aside-transition;
transform: translate3d(0,0,0);
&.aside-open-left {
transform: translate3d($aside-width,0,0);
}
&.aside-open-right {
transform: translate3d(-$aside-width,0,0);
}
}
ion-aside-parent {
position: absolute;
top: 0;

View File

@ -0,0 +1,88 @@
import {Aside} from '../../aside';
import {SlideEdgeGesture} from '../../../../core/gestures/slide-edge-gesture';
class AsideGesture extends SlideEdgeGesture {
constructor(aside: Aside) {
this.aside = aside;
this.slideElement = aside.domElement.parentNode;
super(this.slideElement, {
direction: (aside.side === 'left' || aside.side === 'right') ? 'x' : 'y',
edge: aside.side,
threshold: 75
});
this.listen();
}
canStart(ev) {
// Only restrict edges if the aside is closed
return this.aside.isOpen ? true : super.canStart(ev);
}
onSlideBeforeStart(slide, ev) {
console.log('beforestart');
this.aside.setSliding(true);
this.aside.setChanging(true);
return new Promise(resolve => {
requestAnimationFrame(resolve);
});
}
onSlide(slide, ev) {
this.aside.setTransform('translate3d(' + slide.distance + 'px,0,0)');
}
onSlideEnd(slide, ev) {
this.aside.setTransform('');
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
};
}
}
export class LeftAsideGesture extends AsideGesture {}
export class RightAsideGesture extends LeftAsideGesture {
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.aside.domElement.offsetWidth,
max: 0
};
}
}
export class TopAsideGesture extends AsideGesture {
onSlide(slide, ev) {
this.aside.setTransform('translate3d(0,' + slide.distance + 'px,0)');
}
getSlideBoundaries() {
return {
min: 0,
max: this.aside.domElement.offsetHeight
};
}
}
export class BottomAsideGesture extends TopAsideGesture {
getElementStartPos(slide, ev) {
return this.aside.isOpen ? slide.min : slide.max;
}
getSlideBoundaries() {
return {
min: -this.aside.domElement.offsetHeight,
max: 0
};
}
}

View File

@ -0,0 +1,56 @@
import {Aside} from '../../aside';
// TODO figure out if we can make all of these bindings (eg content's transform is bound to
// aside component's `transform` propety
class AsideBaseType {
constructor(aside: Aside) {
this.aside = aside;
// TODO make this a setter
aside.domElement.classList.add(`type-${aside.type}`);
//FIXME(ajoslin): have to wait for for bindings to apply in a component
setTimeout(() => {
this.aside.content.setIsAside(true);
})
}
setSliding(isSliding) {
this.aside.noTransitionSetter(isSliding);
}
setOpen(isOpen) {
this.aside.openSetter(isOpen);
}
setTransform(transform) {
this.aside.transformSetter(transform);
}
}
export class AsideOverlayType extends AsideBaseType {}
export class AsidePushType extends AsideBaseType {
setSliding(isSliding) {
super.setSliding(isSliding);
this.aside.content.noTransitionSetter(isSliding);
}
setOpen(isOpen) {
super.setOpen(isOpen);
this.aside.content.asideOpenSetter(isOpen, this.aside.side);
}
setTransform(transform) {
super.setTransform(transform);
this.aside.content.transformSetter(transform);
}
}
export class AsideRevealType extends AsideBaseType {
setSliding(isSliding) {
this.aside.content.noTransitionSetter(isSliding);
}
setOpen(isOpen) {
super.setOpen(isOpen);
this.aside.content.asideOpenSetter(isOpen, this.aside.side);
}
setTransform(transform) {
this.aside.content.transformSetter(transform);
}
}

View File

@ -1,2 +1 @@
<aside-app>
</aside-app>
<aside-app style="position:absolute; left:0;top:0;right:0;bottom:0;"></aside-app>

View File

@ -0,0 +1,21 @@
<ion-view>
<ion-aside side="left" [content]="content" type="overlay" style="text-align:center;">
Overlay!
<p>...</p>
<p>...</p>
<p>...</p>
This is android-ey.
</ion-aside>
<ion-content #content>
HELLO, content!
</ion-content>
<ion-aside side="right" [content]="content" type="reveal" style="text-align:center;">
Reveal!
<p>...</p>
<p>...</p>
<p>...</p>
This is iOS-ey.
</ion-aside>
</ion-view>

View File

@ -1,50 +1,16 @@
import {ionicComponents} from 'ionic2/components';
import {Aside} from 'ionic2/components/aside/aside';
import {Content} from 'ionic2/components/content/content';
import {View} from 'ionic2/components/view/view';
import {Template, Component, bootstrap} from 'angular2/angular2';
@Component({
selector: 'aside-app'
})
@Template({
directives: ionicComponents,
inline: `<ion-aside-parent>
<ion-aside side="left">
LEFT
<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>
Side menu!
</ion-aside>
</ion-aside-parent>
<div class="content">
<button class="button" (click)="openLeft()">
Open Left Menu
</button>
</div>`
directives: [Aside, Content, View],
url: 'main.html'
})
class AsideApp {
openLeft() {
}
}
bootstrap(AsideApp);

View File

@ -1,6 +1,4 @@
import {NgElement, Component, Template} from 'angular2/angular2';
import {Ion} from '../ion';
import {NgElement, Component, Template, PropertySetter} from 'angular2/angular2';
@Component({
selector: 'ion-content'
@ -11,8 +9,23 @@ import {Ion} from '../ion';
<content></content>
</div>`
})
export class Content extends Ion {
constructor(@NgElement() ele:NgElement) {
ele.domElement.classList.add('content');
export class Content {
constructor(
@NgElement() ele:NgElement,
@PropertySetter('style.transform') transformSetter: Function,
@PropertySetter('class.notransition') noTransitionSetter: Function
) {
this.domElement = ele.domElement;
this.domElement.classList.add('content');
this.noTransitionSetter = is => { this.domElement.classList[is?'add':'remove']('notransition'); }
// this.noTransitionSetter = noTransitionSetter;
this.transformSetter = transformSetter;
this.setIsAside = is => { this.domElement.classList.add('aside-content'); };
this.asideOpenSetter = (is, side) => {
this.domElement.classList[is?'add':'remove'](`aside-open-${side}`);
}
}
}

View File

@ -0,0 +1,5 @@
.content {
flex: 1;
background-color: white;
z-index: 1;
}

View File

@ -1,5 +1,4 @@
import {NgElement, Component, Template} from 'angular2/angular2'
import {Ion} from '../ion'
@Component({
selector: 'ion-toolbar',

View File

@ -1,6 +1,5 @@
import {NgElement, Component, Template} from 'angular2/angular2'
import {Toolbar} from '../toolbar/toolbar'
import {Ion} from '../ion'
@Component({
selector: 'ion-view',

View File

@ -0,0 +1,3 @@
.pane-container {
flex: 1;
}

View File

@ -1,7 +1,47 @@
import {platform} from '../platform/platform';
import {ConfigCase} from './config-case';
// import {ConfigCase} from './config-case';
import * as util from '../../util';
/*
let MyConfig = new ComponentConfig();
MyConfig.property('side')
.when('left', LeftSlideGesture)
.when('right', RightSlideGesture)
.when('top', TopSlideGesture)
.when('bottom', BottomSlideGesture)
*/
export function ComponentConfig() {
return class Config {
static property(propertyName) {
let self;
return (self = {
when(propertyValue, Class) {
Config.addCase(propertyName, propertyValue, Class);
return self;
}
});
}
static addCase(property, value, Class) {
Config.registry || (Config.registry = {});
(Config.registry[property] || (Config.registry[property] = {}))[value] = Class;
}
invoke(instance, properties = {}) {
let delegates = {};
for (let property in properties) {
let value = properties[property];
let propertyRegistry = Config.registry && Config.registry[property] || {};
if (propertyRegistry[value]) {
delegates[property] = new propertyRegistry[value](instance);
}
}
return delegates;
}
}
}
// TODO stop hardcoding platforms and media sizes
/*
@ -23,86 +63,86 @@ class AsideReveal {
}
*/
export class Config extends ConfigCase {
constructor() {
this._root = this;
this._cases = {};
super({
root: this,
parent: null,
path: ''
});
}
invoke(instance) {
return invokeConfig(this, instance);
}
_addCase(key, baseCase) {
let path = baseCase._path.slice();
path.push(key);
// export class Config extends ConfigCase {
// constructor() {
// this._root = this;
// this._cases = {};
// super({
// root: this,
// parent: null,
// path: ''
// });
// }
// invoke(instance) {
// return invokeConfig(this, instance);
// }
// _addCase(key, baseCase) {
// let path = baseCase._path.slice();
// path.push(key);
// Remove empties & duplicates
path = path
.filter((value, index) => {
return value && path.indexOf(value) === index;
})
.sort();
// // Remove empties & duplicates
// path = path
// .filter((value, index) => {
// return value && path.indexOf(value) === index;
// })
// .sort();
if (path.join(' ') === baseCase._path.join(' ')) {
return baseCase;
}
return this._createCase(path);
}
_createCase(path) {
if (!path.length) return this;
let pathStr = path.join(' ');
let configCase = this._cases[pathStr];
if (!configCase) {
let parentPath = path.slice(0, path.length - 1);
configCase = this._cases[pathStr] = new ConfigCase({
root: this,
parent: this._createCase(parentPath),
path: path
});
}
return configCase;
}
}
// if (path.join(' ') === baseCase._path.join(' ')) {
// return baseCase;
// }
// return this._createCase(path);
// }
// _createCase(path) {
// if (!path.length) return this;
// let pathStr = path.join(' ');
// let configCase = this._cases[pathStr];
// if (!configCase) {
// let parentPath = path.slice(0, path.length - 1);
// configCase = this._cases[pathStr] = new ConfigCase({
// root: this,
// parent: this._createCase(parentPath),
// path: path
// });
// }
// return configCase;
// }
// }
export function invokeConfig(config, object) {
let platformName = platform.get().name;
// export function invokeConfig(config, object) {
// let platformName = platform.get().name;
let passedCases = [config].concat(
Object.keys(config._cases)
.map(name => config._cases[name])
.filter(configCasePasses)
.sort(function(a,b) {
return a._path.length < b._path.length ? -1 : 1;
})
);
// let passedCases = [config].concat(
// Object.keys(config._cases)
// .map(name => config._cases[name])
// .filter(configCasePasses)
// .sort(function(a,b) {
// return a._path.length < b._path.length ? -1 : 1;
// })
// );
// Extend the given object with the values of all the passed cases, starting with the
// most specific.
let defaults = [object];
// Also find the most specific case with a component that we should use.
let ComponentToUse;
for (let i = 0, ii = passedCases.length; i < ii; i++) {
defaults.push(passedCases[i]._values);
if (passedCases[i]._component) {
ComponentToUse = passedCases[i]._component;
}
}
// // Extend the given object with the values of all the passed cases, starting with the
// // most specific.
// let defaults = [object];
// // Also find the most specific case with a component that we should use.
// let ComponentToUse;
// for (let i = 0, ii = passedCases.length; i < ii; i++) {
// defaults.push(passedCases[i]._values);
// if (passedCases[i]._component) {
// ComponentToUse = passedCases[i]._component;
// }
// }
util.defaults.apply(null, defaults);
// util.defaults.apply(null, defaults);
return ComponentToUse;
// return ComponentToUse;
function configCasePasses(configCase) {
let path = configCase._path;
let key;
for (let i = 0, ii = path.length; i < ii; i++) {
if (platformName !== path[i]) return false;
}
return true;
}
// function configCasePasses(configCase) {
// let path = configCase._path;
// let key;
// for (let i = 0, ii = path.length; i < ii; i++) {
// if (platformName !== path[i]) return false;
// }
// return true;
// }
}
// }