diff --git a/ionic/components.ios.scss b/ionic/components.ios.scss
index b37c65aff0..797def8943 100644
--- a/ionic/components.ios.scss
+++ b/ionic/components.ios.scss
@@ -21,6 +21,7 @@
"components/loading/loading.ios",
"components/menu/menu.ios",
"components/modal/modal.ios",
+ "components/picker/picker.ios",
"components/radio/radio.ios",
"components/searchbar/searchbar.ios",
"components/segment/segment.ios",
diff --git a/ionic/components.md.scss b/ionic/components.md.scss
index 4515f65aa1..3b38526227 100644
--- a/ionic/components.md.scss
+++ b/ionic/components.md.scss
@@ -21,6 +21,7 @@
"components/loading/loading.md",
"components/menu/menu.md",
"components/modal/modal.md",
+ "components/picker/picker.md",
"components/radio/radio.md",
"components/searchbar/searchbar.md",
"components/segment/segment.md",
diff --git a/ionic/components.ts b/ionic/components.ts
index 49dc914bf8..bec35db439 100644
--- a/ionic/components.ts
+++ b/ionic/components.ts
@@ -1,51 +1,50 @@
-export * from './components/app/app'
-export * from './components/app/id'
-
-export * from './components/action-sheet/action-sheet'
-export * from './components/alert/alert'
-export * from './components/badge/badge'
-export * from './components/button/button'
-export * from './components/checkbox/checkbox'
-export * from './components/content/content'
-export * from './components/icon/icon'
-export * from './components/img/img'
-export * from './components/infinite-scroll/infinite-scroll'
-export * from './components/infinite-scroll/infinite-scroll-content'
-export * from './components/input/input'
-export * from './components/item/item'
-export * from './components/item/item-sliding'
-export * from './components/label/label'
-export * from './components/list/list'
-export * from './components/loading/loading'
-export * from './components/menu/menu-controller'
-export * from './components/menu/menu'
-export * from './components/menu/menu-types'
-export * from './components/menu/menu-toggle'
-export * from './components/menu/menu-close'
-export * from './components/modal/modal'
-export * from './components/nav/nav'
-export * from './components/nav/nav-controller'
-export * from './components/nav/view-controller'
-export * from './components/nav/nav-params'
-export * from './components/nav/nav-push'
-export * from './components/nav/nav-router'
-export * from './components/navbar/navbar'
-export * from './components/option/option'
-export * from './components/radio/radio-button'
-export * from './components/radio/radio-group'
-export * from './components/refresher/refresher'
-export * from './components/refresher/refresher-content'
-export * from './components/scroll/scroll'
-export * from './components/searchbar/searchbar'
-export * from './components/segment/segment'
-export * from './components/select/select'
-export * from './components/show-hide-when/show-hide-when'
-export * from './components/slides/slides'
-export * from './components/spinner/spinner'
-export * from './components/tabs/tabs'
-export * from './components/tabs/tab'
-export * from './components/tap-click/tap-click'
-export * from './components/toggle/toggle'
-export * from './components/toast/toast'
-export * from './components/toolbar/toolbar'
-export * from './components/virtual-scroll/virtual-scroll'
+export * from './components/app/app';
+export * from './components/app/id';
+export * from './components/action-sheet/action-sheet';
+export * from './components/alert/alert';
+export * from './components/badge/badge';
+export * from './components/button/button';
+export * from './components/checkbox/checkbox';
+export * from './components/content/content';
+export * from './components/icon/icon';
+export * from './components/img/img';
+export * from './components/infinite-scroll/infinite-scroll';
+export * from './components/infinite-scroll/infinite-scroll-content';
+export * from './components/input/input';
+export * from './components/item/item';
+export * from './components/item/item-sliding';
+export * from './components/label/label';
+export * from './components/list/list';
+export * from './components/loading/loading';
+export * from './components/menu/menu-controller';
+export * from './components/menu/menu';
+export * from './components/menu/menu-types';
+export * from './components/menu/menu-toggle';
+export * from './components/menu/menu-close';
+export * from './components/modal/modal';
+export * from './components/nav/nav';
+export * from './components/nav/nav-controller';
+export * from './components/nav/view-controller';
+export * from './components/nav/nav-params';
+export * from './components/nav/nav-push';
+export * from './components/nav/nav-router';
+export * from './components/navbar/navbar';
+export * from './components/option/option';
+export * from './components/picker/picker';
+export * from './components/radio/radio-button';
+export * from './components/radio/radio-group';
+export * from './components/refresher/refresher';
+export * from './components/refresher/refresher-content';
+export * from './components/scroll/scroll';
+export * from './components/searchbar/searchbar';
+export * from './components/segment/segment';
+export * from './components/select/select';
+export * from './components/show-hide-when/show-hide-when';
+export * from './components/slides/slides';
+export * from './components/spinner/spinner';
+export * from './components/tabs/tabs';
+export * from './components/tabs/tab';
+export * from './components/tap-click/tap-click';
+export * from './components/toggle/toggle';
+export * from './components/toolbar/toolbar';
+export * from './components/virtual-scroll/virtual-scroll';
diff --git a/ionic/components.wp.scss b/ionic/components.wp.scss
index e1e423801f..8061150743 100644
--- a/ionic/components.wp.scss
+++ b/ionic/components.wp.scss
@@ -21,6 +21,7 @@
"components/loading/loading.wp",
"components/menu/menu.wp",
"components/modal/modal.wp",
+ "components/picker/picker.wp",
"components/radio/radio.wp",
"components/searchbar/searchbar.wp",
"components/segment/segment.wp",
diff --git a/ionic/components/content/content.ts b/ionic/components/content/content.ts
index ba8273b4c5..bd294c2697 100644
--- a/ionic/components/content/content.ts
+++ b/ionic/components/content/content.ts
@@ -4,7 +4,7 @@ import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {Keyboard} from '../../util/keyboard';
-import {raf, nativeTimeout, transitionEnd} from '../../util/dom';
+import {nativeRaf, nativeTimeout, transitionEnd} from '../../util/dom';
import {ViewController} from '../nav/view-controller';
import {ScrollView} from '../../util/scroll-view';
@@ -198,8 +198,8 @@ export class Content extends Ion {
lastScrollTop = currentScrollTop;
- raf(() => {
- raf(next);
+ nativeRaf(() => {
+ nativeRaf(next);
});
}
diff --git a/ionic/components/img/img.ts b/ionic/components/img/img.ts
index 45a6ca04d8..421cb9c339 100644
--- a/ionic/components/img/img.ts
+++ b/ionic/components/img/img.ts
@@ -1,6 +1,6 @@
import {Component, Input, ElementRef, ChangeDetectionStrategy, ViewEncapsulation, NgZone} from 'angular2/core';
-import {raf} from '../../util/dom';
+import {nativeRaf} from '../../util/dom';
import {isPresent} from '../../util/util';
import {Platform} from '../../platform/platform';
@@ -62,7 +62,7 @@ export class Img {
img.addEventListener('load', () => {
if (img.src === this._normalizeSrc) {
this._elementRef.nativeElement.appendChild(img);
- raf(() => {
+ nativeRaf(() => {
this._update();
});
}
diff --git a/ionic/components/input/native-input.ts b/ionic/components/input/native-input.ts
index 894a2ad9fe..381322a584 100644
--- a/ionic/components/input/native-input.ts
+++ b/ionic/components/input/native-input.ts
@@ -2,7 +2,7 @@ import {Directive, Attribute, ElementRef, Renderer, Input, Output, EventEmitter,
import {NgControl} from 'angular2/common';
import {Config} from '../../config/config';
-import {CSS, hasFocus, raf} from '../../util/dom';
+import {CSS, hasFocus} from '../../util/dom';
/**
diff --git a/ionic/components/item/item-sliding-gesture.ts b/ionic/components/item/item-sliding-gesture.ts
index f7067121ef..edf5b76605 100644
--- a/ionic/components/item/item-sliding-gesture.ts
+++ b/ionic/components/item/item-sliding-gesture.ts
@@ -2,7 +2,7 @@ import {DIRECTION_RIGHT} from '../../gestures/hammer';
import {DragGesture} from '../../gestures/drag-gesture';
import {List} from '../list/list';
-import {CSS, raf, closest} from '../../util/dom';
+import {CSS, nativeRaf, closest} from '../../util/dom';
export class ItemSlidingGesture extends DragGesture {
@@ -108,7 +108,7 @@ export class ItemSlidingGesture extends DragGesture {
itemData.hasMouseOut = true;
}
- raf(() => {
+ nativeRaf(() => {
if (!this.dragEnded && !this.preventDrag) {
isItemActive(itemContainerEle, true);
this.open(itemContainerEle, newX, false);
@@ -146,7 +146,7 @@ export class ItemSlidingGesture extends DragGesture {
itemContainerEle.removeEventListener('mouseout', this.onMouseOut);
itemData.hasMouseOut = false;
- raf(() => {
+ nativeRaf(() => {
this.open(itemContainerEle, restingPoint, true);
});
}
diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts
index 56b172ba83..671f703412 100644
--- a/ionic/components/nav/nav-controller.ts
+++ b/ionic/components/nav/nav-controller.ts
@@ -8,7 +8,6 @@ import {Keyboard} from '../../util/keyboard';
import {NavParams} from './nav-params';
import {pascalCaseToDashCase, isBlank} from '../../util/util';
import {Portal} from './nav-portal';
-import {raf} from '../../util/dom';
import {SwipeBackGesture} from './swipe-back';
import {Transition} from '../../transitions/transition';
import {ViewController} from './view-controller';
diff --git a/ionic/components/picker/picker.ios.scss b/ionic/components/picker/picker.ios.scss
new file mode 100644
index 0000000000..6e58e3811e
--- /dev/null
+++ b/ionic/components/picker/picker.ios.scss
@@ -0,0 +1,155 @@
+@import "../../globals.ios";
+@import "./picker";
+
+// iOS Picker
+// --------------------------------------------------
+
+$picker-ios-height: 260px !default;
+$picker-ios-border-color: $list-ios-border-color !default;
+$picker-ios-background-color: $list-ios-background-color !default;
+
+$picker-ios-toolbar-height: 44px !default;
+$picker-ios-toolbar-background-color: $picker-ios-background-color !default;
+
+$picker-ios-button-height: $picker-ios-toolbar-height !default;
+$picker-ios-button-text-color: $link-ios-color !default;
+$picker-ios-button-background-color: transparent !default;
+
+$picker-ios-column-padding: 0 12px !default;
+
+$picker-ios-option-padding: 0 10px !default;
+$picker-ios-option-text-color: $list-ios-text-color !default;
+$picker-ios-option-font-size: 22px !default;
+$picker-ios-option-height: 42px !default;
+$picker-ios-option-offset-y: (($picker-ios-height - $picker-ios-toolbar-height) / 2) - ($picker-ios-option-height / 2) - 10 !default;
+
+$picker-highlight-opacity: .8 !default;
+
+
+.picker-wrapper {
+ height: $picker-ios-height;
+
+ border-top: 1px solid $picker-ios-border-color;
+
+ background: $picker-ios-background-color;
+}
+
+.picker-toolbar {
+ display: flex;
+
+ height: $picker-ios-toolbar-height;
+
+ border-bottom: 1px solid $picker-ios-border-color;
+
+ background: $picker-ios-toolbar-background-color;
+}
+
+.hairlines .picker-wrapper,
+.hairlines .picker-toolbar {
+ border-width: $hairlines-width;
+}
+
+.picker-toolbar-button {
+ flex: 1;
+
+ text-align: right;
+}
+
+.picker-toolbar-cancel {
+ font-weight: normal;
+
+ text-align: left;
+}
+
+.picker-button,
+.picker-button.activated {
+ margin: 0;
+
+ height: $picker-ios-button-height;
+
+ color: $picker-ios-button-text-color;
+ background: $picker-ios-button-background-color;
+}
+
+.picker-columns {
+ height: $picker-ios-height - $picker-ios-toolbar-height;
+
+ perspective: 1800px;
+}
+
+.picker-col {
+ padding: $picker-ios-column-padding;
+
+ transform-style: preserve-3d;
+}
+
+.picker-prefix,
+.picker-suffix,
+.picker-opts {
+ top: $picker-ios-option-offset-y;
+
+ font-size: $picker-ios-option-font-size;
+ line-height: $picker-ios-option-height;
+ color: $picker-ios-option-text-color;
+
+ transform-style: preserve-3d;
+
+ pointer-events: none;
+}
+
+.picker-opt {
+ margin: 0;
+ padding: $picker-ios-option-padding;
+
+ width: calc(100% - 24px);
+
+ font-size: $picker-ios-option-font-size;
+ line-height: $picker-ios-option-height;
+
+ background: transparent;
+ transform-origin: center center;
+ transform-style: preserve-3d;
+ transition-timing-function: ease-out;
+
+ backface-visibility: hidden;
+
+ pointer-events: auto;
+}
+
+.picker-above-highlight {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 10;
+ display: block;
+
+ width: 100%;
+ height: $picker-ios-option-offset-y + 4px;
+
+ border-bottom: 1px solid $picker-ios-border-color;
+
+ background: linear-gradient(to bottom,
+ rgba($picker-ios-background-color, 1) 20%,
+ rgba($picker-ios-background-color, .7) 100%);
+
+ transform: translate3d(0, 0, 90px);
+}
+
+.picker-below-highlight {
+ position: absolute;
+ top: $picker-ios-option-offset-y + $picker-ios-option-height - 4;
+ left: 0;
+ z-index: 11;
+ display: block;
+
+ width: 100%;
+ height: $picker-ios-option-offset-y + $picker-ios-option-height;
+
+ border-top: 1px solid $picker-ios-border-color;
+
+ background: linear-gradient(to top,
+ rgba($picker-ios-background-color, 1) 30%,
+ rgba($picker-ios-background-color, .7) 100%);
+
+ transform: translate3d(0, 0, 90px);
+}
diff --git a/ionic/components/picker/picker.md.scss b/ionic/components/picker/picker.md.scss
new file mode 100644
index 0000000000..825c72d3ca
--- /dev/null
+++ b/ionic/components/picker/picker.md.scss
@@ -0,0 +1,161 @@
+@import "../../globals.md";
+@import "./picker";
+
+// Material Design Picker
+// --------------------------------------------------
+
+$picker-md-height: 260px !default;
+$picker-md-border-color: $list-md-border-color !default;
+$picker-md-background-color: $list-md-background-color !default;
+
+$picker-md-toolbar-height: 44px !default;
+$picker-md-toolbar-background-color: $picker-md-background-color !default;
+
+$picker-md-button-height: $picker-md-toolbar-height !default;
+$picker-md-button-text-color: $link-md-color !default;
+$picker-md-button-background-color: transparent !default;
+
+$picker-md-column-padding: 0 12px !default;
+
+$picker-md-option-padding: 0 10px !default;
+$picker-md-option-text-color: $list-md-text-color !default;
+$picker-md-option-font-size: 18px !default;
+$picker-md-option-height: 42px !default;
+$picker-md-option-offset-y: (($picker-md-height - $picker-md-toolbar-height) / 2) - ($picker-md-option-height / 2) - 10 !default;
+
+$picker-md-option-selected-font-size: 24px !default;
+$picker-md-option-selected-color: $link-md-color !default;
+
+$picker-highlight-opacity: .8 !default;
+
+
+.picker-wrapper {
+ height: $picker-md-height;
+
+ border-top: 1px solid $picker-md-border-color;
+
+ background: $picker-md-background-color;
+}
+
+.picker-toolbar {
+ display: flex;
+
+ justify-content: flex-end;
+
+ height: $picker-md-toolbar-height;
+
+ background: $picker-md-toolbar-background-color;
+}
+
+.hairlines .picker-wrapper,
+.hairlines .picker-toolbar {
+ border-width: $hairlines-width;
+}
+
+.picker-button,
+.picker-button.activated {
+ margin: 0;
+
+ height: $picker-md-button-height;
+
+ color: $picker-md-button-text-color;
+ background: $picker-md-button-background-color;
+
+ box-shadow: none;
+}
+
+.picker-columns {
+ height: $picker-md-height - $picker-md-toolbar-height;
+
+ perspective: 1800px;
+}
+
+.picker-col {
+ padding: $picker-md-column-padding;
+
+ transform-style: preserve-3d;
+}
+
+.picker-prefix,
+.picker-suffix,
+.picker-opts {
+ top: $picker-md-option-offset-y;
+
+ font-size: $picker-md-option-font-size;
+ line-height: $picker-md-option-height;
+ color: $picker-md-option-text-color;
+
+ transform-style: preserve-3d;
+
+ pointer-events: none;
+}
+
+.picker-opts ion-button-effect {
+ display: none;
+}
+
+.picker-opt {
+ margin: 0;
+ padding: $picker-md-option-padding;
+
+ width: calc(100% - 24px);
+
+ font-size: $picker-md-option-font-size;
+ line-height: $picker-md-option-height;
+
+ background: transparent;
+
+ transition-timing-function: ease-out;
+
+ backface-visibility: hidden;
+
+ pointer-events: auto;
+}
+
+.picker-opt .button-inner {
+ transition: 200ms;
+}
+
+.picker-prefix,
+.picker-suffix,
+.picker-opt-selected {
+ font-size: $picker-md-option-selected-font-size;
+
+ color: $picker-md-option-selected-color;
+}
+
+.picker-above-highlight {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 10;
+
+ width: 100%;
+ height: $picker-md-option-offset-y + 4px;
+
+ border-bottom: 1px solid $picker-md-border-color;
+
+ background: linear-gradient(to bottom,
+ rgba($picker-md-background-color, 1) 20%,
+ rgba($picker-md-background-color, .7) 100%);
+
+ transform: translate3d(0, 0, 90px);
+}
+
+.picker-below-highlight {
+ position: absolute;
+ top: $picker-md-option-offset-y + $picker-md-option-height - 4;
+ left: 0;
+ z-index: 11;
+
+ width: 100%;
+ height: $picker-md-option-offset-y + $picker-md-option-height;
+
+ border-top: 1px solid $picker-md-border-color;
+
+ background: linear-gradient(to top,
+ rgba($picker-md-background-color, 1) 30%,
+ rgba($picker-md-background-color, .7) 100%);
+
+ transform: translate3d(0, 0, 90px);
+}
diff --git a/ionic/components/picker/picker.scss b/ionic/components/picker/picker.scss
new file mode 100644
index 0000000000..792f3698fe
--- /dev/null
+++ b/ionic/components/picker/picker.scss
@@ -0,0 +1,121 @@
+@import "../../globals.core";
+
+// Picker
+// --------------------------------------------------
+
+$picker-width: 100% !default;
+$picker-max-width: 500px !default;
+
+
+ion-picker-cmp {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: $z-index-overlay;
+ display: block;
+
+ width: $picker-width;
+ height: $picker-width;
+}
+
+.picker-toolbar {
+ z-index: 1;
+}
+
+.picker-wrapper {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: $z-index-overlay-wrapper;
+ display: flex;
+
+ overflow: hidden;
+
+ flex-direction: column;
+
+ margin: auto;
+
+ width: $picker-width;
+ max-width: $picker-max-width;
+
+ transform: translate3d(0, 100%, 0);
+}
+
+.picker-columns {
+ position: relative;
+ display: flex;
+
+ overflow: hidden;
+
+ justify-content: center;
+}
+
+.picker-col {
+ position: relative;
+ display: flex;
+
+ max-height: 100%;
+}
+
+.picker-opts {
+ position: relative;
+
+ width: 100%;
+ min-width: 50px;
+ max-width: 100%;
+}
+
+.picker-prefix {
+ position: relative;
+
+ flex: 1;
+
+ min-width: 50px;
+ min-width: 45%;
+
+ text-align: right;
+ white-space: nowrap;
+}
+
+.picker-suffix {
+ position: relative;
+
+ flex: 1;
+
+ min-width: 50px;
+ min-width: 45%;
+
+ text-align: left;
+ white-space: nowrap;
+}
+
+.picker-opt {
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+
+ flex: 1;
+
+ width: 100%;
+
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.picker-opts-left .button-inner {
+ justify-content: flex-start;
+}
+
+.picker-opts-right .button-inner {
+ justify-content: flex-end;
+}
+
+.picker-above-highlight,
+.picker-below-highlight {
+ display: none;
+
+ pointer-events: none;
+}
diff --git a/ionic/components/picker/picker.ts b/ionic/components/picker/picker.ts
new file mode 100644
index 0000000000..0c0f2c9758
--- /dev/null
+++ b/ionic/components/picker/picker.ts
@@ -0,0 +1,625 @@
+import {Component, ElementRef, Input, ViewChild, Renderer, HostListener, ViewEncapsulation} from 'angular2/core';
+
+import {Animation} from '../../animations/animation';
+import {Transition, TransitionOptions} from '../../transitions/transition';
+import {Config} from '../../config/config';
+import {isPresent, isString, isNumber} from '../../util/util';
+import {NavParams} from '../nav/nav-params';
+import {ViewController} from '../nav/view-controller';
+import {nativeRaf, cancelRaf, CSS, pointerCoord} from '../../util/dom';
+
+
+/**
+ * @name Picker
+ * @description
+ *
+ */
+export class Picker extends ViewController {
+
+ constructor(opts: PickerOptions = {}) {
+ opts.columns = opts.columns || [];
+ opts.buttons = opts.buttons || [];
+ opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true;
+
+ super(PickerDisplayCmp, opts);
+ this.viewType = 'picker';
+ this.isOverlay = true;
+
+ // by default, pickers should not fire lifecycle events of other views
+ // for example, when an picker enters, the current active view should
+ // not fire its lifecycle events because it's not conceptually leaving
+ this.fireOtherLifecycles = false;
+ this.usePortal = true;
+ }
+
+ /**
+ * @private
+ */
+ getTransitionName(direction: string) {
+ let key = (direction === 'back' ? 'pickerLeave' : 'pickerEnter');
+ return this._nav && this._nav.config.get(key);
+ }
+
+ /**
+ * @param {any} button Picker toolbar button
+ */
+ addButton(button: any) {
+ this.data.buttons.push(button);
+ }
+
+ /**
+ * @param {any} button Picker toolbar button
+ */
+ addColumn(column: PickerColumn) {
+ this.data.columns.push(column);
+ }
+
+ /**
+ * @param {string} cssClass CSS class name to add to the picker's outer wrapper.
+ */
+ setCssClass(cssClass: string) {
+ this.data.cssClass = cssClass;
+ }
+
+ static create(opts: PickerOptions = {}) {
+ return new Picker(opts);
+ }
+
+}
+
+
+/**
+ * @private
+ */
+@Component({
+ selector: '.picker-col',
+ template:
+ '
{{col.prefix}}
' +
+ '' +
+ '' +
+ '
' +
+ '{{col.suffix}}
',
+ host: {
+ '[style.min-width]': 'col.columnWidth',
+ '[class.picker-opts-left]': 'col.align=="left"',
+ '[class.picker-opts-right]': 'col.align=="right"',
+ '(touchstart)': 'pointerStart($event)',
+ '(touchmove)': 'pointerMove($event)',
+ '(touchend)': 'pointerEnd($event)',
+ '(mousedown)': 'pointerStart($event)',
+ '(mousemove)': 'pointerMove($event)',
+ '(body:mouseup)': 'pointerEnd($event)',
+ '(body:mouseout)': 'mouseOut($event)',
+ }
+})
+class PickerColumnCmp {
+ @ViewChild('colEle') colEle: ElementRef;
+ @Input() col: PickerColumn;
+ y: number = 0;
+ colHeight: number;
+ optHeight: number;
+ velocity: number;
+ pos: number[] = [];
+ msPrv: number = 0;
+ startY: number = null;
+ rafId: number;
+ bounceFrom: number;
+ maxY: number;
+ rotateFactor: number;
+
+ constructor(config: Config) {
+ this.rotateFactor = config.getNumber('pickerRotateFactor', 0);
+ }
+
+ ngAfterViewInit() {
+ // get the scrollable element within the column
+ let colEle: HTMLElement = this.colEle.nativeElement;
+
+ this.colHeight = colEle.clientHeight;
+
+ // get the height of one option
+ this.optHeight = (colEle.firstElementChild ? colEle.firstElementChild.clientHeight : 0);
+
+ // set the scroll position for the selected option
+ let selectedIndex = this.col.options.indexOf(this.col.selected);
+ this.setSelected(selectedIndex, 0);
+ }
+
+ pointerStart(ev) {
+ console.debug('picker, pointerStart', ev.type, this.startY);
+
+ if (this.isPrevented(ev)) {
+ // do not both with mouse events if a touch event already fired
+ return;
+ }
+
+ // cancel any previous raf's that haven't fired yet
+ cancelRaf(this.rafId);
+
+ // remember where the pointer started from`
+ this.startY = pointerCoord(ev).y;
+
+ // reset everything
+ this.velocity = 0;
+ this.pos.length = 0;
+ this.pos.push(this.startY, Date.now());
+ this.maxY = (this.optHeight * (this.col.options.length - 1)) * -1;
+ }
+
+ pointerMove(ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ if (this.startY !== null) {
+ if (this.isPrevented(ev)) {
+ return;
+ }
+
+ var currentY = pointerCoord(ev).y;
+ this.pos.push(currentY, Date.now());
+
+ // update the scroll position relative to pointer start position
+ var y = this.y + (currentY - this.startY);
+
+ if (y > 0) {
+ // scrolling up higher than scroll area
+ y = Math.pow(y, 0.8);
+ this.bounceFrom = y;
+
+ } else if (y < this.maxY) {
+ // scrolling down below scroll area
+ y = y + Math.pow(this.maxY - y, 0.9);
+ this.bounceFrom = y;
+
+ } else {
+ this.bounceFrom = 0;
+ }
+
+ this.update(y, 0, false);
+ }
+ }
+
+ pointerEnd(ev) {
+ if (this.isPrevented(ev)) {
+ return;
+ }
+
+ this.velocity = 0;
+
+ if (this.bounceFrom > 0) {
+ // bounce back up
+ this.update(0, 100, true);
+
+ } else if (this.bounceFrom < 0) {
+ // bounce back down
+ this.update(this.maxY, 100, true);
+
+ } else if (this.startY !== null) {
+ var endY = pointerCoord(ev).y;
+
+ console.debug('picker, pointerEnd', ev.type, endY);
+
+ this.pos.push(endY, Date.now());
+
+ var endPos = (this.pos.length - 1);
+ var startPos = endPos;
+ var timeRange = (Date.now() - 100);
+
+ // move pointer to position measured 100ms ago
+ for (var i = endPos; i > 0 && this.pos[i] > timeRange; i -= 2) {
+ startPos = i;
+ }
+
+ if (startPos !== endPos) {
+ // compute relative movement between these two points
+ var timeOffset = (this.pos[endPos] - this.pos[startPos]);
+ var movedTop = (this.pos[startPos - 1] - this.pos[endPos - 1]);
+
+ // based on XXms compute the movement to apply for each render step
+ this.velocity = ((movedTop / timeOffset) * FRAME_MS);
+ }
+
+ if (Math.abs(endY - this.startY) > 3) {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ var y = this.y + (endY - this.startY);
+ this.update(y, 0, true);
+ }
+
+ }
+
+ this.startY = null;
+ this.decelerate();
+ }
+
+ mouseOut(ev) {
+ if (ev.target.classList.contains('picker-col')) {
+ this.pointerEnd(ev);
+ }
+ }
+
+ decelerate() {
+ var y = 0;
+ cancelRaf(this.rafId);
+
+ if (isNaN(this.y) || !this.optHeight) {
+ // fallback in case numbers get outta wack
+ this.update(y, 0, true);
+
+ } else if (Math.abs(this.velocity) > 0) {
+ // still decelerating
+ this.velocity *= DECELERATION_FRICTION;
+
+ // do not let it go slower than a velocity of 1
+ this.velocity = (this.velocity > 0 ? Math.max(this.velocity, 1) : Math.min(this.velocity, -1));
+
+ y = Math.round(this.y - this.velocity);
+
+ if (y > 0) {
+ // whoops, it's trying to scroll up farther than the options we have!
+ y = 0;
+ this.velocity = 0;
+
+ } else if (y < this.maxY) {
+ // gahh, it's trying to scroll down farther than we can!
+ y = this.maxY;
+ this.velocity = 0;
+ }
+
+ console.log(`decelerate y: ${y}, velocity: ${this.velocity}, optHeight: ${this.optHeight}`);
+
+ this.update(y, 0, true);
+
+ if (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1) {
+ // isn't locked in yet, keep decelerating until it is
+ this.rafId = nativeRaf(this.decelerate.bind(this));
+ }
+
+ } else if (this.y % this.optHeight !== 0) {
+ // needs to still get locked into a position so options line up
+ var currentPos = Math.abs(this.y % this.optHeight);
+
+ // create a velocity in the direction it needs to scroll
+ this.velocity = (currentPos > (this.optHeight / 2) ? 1 : -1);
+
+ this.decelerate();
+ }
+ }
+
+ optClick(ev, index: number) {
+ if (!this.velocity) {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ this.setSelected(index, 150);
+ }
+ }
+
+ setSelected(selectedIndex: number, duration: number) {
+ // if there is a selected index, then figure out it's y position
+ // if there isn't a selected index, then just use the top y position
+ let y = (selectedIndex > -1) ? ((selectedIndex * this.optHeight) * -1) : 0;
+
+ cancelRaf(this.rafId);
+ this.velocity = 0;
+
+ // so what y position we're at
+ this.update(y, duration, true);
+ }
+
+ update(y: number, duration: number, saveY: boolean) {
+ // ensure we've got a good round number :)
+ y = Math.round(y);
+
+ let selectedIndex = Math.abs(Math.round(y / this.optHeight));
+
+ this.col.selected = this.col.options[selectedIndex];
+
+ let colEle: HTMLElement = this.colEle.nativeElement;
+ let optElements: any = colEle.querySelectorAll('.picker-opt');
+
+ for (var i = 0; i < optElements.length; i++) {
+ var optEle: HTMLElement = optElements[i];
+ var optTop = (i * this.optHeight);
+ var optOffset = (optTop + y);
+
+ var rotateX = (optOffset * this.rotateFactor);
+ var translateX = 0;
+ var translateY = 0;
+ var translateZ = 0;
+
+ if (this.rotateFactor !== 0) {
+ translateX = 10;
+ translateZ = 90;
+ if (rotateX > 90 || rotateX < -90) {
+ translateX = -9999;
+ rotateX = 0;
+ }
+
+ } else {
+ translateY = optOffset;
+ }
+
+ optEle.style[CSS.transform] = `rotateX(${rotateX}deg) translate3d(${translateX}px,${translateY}px,${translateZ}px)`;
+
+ optEle.style[CSS.transitionDuration] = (duration > 0 ? duration + 'ms' : '');
+
+ optEle.classList[i === selectedIndex ? 'add' : 'remove']('picker-opt-selected');
+
+ }
+
+ if (saveY) {
+ this.y = y;
+ }
+ }
+
+ isPrevented(ev) {
+ if (ev.type.indexOf('touch') > -1) {
+ // this is a touch event, so prevent mouse events for a while
+ this.msPrv = Date.now() + 2000;
+
+ } else if (this.msPrv > Date.now() && ev.type.indexOf('mouse') > -1) {
+ // this is a mouse event, and a touch event already happend recently
+ // prevent the calling method from continuing
+ ev.preventDefault();
+ ev.stopPropagation();
+ return true;
+ }
+ }
+
+}
+
+
+/**
+ * @private
+ */
+@Component({
+ selector: 'ion-picker-cmp',
+ template:
+ '' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
',
+ host: {
+ 'role': 'dialog'
+ },
+ directives: [PickerColumnCmp],
+ encapsulation: ViewEncapsulation.None,
+})
+class PickerDisplayCmp {
+ private d: PickerOptions;
+ private created: number;
+ private lastClick: number;
+ private id: number;
+
+ constructor(
+ private _viewCtrl: ViewController,
+ private _elementRef: ElementRef,
+ private _config: Config,
+ params: NavParams,
+ renderer: Renderer
+ ) {
+ this.d = params.data;
+
+ if (this.d.cssClass) {
+ this.d.cssClass.split(' ').forEach(cssClass => {
+ renderer.setElementClass(_elementRef.nativeElement, cssClass, true);
+ });
+ }
+
+ this.id = (++pickerIds);
+ this.created = Date.now();
+ this.lastClick = 0;
+ }
+
+ onPageLoaded() {
+ // normalize the data
+ let data = this.d;
+
+ data.buttons = data.buttons.map(button => {
+ if (isString(button)) {
+ return { text: button };
+ }
+ if (button.role) {
+ button.cssRole = `picker-toolbar-${button.role}`;
+ }
+ return button;
+ });
+
+ // clean up dat data
+ data.columns = data.columns.map(column => {
+ if (!isPresent(column.columnWidth)) {
+ column.columnWidth = (100 / data.columns.length) + '%';
+ }
+ if (!isPresent(column.options)) {
+ column.options = [];
+ }
+
+ column.options = column.options.map(inputOpt => {
+ let opt: PickerColumnOption = {
+ text: '',
+ value: ''
+ };
+
+ if (isPresent(inputOpt)) {
+ if (isString(inputOpt) || isNumber(inputOpt)) {
+ opt.text = inputOpt;
+ opt.value = inputOpt;
+
+ } else {
+ opt.text = isPresent(inputOpt.text) ? inputOpt.text : inputOpt.value;
+ opt.value = isPresent(inputOpt.value) ? inputOpt.value : inputOpt.text;
+ }
+ }
+
+ return opt;
+ });
+ return column;
+ });
+ }
+
+ @HostListener('body:keyup', ['$event'])
+ private _keyUp(ev: KeyboardEvent) {
+ if (this.isEnabled() && this._viewCtrl.isLast()) {
+ if (ev.keyCode === 13) {
+ if (this.lastClick + 1000 < Date.now()) {
+ // do not fire this click if there recently was already a click
+ // this can happen when the button has focus and used the enter
+ // key to click the button. However, both the click handler and
+ // this keyup event will fire, so only allow one of them to go.
+ console.debug('picker, enter button');
+ let button = this.d.buttons[this.d.buttons.length - 1];
+ this.btnClick(button);
+ }
+
+ } else if (ev.keyCode === 27) {
+ console.debug('picker, escape button');
+ this.bdClick();
+ }
+ }
+ }
+
+ onPageDidEnter() {
+ let activeElement: any = document.activeElement;
+ if (activeElement) {
+ activeElement.blur();
+ }
+
+ let focusableEle = this._elementRef.nativeElement.querySelector('button');
+ if (focusableEle) {
+ focusableEle.focus();
+ }
+ }
+
+ btnClick(button, dismissDelay?) {
+ if (!this.isEnabled()) {
+ return;
+ }
+
+ // keep the time of the most recent button click
+ this.lastClick = Date.now();
+
+ let shouldDismiss = true;
+
+ if (button.handler) {
+ // a handler has been provided, execute it
+ // pass the handler the values from the inputs
+ if (button.handler(this.getValues()) === false) {
+ // if the return value of the handler is false then do not dismiss
+ shouldDismiss = false;
+ }
+ }
+
+ if (shouldDismiss) {
+ setTimeout(() => {
+ this.dismiss(button.role);
+ }, dismissDelay || this._config.get('pageTransitionDelay'));
+ }
+ }
+
+ bdClick() {
+ if (this.isEnabled() && this.d.enableBackdropDismiss) {
+ this.dismiss('backdrop');
+ }
+ }
+
+ dismiss(role): Promise {
+ return this._viewCtrl.dismiss(this.getValues(), role);
+ }
+
+ getValues() {
+ // this is an alert with text inputs
+ // return an object of all the values with the input name as the key
+ let values = {};
+ this.d.columns.forEach(col => {
+ values[col.name] = col.selected ? col.selected.value : null;
+ });
+ return values;
+ }
+
+ isEnabled() {
+ let tm = this._config.getNumber('overlayCreatedDiff', 750);
+ return (this.created + tm < Date.now());
+ }
+}
+
+export interface PickerOptions {
+ buttons?: any[];
+ columns?: PickerColumn[];
+ cssClass?: string;
+ enableBackdropDismiss?: boolean;
+}
+
+export interface PickerColumn {
+ name?: string;
+ selected?: PickerColumnOption;
+ prefix?: string;
+ suffix?: string;
+ options: PickerColumnOption[];
+ cssClass?: string;
+ columnWidth?: string;
+ prefixWidth?: string;
+ suffixWidth?: string;
+ optionsWidth?: string;
+}
+
+export interface PickerColumnOption {
+ value?: any;
+ text?: any;
+}
+
+
+/**
+ * Animations for pickers
+ */
+class PickerSlideIn extends Transition {
+ constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+ super(opts);
+
+ let ele = enteringView.pageRef().nativeElement;
+ let backdrop = new Animation(ele.querySelector('.backdrop'));
+ let wrapper = new Animation(ele.querySelector('.picker-wrapper'));
+
+ backdrop.fromTo('opacity', 0.01, 0.26);
+ wrapper.fromTo('translateY', '100%', '0%');
+
+ this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper);
+ }
+}
+Transition.register('picker-slide-in', PickerSlideIn);
+
+
+class PickerSlideOut extends Transition {
+ constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
+ super(opts);
+
+ let ele = leavingView.pageRef().nativeElement;
+ let backdrop = new Animation(ele.querySelector('.backdrop'));
+ let wrapper = new Animation(ele.querySelector('.picker-wrapper'));
+
+ backdrop.fromTo('opacity', 0.26, 0);
+ wrapper.fromTo('translateY', '0%', '100%');
+
+ this.easing('cubic-bezier(.36,.66,.04,1)').duration(450).add(backdrop).add(wrapper);
+ }
+}
+Transition.register('picker-slide-out', PickerSlideOut);
+
+
+let pickerIds = -1;
+const DECELERATION_FRICTION = 0.97;
+const FRAME_MS = (1000 / 60);
diff --git a/ionic/components/picker/picker.wp.scss b/ionic/components/picker/picker.wp.scss
new file mode 100644
index 0000000000..85fdb18cf4
--- /dev/null
+++ b/ionic/components/picker/picker.wp.scss
@@ -0,0 +1,173 @@
+@import "../../globals.wp";
+@import "./picker";
+
+// Windows Picker
+// --------------------------------------------------
+
+$picker-wp-height: 260px !default;
+$picker-wp-border-color: $list-wp-border-color !default;
+$picker-wp-background-color: $list-wp-background-color !default;
+
+$picker-wp-toolbar-height: 44px !default;
+$picker-wp-toolbar-background-color: $picker-wp-background-color !default;
+
+$picker-wp-button-height: $picker-wp-toolbar-height !default;
+$picker-wp-button-text-color: $link-wp-color !default;
+$picker-wp-button-background-color: transparent !default;
+
+$picker-wp-column-padding: 0 12px !default;
+
+$picker-wp-option-padding: 0 10px !default;
+$picker-wp-option-text-color: $list-wp-text-color !default;
+$picker-wp-option-font-size: 18px !default;
+$picker-wp-option-height: 42px !default;
+$picker-wp-option-offset-y: (($picker-wp-height - $picker-wp-toolbar-height) / 2) - ($picker-wp-option-height / 2) - 10 !default;
+
+$picker-wp-option-selected-font-size: 24px !default;
+$picker-wp-option-selected-color: $link-wp-color !default;
+
+$picker-highlight-opacity: .8 !default;
+
+
+.picker-wrapper {
+ height: $picker-wp-height;
+
+ border-top: 1px solid $picker-wp-border-color;
+
+ background: $picker-wp-background-color;
+}
+
+.picker-toolbar {
+ display: flex;
+
+ justify-content: flex-end;
+
+ height: $picker-wp-toolbar-height;
+
+ background: $picker-wp-toolbar-background-color;
+}
+
+.hairlines .picker-wrapper,
+.hairlines .picker-toolbar {
+ border-width: $hairlines-width;
+}
+
+.picker-toolbar-button {
+ flex: 1;
+
+ text-align: right;
+}
+
+.picker-toolbar-cancel {
+ font-weight: normal;
+
+ text-align: left;
+}
+
+.picker-button,
+.picker-button.activated {
+ margin: 0;
+
+ height: $picker-wp-button-height;
+
+ color: $picker-wp-button-text-color;
+ background: $picker-wp-button-background-color;
+
+ box-shadow: none;
+}
+
+.picker-columns {
+ height: $picker-wp-height - $picker-wp-toolbar-height;
+
+ perspective: 1800px;
+}
+
+.picker-col {
+ padding: $picker-wp-column-padding;
+
+ transform-style: preserve-3d;
+}
+
+.picker-prefix,
+.picker-suffix,
+.picker-opts {
+ top: $picker-wp-option-offset-y;
+
+ font-size: $picker-wp-option-font-size;
+ line-height: $picker-wp-option-height;
+ color: $picker-wp-option-text-color;
+
+ transform-style: preserve-3d;
+
+ pointer-events: none;
+}
+
+.picker-opts ion-button-effect {
+ display: none;
+}
+
+.picker-opt {
+ margin: 0;
+ padding: $picker-wp-option-padding;
+
+ width: calc(100% - 24px);
+
+ font-size: $picker-wp-option-font-size;
+ line-height: $picker-wp-option-height;
+
+ background: transparent;
+
+ transition-timing-function: ease-out;
+
+ backface-visibility: hidden;
+
+ pointer-events: auto;
+}
+
+.picker-opt .button-inner {
+ transition: 200ms;
+}
+
+.picker-prefix,
+.picker-suffix,
+.picker-opt-selected {
+ font-size: $picker-wp-option-selected-font-size;
+
+ color: $picker-wp-option-selected-color;
+}
+
+.picker-above-highlight {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 10;
+
+ width: 100%;
+ height: $picker-wp-option-offset-y + 4px;
+
+ border-bottom: 1px solid $picker-wp-border-color;
+
+ background: linear-gradient(to bottom,
+ rgba($picker-wp-background-color, 1) 20%,
+ rgba($picker-wp-background-color, .7) 100%);
+
+ transform: translate3d(0, 0, 90px);
+}
+
+.picker-below-highlight {
+ position: absolute;
+ top: $picker-wp-option-offset-y + $picker-wp-option-height - 4;
+ left: 0;
+ z-index: 11;
+
+ width: 100%;
+ height: $picker-wp-option-offset-y + $picker-wp-option-height;
+
+ border-top: 1px solid $picker-wp-border-color;
+
+ background: linear-gradient(to top,
+ rgba($picker-wp-background-color, 1) 30%,
+ rgba($picker-wp-background-color, .7) 100%);
+
+ transform: translate3d(0, 0, 90px);
+}
diff --git a/ionic/components/picker/test/basic/index.ts b/ionic/components/picker/test/basic/index.ts
new file mode 100644
index 0000000000..1d05985f57
--- /dev/null
+++ b/ionic/components/picker/test/basic/index.ts
@@ -0,0 +1,221 @@
+import {ViewEncapsulation} from 'angular2/core';
+import {App, Page, Picker, NavController} from 'ionic-angular';
+
+
+@Page({
+ templateUrl: 'main.html',
+ encapsulation: ViewEncapsulation.None,
+})
+class E2EPage {
+ smoothie: string;
+ timer: string;
+
+ constructor(private nav: NavController) {}
+
+ twoColumns() {
+ let picker = Picker.create({
+ buttons: [
+ {
+ text: 'Cancel',
+ role: 'cancel'
+ },
+ {
+ text: 'Done',
+ handler: (data) => {
+ this.smoothie = `${data.flavor1} ${data.flavor2}`;
+ }
+ }
+ ],
+ columns: [
+ {
+ name: 'flavor1',
+ align: 'right',
+ options: [
+ { text: 'Mango' },
+ { text: 'Banana' },
+ { text: 'Cherry' },
+ { text: 'Strawberry' },
+ { text: 'Raspberry' },
+ { text: 'Blueberry' },
+ { text: 'Peach' },
+ { text: 'Coconut' },
+ { text: 'Pineapple' },
+ { text: 'Honeydew' },
+ { text: 'Watermelon' },
+ { text: 'Grape' },
+ { text: 'Avocado' },
+ { text: 'Kiwi' },
+ { text: 'Orange' },
+ { text: 'Papaya' },
+ ]
+ },
+ {
+ name: 'flavor2',
+ align: 'left',
+ options: [
+ { text: 'Banana' },
+ { text: 'Orange' },
+ { text: 'Grape' },
+ { text: 'Watermelon' },
+ { text: 'Strawberry' },
+ { text: 'Papaya' },
+ { text: 'Kiwi' },
+ { text: 'Cherry' },
+ { text: 'Raspberry' },
+ { text: 'Mango' },
+ { text: 'Pineapple' },
+ { text: 'Peach' },
+ { text: 'Avocado' },
+ { text: 'Honeydew' },
+ { text: 'Blueberry' },
+ { text: 'Coconut' },
+ ]
+ },
+ ]
+ });
+
+ this.nav.present(picker);
+ }
+
+ prefixLabel() {
+ let picker = Picker.create({
+ buttons: [
+ {
+ text: 'Nerp',
+ role: 'cancel'
+ },
+ {
+ text: 'Woot!',
+ handler: (data) => {
+ this.smoothie = `${data.flavor1}`;
+ }
+ }
+ ],
+ columns: [
+ {
+ name: 'flavor1',
+ align: 'left',
+ prefix: 'Flavor',
+ options: [
+ { text: 'Mango' },
+ { text: 'Banana' },
+ { text: 'Cherry' },
+ { text: 'Strawberry' },
+ { text: 'Raspberry' },
+ { text: 'Blueberry' },
+ { text: 'Peach' },
+ { text: 'Coconut' },
+ { text: 'Pineapple' },
+ { text: 'Honeydew' },
+ { text: 'Watermelon' },
+ { text: 'Grape' },
+ { text: 'Avocado' },
+ { text: 'Kiwi' },
+ { text: 'Orange' },
+ { text: 'Papaya' },
+ ]
+ }
+ ]
+ });
+
+ this.nav.present(picker);
+ }
+
+ suffixLabel() {
+ let picker = Picker.create({
+ buttons: [
+ {
+ text: 'No',
+ role: 'cancel'
+ },
+ {
+ text: 'Si',
+ handler: (data) => {
+ this.smoothie = `${data.flavor1}`;
+ }
+ }
+ ],
+ columns: [
+ {
+ name: 'flavor1',
+ align: 'right',
+ suffix: 'flavor',
+ options: [
+ { text: 'Mango' },
+ { text: 'Banana' },
+ { text: 'Cherry' },
+ { text: 'Strawberry' },
+ { text: 'Raspberry' },
+ { text: 'Blueberry' },
+ { text: 'Peach' },
+ { text: 'Coconut' },
+ { text: 'Pineapple' },
+ { text: 'Honeydew' },
+ { text: 'Watermelon' },
+ { text: 'Grape' },
+ { text: 'Avocado' },
+ { text: 'Kiwi' },
+ { text: 'Orange' },
+ { text: 'Papaya' },
+ ]
+ }
+ ]
+ });
+
+ this.nav.present(picker);
+ }
+
+ columnSizes() {
+ let picker = Picker.create();
+
+ picker.addButton({
+ text: 'Cancel',
+ role: 'cancel'
+ });
+
+ picker.addButton({
+ text: 'Set Timer',
+ handler: (data) => {
+ this.timer = `${data.hour}:${data.min}`;
+ }
+ });
+
+ picker.addColumn({
+ name: 'hour',
+ suffix: 'hour',
+ columnWidth: '30%',
+ optionsWidth: '50px',
+ options: Array.apply(null, {length: 23}).map(Number.call, Number)
+ });
+
+ var minuteOptions = [];
+
+ for (var i = 0; i < 60; i++) {
+ minuteOptions.push({
+ text: i,
+ value: ('0' + i).slice(-2)
+ });
+ }
+
+ picker.addColumn({
+ name: 'min',
+ suffix: 'min',
+ columnWidth: '40%',
+ optionsWidth: '80px',
+ options: minuteOptions
+ });
+
+ this.nav.present(picker);
+ }
+}
+
+
+@App({
+ template: ''
+})
+class E2EApp {
+ root;
+ constructor() {
+ this.root = E2EPage;
+ }
+}
diff --git a/ionic/components/picker/test/basic/main.html b/ionic/components/picker/test/basic/main.html
new file mode 100644
index 0000000000..fa80234cb4
--- /dev/null
+++ b/ionic/components/picker/test/basic/main.html
@@ -0,0 +1,33 @@
+
+ Picker
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Smoothie: {{ smoothie }}
+ Timer: {{ timer }}
+
+
+
+
+
\ No newline at end of file
diff --git a/ionic/components/tap-click/activator.ts b/ionic/components/tap-click/activator.ts
index 24732bc2d3..7fadd82958 100644
--- a/ionic/components/tap-click/activator.ts
+++ b/ionic/components/tap-click/activator.ts
@@ -1,4 +1,4 @@
-import {raf, rafFrames} from '../../util/dom';
+import {rafFrames} from '../../util/dom';
export class Activator {
diff --git a/ionic/components/tap-click/ripple.ts b/ionic/components/tap-click/ripple.ts
index 01f40d971f..f0a0f421f8 100644
--- a/ionic/components/tap-click/ripple.ts
+++ b/ionic/components/tap-click/ripple.ts
@@ -1,5 +1,5 @@
import {Activator} from './activator';
-import {CSS, raf, rafFrames} from '../../util/dom';
+import {CSS, nativeRaf, rafFrames} from '../../util/dom';
const win: any = window;
@@ -22,7 +22,7 @@ export class RippleActivator extends Activator {
self._queue.push(activatableEle);
this._zone.runOutsideAngular(function() {
- raf(function() {
+ nativeRaf(function() {
var i;
for (i = 0; i < self._queue.length; i++) {
diff --git a/ionic/components/tap-click/tap-click.ts b/ionic/components/tap-click/tap-click.ts
index 24b51e4761..ec1eee034e 100644
--- a/ionic/components/tap-click/tap-click.ts
+++ b/ionic/components/tap-click/tap-click.ts
@@ -137,7 +137,7 @@ export class TapClick {
pointerEnd(ev) {
let activatableEle = getActivatableTarget(ev.target);
- if (activatableEle) {
+ if (activatableEle && this.startCoord) {
this.activator && this.activator.upAction(ev, activatableEle, this.startCoord.x, this.startCoord.y);
}
this.moveListeners(false);
diff --git a/ionic/components/virtual-scroll/virtual-scroll.ts b/ionic/components/virtual-scroll/virtual-scroll.ts
index 80e64d0a02..d8fea3201d 100644
--- a/ionic/components/virtual-scroll/virtual-scroll.ts
+++ b/ionic/components/virtual-scroll/virtual-scroll.ts
@@ -8,7 +8,7 @@ import {VirtualItem, VirtualHeader, VirtualFooter} from './virtual-item';
import {VirtualCell, VirtualNode, VirtualData} from './virtual-util';
import {processRecords, populateNodeData, initReadNodes, writeToNodes, updateDimensions, adjustRendered, calcDimensions, estimateHeight} from './virtual-util';
import {isBlank, isPresent, isFunction} from '../../util/util';
-import {rafFrames, raf, cancelRaf, pointerCoord, nativeTimeout, clearNativeTimeout} from '../../util/dom';
+import {rafFrames, nativeRaf, cancelRaf, pointerCoord, nativeTimeout, clearNativeTimeout} from '../../util/dom';
import {Img} from '../img/img';
@@ -391,7 +391,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
// oh no! the DOM doesn't have good data yet!
// let's try again in XXms, and give up eventually if we never get data
attempts++;
- raf(function() {
+ nativeRaf(function() {
readDimensions(done);
});
}
@@ -439,7 +439,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
this._cd.detectChanges();
// wait a frame before trying to read and calculate the dimensions
- raf(this.postRenderVirtual.bind(this));
+ nativeRaf(this.postRenderVirtual.bind(this));
}
/**
diff --git a/ionic/config/modes.ts b/ionic/config/modes.ts
index 55901bfcf8..9d6d1dffca 100644
--- a/ionic/config/modes.ts
+++ b/ionic/config/modes.ts
@@ -31,6 +31,10 @@ Config.setModeConfig('ios', {
pageTransition: 'ios-transition',
pageTransitionDelay: 16,
+ pickerEnter: 'picker-slide-in',
+ pickerLeave: 'picker-slide-out',
+ pickerRotateFactor: -0.46,
+
spinner: 'ios',
tabbarPlacement: 'bottom',
@@ -66,6 +70,9 @@ Config.setModeConfig('md', {
pageTransition: 'md-transition',
pageTransitionDelay: 96,
+ pickerEnter: 'picker-slide-in',
+ pickerLeave: 'picker-slide-out',
+
spinner: 'crescent',
tabbarHighlight: true,
@@ -104,6 +111,9 @@ Config.setModeConfig('wp', {
pageTransition: 'wp-transition',
pageTransitionDelay: 96,
+ pickerEnter: 'picker-slide-in',
+ pickerLeave: 'picker-slide-out',
+
spinner: 'circles',
tabbarPlacement: 'top',
diff --git a/ionic/util/dom.ts b/ionic/util/dom.ts
index d233eaa031..d8dae8e354 100644
--- a/ionic/util/dom.ts
+++ b/ionic/util/dom.ts
@@ -25,7 +25,10 @@
})();
// use native raf rather than the zone wrapped one
-export const raf = (window[window['Zone']['__symbol__']('requestAnimationFrame')] || window[window['Zone']['__symbol__']('webkitRequestAnimationFrame')])['bind'](window);
+export const nativeRaf = (window[window['Zone']['__symbol__']('requestAnimationFrame')] || window[window['Zone']['__symbol__']('webkitRequestAnimationFrame')])['bind'](window);
+
+// zone wrapped raf
+export const raf = window.requestAnimationFrame.bind(window);
export const cancelRaf = window.cancelAnimationFrame.bind(window);
export const nativeTimeout = window[window['Zone']['__symbol__']('setTimeout')]['bind'](window);
@@ -35,11 +38,11 @@ export function rafFrames(framesToWait, callback) {
framesToWait = Math.ceil(framesToWait);
if (framesToWait < 2) {
- raf(callback);
+ nativeRaf(callback);
} else {
nativeTimeout(() => {
- raf(callback);
+ nativeRaf(callback);
}, (framesToWait - 1) * 16.6667);
}
}
diff --git a/ionic/util/keyboard.ts b/ionic/util/keyboard.ts
index aa6af126bc..182a23a957 100644
--- a/ionic/util/keyboard.ts
+++ b/ionic/util/keyboard.ts
@@ -2,7 +2,7 @@ import {Injectable, NgZone} from 'angular2/core';
import {Config} from '../config/config';
import {Form} from './form';
-import {hasFocusedTextInput, raf, rafFrames, nativeTimeout} from './dom';
+import {hasFocusedTextInput, nativeRaf, rafFrames, nativeTimeout} from './dom';
/**
* @name Keyboard
@@ -109,7 +109,7 @@ export class Keyboard {
*/
close() {
console.debug('keyboard close()');
- raf(() => {
+ nativeRaf(() => {
if (hasFocusedTextInput()) {
// only focus out when a text input has focus
this._form.focusOut();
@@ -137,7 +137,7 @@ export class Keyboard {
let isKeyInputEnabled = false;
function cssClass() {
- raf(() => {
+ nativeRaf(() => {
document.body.classList[isKeyInputEnabled ? 'add' : 'remove']('focus-outline');
});
}
diff --git a/ionic/util/scroll-view.ts b/ionic/util/scroll-view.ts
index c0ab1e7d28..423775bd6e 100644
--- a/ionic/util/scroll-view.ts
+++ b/ionic/util/scroll-view.ts
@@ -1,4 +1,4 @@
-import {CSS, pointerCoord, raf, cancelRaf} from '../util/dom';
+import {CSS, pointerCoord, nativeRaf, cancelRaf} from '../util/dom';
export class ScrollView {
@@ -84,7 +84,7 @@ export class ScrollView {
}
if (easedT < 1) {
- raf(step);
+ nativeRaf(step);
} else {
// done
@@ -96,9 +96,9 @@ export class ScrollView {
self.isPlaying = true;
// chill out for a frame first
- raf(() => {
+ nativeRaf(() => {
startTime = Date.now();
- raf(step);
+ nativeRaf(step);
});
});
@@ -232,7 +232,7 @@ export class ScrollView {
// ******** DOM READ ****************
this._setMax();
- this._rafId = raf(this._decelerate.bind(this));
+ this._rafId = nativeRaf(this._decelerate.bind(this));
}
}
@@ -260,7 +260,7 @@ export class ScrollView {
self.setTop(self._top);
if (self._top > 0 && self._top < self._max && Math.abs(self._velocity) > MIN_VELOCITY_CONTINUE_DECELERATION) {
- self._rafId = raf(self._decelerate.bind(self));
+ self._rafId = nativeRaf(self._decelerate.bind(self));
}
}
}