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 + + + + + + + + + + + + + + + + + + \ 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)); } } }