diff --git a/ionic/components.ts b/ionic/components.ts index 100dee858c..31d369a9dd 100644 --- a/ionic/components.ts +++ b/ionic/components.ts @@ -10,6 +10,7 @@ export * from 'ionic/components/form/input' export * from 'ionic/components/icon/icon' export * from 'ionic/components/item/item' export * from 'ionic/components/item/item-group' +export * from 'ionic/components/item/item-sliding' export * from 'ionic/components/menu/menu' export * from 'ionic/components/menu/menu-types' export * from 'ionic/components/menu/menu-toggle' diff --git a/ionic/components/app/structure.scss b/ionic/components/app/structure.scss index ec12bdc77a..c57ee0fc69 100644 --- a/ionic/components/app/structure.scss +++ b/ionic/components/app/structure.scss @@ -22,6 +22,7 @@ $z-index-backdrop: 1; $z-index-overlay-wrapper: 10; +$z-index-item-options: 1; // Flex Order // -------------------------------------------------- diff --git a/ionic/components/item/item-sliding.ts b/ionic/components/item/item-sliding.ts new file mode 100644 index 0000000000..dda5f995e9 --- /dev/null +++ b/ionic/components/item/item-sliding.ts @@ -0,0 +1,242 @@ +import {Component, Directive, View, ElementRef, NgIf, Host, Optional} from 'angular2/angular2'; + +import {Gesture} from 'ionic/gestures/gesture'; +import {DragGesture} from 'ionic/gestures/drag-gesture'; +import {Hammer} from 'ionic/gestures/hammer'; +import {List} from 'ionic/components/list/list'; + +import * as util from 'ionic/util'; + +import {CSS, raf} from 'ionic/util/dom'; + + +/** + * @name ionItem + * @description + * Creates a list-item that can easily be swiped, + * deleted, reordered, edited, and more. + * + * @usage + * ```html + * + * + * {{item.title}} + * + * {{item.note}} + * + * + * + * ``` + */ +@Component({ + selector: 'ion-item-sliding,[ion-item-sliding]', + host: { + 'class': 'item' + }, + properties: [ + 'sliding' + ] +}) +@View({ + template: + '' + + '' + + '' + + ''+ + '' + + '' + + '' + + '', + directives: [NgIf] +}) +export class ItemSliding { + /** + * TODO + * @param {ElementRef} elementRef A reference to the component's DOM element. + */ + constructor(elementRef: ElementRef, @Optional() @Host() list: List) { + this._isOpen = false; + this._isSlideActive = false; + this._isTransitioning = false; + this._transform = ''; + + this.list = list; + + this.ele = elementRef.nativeElement; + this.swipeButtons = {}; + this.optionButtons = {}; + + } + + onInit() { + this._initSliding(); + } + + _initSliding() { + var itemSlidingContent = this.ele.querySelector('ion-item-sliding-content'); + var itemOptionsContent = this.ele.querySelector('ion-item-options'); + + this.itemSlidingContent = itemSlidingContent; + this.itemOptions = itemOptionsContent; + + this.itemWidth = itemSlidingContent.offsetWidth; + this.itemOptionsWidth = itemOptionsContent && itemOptionsContent.offsetWidth || 0; + + this.openAmount = 0; + + this.gesture = new ItemSlideGesture(this, itemSlidingContent); + } + + close(andStopDrag) { + this.openAmount = 0; + + // Enable it once, it'll get disabled on the next drag + raf(() => { + this.enableAnimation(); + if(this.itemSlidingContent) { + this.itemSlidingContent.style[CSS.transform] = 'translateX(0)'; + } + }); + } + open(amt) { + let el = this.itemSlidingContent; + this.openAmount = amt || 0; + + if(this.list) { + this.list.setOpenItem(this); + } + + if(amt === '') { + el.style[CSS.transform] = ''; + } else { + el.style[CSS.transform] = 'translateX(' + -amt + 'px)'; + } + } + isOpen() { + return this.openAmount > 0; + } + getOpenAmt() { + return this.openAmount; + } + getItemWidth() { + return this.itemWidth; + } + disableAnimation() { + this.itemSlidingContent.style[CSS.transition] = 'none'; + } + enableAnimation() { + // Clear the explicit transition, allow for CSS one to take over + this.itemSlidingContent.style[CSS.transition] = ''; + } + /** + * User did a touchstart + */ + didTouch() { + if(this.isOpen()) { + this.close(); + this.didClose = true; + } else { + if(this.list) { + this.list.closeOpenItem(); + } + } + + } +} + +class ItemSlideGesture extends DragGesture { + constructor(item: ItemSliding, el: Element) { + + super(el, { + direction: 'x', + threshold: el.offsetWidth + }); + + this.el = el; + this.item = item; + this.canDrag = true; + this.listen(); + + this.el.addEventListener('touchstart', (e) => { + this.item.didTouch(); + }) + + this.el.addEventListener('touchend', (e) => { + this.item.didClose = false; + }); + this.el.addEventListener('touchcancel', (e) => { + this.item.didClose = false; + }); + } + + onDragStart(ev) { + if(this.item.didClose) { return; } + + this.slide = {}; + + this.slide.offsetX = this.item.getOpenAmt(); + this.slide.startX = ev.center[this.direction]; + this.slide.started = true; + + this.item.disableAnimation(); + } + + onDrag(ev) { + if (!this.slide || !this.slide.started) return; + + this.slide.x = ev.center[this.direction]; + this.slide.delta = this.slide.x - this.slide.startX; + + let newX = Math.max(0, this.slide.offsetX - this.slide.delta); + + let buttonsWidth = this.item.itemOptionsWidth; + + if(newX > this.item.itemOptionsWidth) { + // Calculate the new X position, capped at the top of the buttons + newX = -Math.min(-buttonsWidth, -buttonsWidth + (((this.slide.delta + buttonsWidth) * 0.4))); + } + + this.item.open(newX); + } + + onDragEnd(ev) { + if (!this.slide || !this.slide.started) return; + + let buttonsWidth = this.item.itemOptionsWidth; + + // If we are currently dragging, we want to snap back into place + // The final resting point X will be the width of the exposed buttons + var restingPoint = this.item.itemOptionsWidth; + + // Check if the drag didn't clear the buttons mid-point + // and we aren't moving fast enough to swipe open + if (this.item.openAmount < (buttonsWidth / 2)) { + + // If we are going left but too slow, or going right, go back to resting + if (ev.direction & Hammer.DIRECTION_RIGHT) { + // Left + restingPoint = 0; + } else if (Math.abs(ev.velocityX) < 0.3) { + // Right + restingPoint = 0; + } + } + + raf(() => { + if (restingPoint === 0) { + // Reset to zero + this.item.open(''); + var buttons = this.item.itemOptions; + clearTimeout(this.hideButtonsTimeout); + this.hideButtonsTimeout = setTimeout(() => { + buttons && buttons.classList.add('invisible'); + }, 250); + } else { + this.item.open(restingPoint); + } + this.item.enableAnimation(); + + this.slide = null; + }); + } +} diff --git a/ionic/components/item/item.scss b/ionic/components/item/item.scss index 95041526d9..ee63b905eb 100644 --- a/ionic/components/item/item.scss +++ b/ionic/components/item/item.scss @@ -137,3 +137,21 @@ ion-input.item { .item.item.item.no-border-bottom + .item { margin-top: -13px; } + +/** + * The hidden right-side buttons that can be exposed under a list item + * with dragging. + */ +ion-item-sliding-content { + display: block; + z-index: $z-index-item-options + 1; + flex: 1; +} +ion-item-options { + display: block; + position: absolute; + top: 0; + right: 0; + z-index: $z-index-item-options; + height: 100%; +} diff --git a/ionic/components/item/item.ts b/ionic/components/item/item.ts index 8bf9875156..53750e6e93 100644 --- a/ionic/components/item/item.ts +++ b/ionic/components/item/item.ts @@ -1,6 +1,6 @@ import {Component, Directive, View, ElementRef, NgIf, ViewQuery, QueryList} from 'angular2/angular2'; -import {dom} from 'ionic/util'; +import * as util from 'ionic/util'; /** @@ -31,6 +31,7 @@ import {dom} from 'ionic/util'; '' + ''+ '' + + '' + '', directives: [NgIf] }) @@ -48,55 +49,7 @@ export class Item { this.ele = elementRef.nativeElement; this.swipeButtons = {}; this.optionButtons = {}; + } } - - -class Slideable { - constructor(slideElement: Element) { - } - - // override - onTransform(str: String) {} - // override - onTransitionActive(active: Boolean) {} - //override - onSlideActive(active: boolean) {} - - transform(str: String) { - if (arguments.length && str !== this._transform) { - this.onTransform() - } - } - - isTransitionActive(active: Boolean) { - if (arguments.length && active !== this._isTransitionActive) { - this._isTransitionActive = active - this.onSetTransitionActive(active) - } - return this._isTransitioning - } - - isSlideActive(active: Boolean) { - if (arguments.length && active !== this._isSlideActive) { - this._isSlideActive = active - this.onSetDragActive(active) - } - return this._isSlideActive - } - - isOpen(open: Boolean) { - if (arguments.length && open !== this._isOpen) { - this.isTransitionActive(true) - dom.raf(() => { - this.isOpen = isOpen - this.onSetIsOpen(open) - }) - } - } - -} - -class ItemSlideGesture { -} diff --git a/ionic/components/item/modes/ios.scss b/ionic/components/item/modes/ios.scss index 3e9b89a20f..91d497470e 100644 --- a/ionic/components/item/modes/ios.scss +++ b/ionic/components/item/modes/ios.scss @@ -26,6 +26,8 @@ $item-ios-divider-bg: #f5f5f5 !default; $item-ios-divider-color: #222 !default; $item-ios-divider-padding: 5px 15px !default; +$item-ios-sliding-content-bg: $background-color !default; +$item-ios-sliding-transition: transform 250ms ease-in-out !default; .item-group-title { padding: $item-ios-padding-top $item-ios-padding-right $item-ios-padding-bottom $item-ios-padding-left; @@ -157,6 +159,41 @@ $item-ios-divider-padding: 5px 15px !default; } } + + ion-item-sliding.item { + padding-left: 0; + padding-right: 0; + } + ion-item-sliding-content { + background-color: $item-ios-sliding-content-bg; + padding-right: ($item-ios-padding-right / 2); + padding-left: ($item-ios-padding-left / 2); + display: flex; + min-height: 42px; + justify-content: center; + + transition: $item-ios-sliding-transition; + + // To allow the hairlines through + margin-top: 1px; + margin-bottom: 1px; + } + ion-item-options { + button, [button] { + height: calc(100% - 2px); + margin: 1px 0 2px 0; + + border: none; + border-radius: 0; + display: inline-flex; + align-items: center; + box-sizing: border-box; + + &:before{ + margin: 0 auto; + } + } + } } .item.activated, @@ -175,14 +212,25 @@ button.item { .list, .card { button[ion-item]:not([detail-none]), - a[ion-item]:not([detail-push]), - [detail-push] { + a[ion-item]:not([detail-none]), + [detail-push]:not(ion-item-sliding) { @include ios-detail-push-icon($item-ios-detail-push-color); background-repeat: no-repeat; background-position: right ($item-ios-padding-right - 2) center; background-size: 14px 14px; padding-right: 32px; } + + ion-item-sliding[detail-push] { + + ion-item-sliding-content { + @include ios-detail-push-icon($item-ios-detail-push-color); + background-repeat: no-repeat; + background-position: right ($item-ios-padding-right - 2) center; + background-size: 14px 14px; + padding-right: 32px; + } + } } diff --git a/ionic/components/item/modes/md.scss b/ionic/components/item/modes/md.scss index 13339401c2..e67a1c6559 100644 --- a/ionic/components/item/modes/md.scss +++ b/ionic/components/item/modes/md.scss @@ -29,6 +29,9 @@ $item-md-divider-bg: #fff !default; $item-md-divider-color: #222 !default; $item-md-divider-padding: 5px 15px !default; +$item-md-sliding-content-bg: $background-color !default; +$item-md-sliding-transition: transform 250ms ease-in-out !default; + .list { .item-group-title { @@ -202,6 +205,41 @@ $item-md-divider-padding: 5px 15px !default; box-shadow: none; } + ion-item-sliding.item { + padding-left: 0; + padding-right: 0; + } + ion-item-sliding-content { + background-color: $item-md-sliding-content-bg; + padding-right: ($item-md-padding-right / 2); + padding-left: ($item-md-padding-left / 2); + display: flex; + min-height: 42px; + justify-content: center; + + transition: $item-md-sliding-transition; + + // To allow the hairlines through + margin-top: 1px; + margin-bottom: 1px; + } + ion-item-options { + button, [button] { + height: calc(100% - 2px); + margin: 1px 0 1px 0; + box-shadow: none; + + border: none; + border-radius: 0; + display: inline-flex; + align-items: center; + box-sizing: border-box; + + &:before{ + margin: 0 auto; + } + } + } } .item, diff --git a/ionic/components/item/test/sliding/e2e.ts b/ionic/components/item/test/sliding/e2e.ts new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/ionic/components/item/test/sliding/e2e.ts @@ -0,0 +1 @@ + diff --git a/ionic/components/item/test/sliding/index.ts b/ionic/components/item/test/sliding/index.ts new file mode 100644 index 0000000000..43aed36502 --- /dev/null +++ b/ionic/components/item/test/sliding/index.ts @@ -0,0 +1,7 @@ +import {App} from 'ionic/ionic'; + + +@App({ + templateUrl: 'main.html' +}) +class E2EApp {} diff --git a/ionic/components/item/test/sliding/main.html b/ionic/components/item/test/sliding/main.html new file mode 100644 index 0000000000..5ac6e19fec --- /dev/null +++ b/ionic/components/item/test/sliding/main.html @@ -0,0 +1,34 @@ +Sliding Items + + + + + Max Lynch + + Hey do you want to go to the game tonight? + + + Archive + + + + + Adam Bradley + + I think I figured out how to get more Mountain Dew + + + Archive + + + + + Ben Sperry + + I like paper + + + Archive + + + diff --git a/ionic/components/list/list.ts b/ionic/components/list/list.ts index 72fe0b7190..3c1b16b7eb 100644 --- a/ionic/components/list/list.ts +++ b/ionic/components/list/list.ts @@ -67,6 +67,18 @@ export class List extends Ion { setItemTemplate(item) { this.itemTemplate = item; } + + /** + * Keeps track of any open item (a sliding item, for example), to close it later + */ + setOpenItem(item) { + this.openItem = item; + } + closeOpenItem() { + if(this.openItem) { + this.openItem.close(true); + } + } } /** diff --git a/ionic/components/menu/menu-types.ts b/ionic/components/menu/menu-types.ts index 8b733aefae..c24195a540 100644 --- a/ionic/components/menu/menu-types.ts +++ b/ionic/components/menu/menu-types.ts @@ -66,7 +66,7 @@ export class MenuType { return promise; } - onDestory() { + onDestroy() { this.open && this.open.dispose(); this.close && this.close.dispose(); this.seek && this.seek.dispose(); diff --git a/ionic/components/search-bar/modes/ios.scss b/ionic/components/search-bar/modes/ios.scss index ff446af8e5..c9de313a5d 100644 --- a/ionic/components/search-bar/modes/ios.scss +++ b/ionic/components/search-bar/modes/ios.scss @@ -12,7 +12,8 @@ $search-bar-ios-input-search-icon-svg: " - + (input)="inputChanged($event)" class="search-bar-input" type="search" [attr.placeholder]="placeholder" [(ng-model)]="value"> + - {{cancelText}}` + {{cancelText}}` }) + export class SearchBar extends Ion { /** * TODO @@ -69,28 +78,20 @@ export class SearchBar extends Ion { */ writeValue(value) { this.value = value; - console.log('writeValue', value); this.renderer.setElementProperty(this.elementRef, 'value', this.value); } registerOnChange(val) { - console.log('registerONChange', val); } registerOnTouched(val) { - console.log('register on touched', val); } inputChanged(event) { this.value = event.target.value; - console.log('Search changed', this.value); this.ngControl.valueAccessor.writeValue(this.value); this.ngControl.control.updateValue(this.value); - // this.ngControl.valueAccessor.updateValue(this.value); - // this.ngControl.updateValue(this.value); - // TODO: Better way to do this? - //this.controlDirective._control().updateValue(event.target.value); } inputFocused() { @@ -101,6 +102,11 @@ export class SearchBar extends Ion { this.isFocused = false; this.shouldLeftAlign = this.value.trim() != ''; } + + clearInput() { + this.value = ''; + this.ngControl.control.updateValue(''); + } } /* @@ -115,13 +121,11 @@ export class SearchPipe extends Pipe { } transform(value, ...args) { - console.log('Transforming', value, args); return value; //return `${value} state:${this.state ++}`; } create(cdRef) { - console.log('REF', cdRef); return new SearchPipe(cdRef); } } diff --git a/ionic/components/search-bar/search-bar.ts.orig b/ionic/components/search-bar/search-bar.ts.orig new file mode 100644 index 0000000000..f250b7d108 --- /dev/null +++ b/ionic/components/search-bar/search-bar.ts.orig @@ -0,0 +1,135 @@ +import {ElementRef, Pipe, NgControl, Renderer, View} from 'angular2/angular2'; +//import {ControlGroup} from 'angular2/forms' + +import {Ion} from '../ion'; +import {IonicConfig} from '../../config/config'; +import {IonicComponent} from '../../config/decorators'; + +/** + * @name Search Bar + * @description + * The Search Bar service adds an input field which can be used to search or filter items. + * + * @usage + * ```html + * + * ``` + */ +@IonicComponent({ + selector: 'ion-search-bar', + inputs: [ + 'list', + 'query' + ], +<<<<<<< HEAD + defaultInputs: { +======= + defaultProperties: { + 'showCancel': false, +>>>>>>> master + 'cancelText': 'Cancel', + 'placeholder': 'Search', + 'cancelAction': function() { + console.log('Default Cancel'); + this.isFocused = false; + this.shouldLeftAlign = this.value.trim() != ''; + // TODO input blur + } + } +}) +@View({ + template: ` + + + + + + {{cancelText}}` +}) + +export class SearchBar extends Ion { + /** + * TODO + * @param {ElementRef} elementRef TODO + * @param {IonicConfig} config TODO + */ + constructor( + elementRef: ElementRef, + config: IonicConfig, + ngControl: NgControl, + renderer: Renderer + ) { + super(elementRef, config); + this.renderer = renderer; + this.elementRef = elementRef; + if(!ngControl) { + // They don't want to do anything that works, so we won't do anything that breaks + return; + } + + this.ngControl = ngControl; + + ngControl.valueAccessor = this; + + this.query = ''; + } + + /** + * Much like ngModel, this is called from our valueAccessor for the attached + * ControlDirective to update the value internally. + */ + writeValue(value) { + this.value = value; + this.renderer.setElementProperty(this.elementRef, 'value', this.value); + + } + + registerOnChange(val) { + } + + registerOnTouched(val) { + } + + inputChanged(event) { + this.value = event.target.value; + this.ngControl.valueAccessor.writeValue(this.value); + this.ngControl.control.updateValue(this.value); + } + + inputFocused() { + this.isFocused = true; + this.shouldLeftAlign = true; + } + inputBlurred() { + this.isFocused = false; + this.shouldLeftAlign = this.value.trim() != ''; + } + + clearInput() { + this.value = ''; + this.ngControl.control.updateValue(''); + } +} + +/* +export class SearchPipe extends Pipe { + constructor() { + super(); + this.state = 0; + } + + supports(newValue) { + return true; + } + + transform(value, ...args) { + return value; + //return `${value} state:${this.state ++}`; + } + + create(cdRef) { + return new SearchPipe(cdRef); + } +} +*/ diff --git a/ionic/components/search-bar/test/floating/index.ts b/ionic/components/search-bar/test/floating/index.ts index cf5eadfd07..f04c84767f 100644 --- a/ionic/components/search-bar/test/floating/index.ts +++ b/ionic/components/search-bar/test/floating/index.ts @@ -18,4 +18,7 @@ class IonicApp { toolbarSearchQuery: ['', Validators.required] }) } + myCancelAction = function() { + console.log('myCancelAction'); + } } diff --git a/ionic/components/search-bar/test/floating/main.html b/ionic/components/search-bar/test/floating/main.html index 0f7912fd4f..da43d4f498 100644 --- a/ionic/components/search-bar/test/floating/main.html +++ b/ionic/components/search-bar/test/floating/main.html @@ -5,9 +5,15 @@ --> - Default Search + Search - Default - Placeholder Search + Search - Custom Placeholder + Search - Cancel Button + + Search - Custom Cancel Button + + Search - Custom Cancel Action + diff --git a/ionic/config/decorators.ts.orig b/ionic/config/decorators.ts.orig new file mode 100644 index 0000000000..89c22f945c --- /dev/null +++ b/ionic/config/decorators.ts.orig @@ -0,0 +1,256 @@ +import {Component, Directive, View, bootstrap} from 'angular2/angular2' + +import * as util from 'ionic/util'; +<<<<<<< HEAD +import {ionicBindings} from './bootstrap'; +import {IONIC_DIRECTIVES} from './directives'; +======= +import {IonicConfig} from './config'; +import {ionicBootstrap} from '../components/app/app'; +import { + Menu, MenuToggle, MenuClose, + Button, Content, Scroll, Refresher, + Slides, Slide, SlideLazy, + Tabs, Tab, + Card, List, ListHeader, Item, ItemGroup, ItemGroupTitle, ItemSliding, + Toolbar, ToolbarTitle, ToolbarItem, + Icon, + Checkbox, Switch, + TextInput, TextInputElement, Label, + Segment, SegmentButton, SegmentControlValueAccessor, + RadioGroup, RadioButton, SearchBar, + Nav, NavbarTemplate, Navbar, + NavPush, NavPop, NavRouter, + IdRef, + ShowWhen, HideWhen +} from '../ionic'; + +/** + * The core Ionic directives. Automatically available in every IonicView + * template. + */ +export const IONIC_DIRECTIVES = [ + // Angular + CORE_DIRECTIVES, + FORM_DIRECTIVES, + + NgStyle, + + // Content + forwardRef(() => Menu), + forwardRef(() => MenuToggle), + forwardRef(() => MenuClose), + + forwardRef(() => Button), + forwardRef(() => Content), + forwardRef(() => Scroll), + forwardRef(() => Refresher), + + // Lists + forwardRef(() => Card), + forwardRef(() => List), + forwardRef(() => ListHeader), + forwardRef(() => Item), + forwardRef(() => ItemGroup), + forwardRef(() => ItemGroupTitle), + forwardRef(() => ItemSliding), + + // Slides + forwardRef(() => Slides), + forwardRef(() => Slide), + forwardRef(() => SlideLazy), + + // Tabs + forwardRef(() => Tabs), + forwardRef(() => Tab), + + // Toolbar + forwardRef(() => Toolbar), + forwardRef(() => ToolbarTitle), + forwardRef(() => ToolbarItem), + + // Media + forwardRef(() => Icon), + + // Forms + forwardRef(() => SearchBar), + forwardRef(() => Segment), + forwardRef(() => SegmentButton), + forwardRef(() => SegmentControlValueAccessor), + forwardRef(() => Checkbox), + forwardRef(() => RadioGroup), + forwardRef(() => RadioButton), + forwardRef(() => Switch), + forwardRef(() => TextInput), + forwardRef(() => TextInputElement), + forwardRef(() => Label), + + // Nav + forwardRef(() => Nav), + forwardRef(() => NavbarTemplate), + forwardRef(() => Navbar), + + forwardRef(() => NavPush), + forwardRef(() => NavPop), + forwardRef(() => NavRouter), + forwardRef(() => IdRef), + + forwardRef(() => ShowWhen), + forwardRef(() => HideWhen) +]; +>>>>>>> master + +/** + * @private + */ +class IonicViewImpl extends View { + constructor(args = {}) { + args.directives = (args.directives || []).concat(IONIC_DIRECTIVES); + super(args); + } +} + +/** + * The IonicView decorator indicates that the decorated class is an Ionic + * navigation view, meaning it can be navigated to using a [NavController](../../Nav/NavController/#creating_views) + * + * Ionic views have all [IONIC_DIRECTIVES](../IONIC_DIRECTIVES/), which include + * all Ionic components, as well as Angular's [CORE_DIRECTIVES](https://angular.io/docs/js/latest/api/core/CORE_DIRECTIVES-const.html) + * and [FORM_DIRECTIVES](https://angular.io/docs/js/latest/api/core/FORM_DIRECTIVES-const.html), + * already provided to them, so you only need to supply custom directives to + * your Ionic views: + * + * ```ts + * @IonicView({ + * template: '' + + * '' + * directives: [MyCustomDirective] + * }) + * class MyPage {} + * ``` + * Here [Checkbox](../../../components/checkbox/Checkbox/) will load because + * it is in IONIC_DIRECTIVES, so there is no need to add it to the `directives` + * array. + * + * For custom components that use Ionic components, you will need to include + * IONIC_DIRECTIVES in the `directives` array: + * + * ```ts + * import {IONIC_DIRECTIVES} from 'ionic/ionic'; + * @Component({ + * template: ` + * + * ` + * }) + * @View({ + * directives: [IONIC_DIRECTIVES] + * }) + * class MyCustomCheckbox {} + *``` + * Alternatively, you could: + * ```ts + * import {Checkbox} from 'ionic/ionic' + * ``` + * along with any other components and add them individually: + * ``` + * @View({ + * directives: [Checkbox] + * }) + * ``` + * However, using IONIC_DIRECTIVES will always Just Work :tm: with no + * performance overhead, so there is really no reason to not always use it. + * + * Ionic views are also automatically wrapped in ``, so although you + * may see these tags if you inspect your markup, you don't need to include them + * in your templates. + * + */ +export function IonicView(args) { + return function(cls) { + var annotations = Reflect.getMetadata('annotations', cls) || []; + annotations.push(new IonicViewImpl(args)); + Reflect.defineMetadata('annotations', annotations, cls); + return cls; + } +} + +/** + * TODO + */ +export function IonicDirective(config) { + return function(cls) { + var annotations = Reflect.getMetadata('annotations', cls) || []; + annotations.push(new Directive(appendConfig(cls, config))); + Reflect.defineMetadata('annotations', annotations, cls); + return cls; + } +} + +/** + * TODO + */ +export function IonicComponent(config) { + return function(cls) { + return makeComponent(cls, appendConfig(cls, config)); + } +} + +export function makeComponent(cls, config) { + var annotations = Reflect.getMetadata('annotations', cls) || []; + annotations.push(new Component(config)); + Reflect.defineMetadata('annotations', annotations, cls); + return cls; +} + +function appendConfig(cls, config) { + config.host = config.host || {}; + + cls.defaultProperties = config.defaultProperties || {}; + + config.properties = config.properties || []; + + for (let prop in cls.defaultProperties) { + // add the property to the component "properties" + config.properties.push(prop); + + // set the component "hostProperties", so the instance's + // property value will be used to set the element's attribute + config.host['[attr.' + util.pascalCaseToDashCase(prop) + ']'] = prop; + } + + cls.delegates = config.delegates; + + let componentId = config.classId || (config.selector && config.selector.replace('ion-', '')); + config.host['class'] = ((config.host['class'] || '') + ' ' + componentId).trim(); + + return config; +} + +/** + * TODO + */ +export function App(args={}) { + return function(cls) { + // get current annotations + let annotations = Reflect.getMetadata('annotations', cls) || []; + + // create @Component + args.selector = args.selector || 'ion-app'; + annotations.push(new Component(args)); + + // create @View + // if no template was provided, default so it has a root ion-nav + if (!args.templateUrl && !args.template) { + args.template = ''; + } + + annotations.push(new IonicViewImpl(args)); + + // redefine with added annotations + Reflect.defineMetadata('annotations', annotations, cls); + + bootstrap(cls, ionicBindings(cls, args.config)); + + return cls; + } +} diff --git a/package.json b/package.json index 62e8e5ec0b..d4b4796d9a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/driftyco/ionic2.git" }, "scripts": { - "test": "gulp tests", + "test": "gulp karma", "link": "npm install && gulp src && npm link" }, "dependencies": {
+ Hey do you want to go to the game tonight? +
+ I think I figured out how to get more Mountain Dew +
+ I like paper +