UNPKG

ionic-angular

Version:

A powerful framework for building mobile and progressive web apps with JavaScript and Angular 2

727 lines 27.5 kB
import { Component, ElementRef, EventEmitter, Input, HostListener, NgZone, Output, Renderer, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core'; import { clamp, isNumber, isPresent, isString } from '../../util/util'; import { Config } from '../../config/config'; import { DomController } from '../../platform/dom-controller'; import { GestureController, BLOCK_ALL } from '../../gestures/gesture-controller'; import { Haptic } from '../../tap-click/haptic'; import { Key } from '../../platform/key'; import { NavParams } from '../../navigation/nav-params'; import { Platform } from '../../platform/platform'; import { pointerCoord } from '../../util/dom'; import { UIEventManager } from '../../gestures/ui-event-manager'; import { ViewController } from '../../navigation/view-controller'; export var PickerColumnCmp = (function () { /** * @param {?} config * @param {?} _plt * @param {?} elementRef * @param {?} _zone * @param {?} _haptic * @param {?} plt * @param {?} domCtrl */ function PickerColumnCmp(config, _plt, elementRef, _zone, _haptic, plt, domCtrl) { this._plt = _plt; this.elementRef = elementRef; this._zone = _zone; this._haptic = _haptic; this.y = 0; this.pos = []; this.startY = null; this.ionChange = new EventEmitter(); this.events = new UIEventManager(plt); this.rotateFactor = config.getNumber('pickerRotateFactor', 0); this.scaleFactor = config.getNumber('pickerScaleFactor', 1); this.decelerateFunc = this.decelerate.bind(this); this.debouncer = domCtrl.debouncer(); } /** * @return {?} */ PickerColumnCmp.prototype.ngAfterViewInit = function () { // get the scrollable element within the column var /** @type {?} */ colEle = 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 this.setSelected(this.col.selectedIndex, 0); // Listening for pointer events this.events.pointerEvents({ element: this.elementRef.nativeElement, pointerDown: this.pointerStart.bind(this), pointerMove: this.pointerMove.bind(this), pointerUp: this.pointerEnd.bind(this), capture: true, zone: false }); }; /** * @return {?} */ PickerColumnCmp.prototype.ngOnDestroy = function () { this._plt.cancelRaf(this.rafId); this.events.destroy(); }; /** * @param {?} ev * @return {?} */ PickerColumnCmp.prototype.pointerStart = function (ev) { (void 0) /* console.debug */; this._haptic.gestureSelectionStart(); // We have to prevent default in order to block scrolling under the picker // but we DO NOT have to stop propagation, since we still want // some "click" events to capture ev.preventDefault(); // cancel any previous raf's that haven't fired yet this._plt.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()); var /** @type {?} */ options = this.col.options; var /** @type {?} */ minY = (options.length - 1); var /** @type {?} */ maxY = 0; for (var /** @type {?} */ i = 0; i < options.length; i++) { if (!options[i].disabled) { minY = Math.min(minY, i); maxY = Math.max(maxY, i); } } this.minY = (minY * this.optHeight * -1); this.maxY = (maxY * this.optHeight * -1); return true; }; /** * @param {?} ev * @return {?} */ PickerColumnCmp.prototype.pointerMove = function (ev) { var _this = this; ev.preventDefault(); ev.stopPropagation(); var /** @type {?} */ currentY = pointerCoord(ev).y; this.pos.push(currentY, Date.now()); this.debouncer.write(function () { if (_this.startY === null) { return; } // update the scroll position relative to pointer start position var /** @type {?} */ y = _this.y + (currentY - _this.startY); if (y > _this.minY) { // 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 += Math.pow(_this.maxY - y, 0.9); _this.bounceFrom = y; } else { _this.bounceFrom = 0; } _this.update(y, 0, false, false); var /** @type {?} */ currentIndex = Math.max(Math.abs(Math.round(y / _this.optHeight)), 0); if (currentIndex !== _this.lastTempIndex) { // Trigger a haptic event for physical feedback that the index has changed _this._haptic.gestureSelectionChanged(); _this.lastTempIndex = currentIndex; } }); }; /** * @param {?} ev * @return {?} */ PickerColumnCmp.prototype.pointerEnd = function (ev) { ev.preventDefault(); this.debouncer.cancel(); if (this.startY === null) { return; } (void 0) /* console.debug */; this.velocity = 0; if (this.bounceFrom > 0) { // bounce back up this.update(this.minY, 100, true, true); return; } else if (this.bounceFrom < 0) { // bounce back down this.update(this.maxY, 100, true, true); return; } var /** @type {?} */ endY = pointerCoord(ev).y; this.pos.push(endY, Date.now()); var /** @type {?} */ endPos = (this.pos.length - 1); var /** @type {?} */ startPos = endPos; var /** @type {?} */ timeRange = (Date.now() - 100); // move pointer to position measured 100ms ago for (var /** @type {?} */ i = endPos; i > 0 && this.pos[i] > timeRange; i -= 2) { startPos = i; } if (startPos !== endPos) { // compute relative movement between these two points var /** @type {?} */ timeOffset = (this.pos[endPos] - this.pos[startPos]); var /** @type {?} */ movedTop = (this.pos[startPos - 1] - this.pos[endPos - 1]); // based on XXms compute the movement to apply for each render step var /** @type {?} */ velocity = ((movedTop / timeOffset) * FRAME_MS); this.velocity = clamp(-MAX_PICKER_SPEED, velocity, MAX_PICKER_SPEED); } if (Math.abs(endY - this.startY) > 3) { var /** @type {?} */ y = this.y + (endY - this.startY); this.update(y, 0, true, true); } this.startY = null; this.decelerate(); }; /** * @return {?} */ PickerColumnCmp.prototype.decelerate = function () { var /** @type {?} */ y = 0; if (isNaN(this.y) || !this.optHeight) { // fallback in case numbers get outta wack this.update(y, 0, true, true); this._haptic.gestureSelectionEnd(); } 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 > this.minY) { // whoops, it's trying to scroll up farther than the options we have! y = this.minY; 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; } var /** @type {?} */ notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1); this.update(y, 0, true, !notLockedIn); if (notLockedIn) { // isn't locked in yet, keep decelerating until it is this.rafId = this._plt.raf(this.decelerateFunc); } } else if (this.y % this.optHeight !== 0) { // needs to still get locked into a position so options line up var /** @type {?} */ 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._haptic.gestureSelectionEnd(); this.decelerate(); } var /** @type {?} */ currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0); if (currentIndex !== this.lastTempIndex) { // Trigger a haptic event for physical feedback that the index has changed this._haptic.gestureSelectionChanged(); } this.lastTempIndex = currentIndex; }; /** * @param {?} ev * @param {?} index * @return {?} */ PickerColumnCmp.prototype.optClick = function (ev, index) { if (!this.velocity) { ev.preventDefault(); ev.stopPropagation(); this.setSelected(index, 150); } }; /** * @param {?} selectedIndex * @param {?} duration * @return {?} */ PickerColumnCmp.prototype.setSelected = function (selectedIndex, duration) { // 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 var /** @type {?} */ y = (selectedIndex > -1) ? ((selectedIndex * this.optHeight) * -1) : 0; this._plt.cancelRaf(this.rafId); this.velocity = 0; // so what y position we're at this.update(y, duration, true, true); }; /** * @param {?} y * @param {?} duration * @param {?} saveY * @param {?} emitChange * @return {?} */ PickerColumnCmp.prototype.update = function (y, duration, saveY, emitChange) { // ensure we've got a good round number :) y = Math.round(y); var /** @type {?} */ i; var /** @type {?} */ button; var /** @type {?} */ opt; var /** @type {?} */ optOffset; var /** @type {?} */ visible; var /** @type {?} */ translateX; var /** @type {?} */ translateY; var /** @type {?} */ translateZ; var /** @type {?} */ rotateX; var /** @type {?} */ transform; var /** @type {?} */ selected; var /** @type {?} */ parent = this.colEle.nativeElement; var /** @type {?} */ children = parent.children; var /** @type {?} */ length = children.length; var /** @type {?} */ selectedIndex = this.col.selectedIndex = Math.min(Math.max(Math.round(-y / this.optHeight), 0), length - 1); var /** @type {?} */ durationStr = (duration === 0) ? null : duration + 'ms'; var /** @type {?} */ scaleStr = "scale(" + this.scaleFactor + ")"; for (i = 0; i < length; i++) { button = children[i]; opt = (this.col.options[i]); optOffset = (i * this.optHeight) + y; visible = true; transform = ''; if (this.rotateFactor !== 0) { rotateX = optOffset * this.rotateFactor; if (Math.abs(rotateX) > 90) { visible = false; } else { translateX = 0; translateY = 0; translateZ = 90; transform = "rotateX(" + rotateX + "deg) "; } } else { translateX = 0; translateZ = 0; translateY = optOffset; if (Math.abs(translateY) > 170) { visible = false; } } selected = selectedIndex === i; if (visible) { transform += "translate3d(0px," + translateY + "px," + translateZ + "px) "; if (this.scaleFactor !== 1 && !selected) { transform += scaleStr; } } else { transform = 'translate3d(-9999px,0px,0px)'; } // Update transition duration if (duration !== opt._dur) { opt._dur = duration; button.style[this._plt.Css.transitionDuration] = durationStr; } // Update transform if (transform !== opt._trans) { opt._trans = transform; button.style[this._plt.Css.transform] = transform; } // Update selected item if (selected !== opt._selected) { opt._selected = selected; if (selected) { button.classList.add(PICKER_OPT_SELECTED); } else { button.classList.remove(PICKER_OPT_SELECTED); } } } if (saveY) { this.y = y; } if (emitChange) { if (this.lastIndex === undefined) { // have not set a last index yet this.lastIndex = this.col.selectedIndex; } else if (this.lastIndex !== this.col.selectedIndex) { // new selected index has changed from the last index // update the lastIndex and emit that it has changed this.lastIndex = this.col.selectedIndex; var /** @type {?} */ ionChange = this.ionChange; if (ionChange.observers.length > 0) { this._zone.run(ionChange.emit.bind(ionChange, this.col.options[this.col.selectedIndex])); } } } }; /** * @return {?} */ PickerColumnCmp.prototype.refresh = function () { var /** @type {?} */ min = this.col.options.length - 1; var /** @type {?} */ max = 0; for (var /** @type {?} */ i = 0; i < this.col.options.length; i++) { if (!this.col.options[i].disabled) { min = Math.min(min, i); max = Math.max(max, i); } } var /** @type {?} */ selectedIndex = clamp(min, this.col.selectedIndex, max); if (selectedIndex !== this.col.selectedIndex) { var /** @type {?} */ y = (selectedIndex * this.optHeight) * -1; this.update(y, 150, true, true); } }; PickerColumnCmp.decorators = [ { type: Component, args: [{ selector: '.picker-col', template: '<div *ngIf="col.prefix" class="picker-prefix" [style.width]="col.prefixWidth">{{col.prefix}}</div>' + '<div class="picker-opts" #colEle [style.max-width]="col.optionsWidth">' + '<button *ngFor="let o of col.options; let i=index"' + '[class.picker-opt-disabled]="o.disabled" ' + 'class="picker-opt" disable-activated (click)="optClick($event, i)">' + '{{o.text}}' + '</button>' + '</div>' + '<div *ngIf="col.suffix" class="picker-suffix" [style.width]="col.suffixWidth">{{col.suffix}}</div>', host: { '[style.max-width]': 'col.columnWidth', '[class.picker-opts-left]': 'col.align=="left"', '[class.picker-opts-right]': 'col.align=="right"', } },] }, ]; /** @nocollapse */ PickerColumnCmp.ctorParameters = function () { return [ { type: Config, }, { type: Platform, }, { type: ElementRef, }, { type: NgZone, }, { type: Haptic, }, { type: Platform, }, { type: DomController, }, ]; }; PickerColumnCmp.propDecorators = { 'colEle': [{ type: ViewChild, args: ['colEle',] },], 'col': [{ type: Input },], 'ionChange': [{ type: Output },], }; return PickerColumnCmp; }()); function PickerColumnCmp_tsickle_Closure_declarations() { /** @type {?} */ PickerColumnCmp.decorators; /** * @nocollapse * @type {?} */ PickerColumnCmp.ctorParameters; /** @type {?} */ PickerColumnCmp.propDecorators; /** @type {?} */ PickerColumnCmp.prototype.colEle; /** @type {?} */ PickerColumnCmp.prototype.col; /** @type {?} */ PickerColumnCmp.prototype.y; /** @type {?} */ PickerColumnCmp.prototype.colHeight; /** @type {?} */ PickerColumnCmp.prototype.optHeight; /** @type {?} */ PickerColumnCmp.prototype.velocity; /** @type {?} */ PickerColumnCmp.prototype.pos; /** @type {?} */ PickerColumnCmp.prototype.startY; /** @type {?} */ PickerColumnCmp.prototype.rafId; /** @type {?} */ PickerColumnCmp.prototype.bounceFrom; /** @type {?} */ PickerColumnCmp.prototype.minY; /** @type {?} */ PickerColumnCmp.prototype.maxY; /** @type {?} */ PickerColumnCmp.prototype.rotateFactor; /** @type {?} */ PickerColumnCmp.prototype.scaleFactor; /** @type {?} */ PickerColumnCmp.prototype.lastIndex; /** @type {?} */ PickerColumnCmp.prototype.lastTempIndex; /** @type {?} */ PickerColumnCmp.prototype.decelerateFunc; /** @type {?} */ PickerColumnCmp.prototype.debouncer; /** @type {?} */ PickerColumnCmp.prototype.events; /** @type {?} */ PickerColumnCmp.prototype.ionChange; /** @type {?} */ PickerColumnCmp.prototype._plt; /** @type {?} */ PickerColumnCmp.prototype.elementRef; /** @type {?} */ PickerColumnCmp.prototype._zone; /** @type {?} */ PickerColumnCmp.prototype._haptic; } export var PickerCmp = (function () { /** * @param {?} _viewCtrl * @param {?} _elementRef * @param {?} config * @param {?} _plt * @param {?} gestureCtrl * @param {?} params * @param {?} renderer */ function PickerCmp(_viewCtrl, _elementRef, config, _plt, gestureCtrl, params, renderer) { this._viewCtrl = _viewCtrl; this._elementRef = _elementRef; this._plt = _plt; this._gestureBlocker = gestureCtrl.createBlocker(BLOCK_ALL); this.d = params.data; this.mode = config.get('mode'); renderer.setElementClass(_elementRef.nativeElement, "picker-" + this.mode, true); if (this.d.cssClass) { this.d.cssClass.split(' ').forEach(function (cssClass) { renderer.setElementClass(_elementRef.nativeElement, cssClass, true); }); } this.id = (++pickerIds); this.lastClick = 0; } /** * @return {?} */ PickerCmp.prototype.ionViewWillLoad = function () { // normalize the data var /** @type {?} */ data = this.d; data.buttons = data.buttons.map(function (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(function (column) { if (!isPresent(column.options)) { column.options = []; } column.selectedIndex = column.selectedIndex || 0; column.options = column.options.map(function (inputOpt) { var /** @type {?} */ opt = { text: '', value: '', disabled: inputOpt.disabled, }; if (isPresent(inputOpt)) { if (isString(inputOpt) || isNumber(inputOpt)) { opt.text = inputOpt.toString(); 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; }); }; /** * @return {?} */ PickerCmp.prototype.ionViewWillEnter = function () { this._gestureBlocker.block(); }; /** * @return {?} */ PickerCmp.prototype.ionViewDidLeave = function () { this._gestureBlocker.unblock(); }; /** * @return {?} */ PickerCmp.prototype.refresh = function () { this._cols.forEach(function (column) { column.refresh(); }); }; /** * @param {?} selectedOption * @return {?} */ PickerCmp.prototype._colChange = function (selectedOption) { // one of the columns has changed its selected index var /** @type {?} */ picker = (this._viewCtrl); picker.ionChange.emit(this.getSelected()); }; /** * @param {?} ev * @return {?} */ PickerCmp.prototype._keyUp = function (ev) { if (this.enabled && this._viewCtrl.isLast()) { if (ev.keyCode === Key.ENTER) { 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. (void 0) /* console.debug */; var /** @type {?} */ button = this.d.buttons[this.d.buttons.length - 1]; this.btnClick(button); } } else if (ev.keyCode === Key.ESCAPE) { (void 0) /* console.debug */; this.bdClick(); } } }; /** * @return {?} */ PickerCmp.prototype.ionViewDidEnter = function () { this._plt.focusOutActiveElement(); var /** @type {?} */ focusableEle = this._elementRef.nativeElement.querySelector('button'); if (focusableEle) { focusableEle.focus(); } this.enabled = true; }; /** * @param {?} button * @return {?} */ PickerCmp.prototype.btnClick = function (button) { if (!this.enabled) { return; } // keep the time of the most recent button click this.lastClick = Date.now(); var /** @type {?} */ shouldDismiss = true; if (button.handler) { // a handler has been provided, execute it // pass the handler the values from the inputs if (button.handler(this.getSelected()) === false) { // if the return value of the handler is false then do not dismiss shouldDismiss = false; } } if (shouldDismiss) { this.dismiss(button.role); } }; /** * @return {?} */ PickerCmp.prototype.bdClick = function () { if (this.enabled && this.d.enableBackdropDismiss) { this.dismiss('backdrop'); } }; /** * @param {?} role * @return {?} */ PickerCmp.prototype.dismiss = function (role) { return this._viewCtrl.dismiss(this.getSelected(), role); }; /** * @return {?} */ PickerCmp.prototype.getSelected = function () { var /** @type {?} */ selected = {}; this.d.columns.forEach(function (col, index) { var /** @type {?} */ selectedColumn = col.options[col.selectedIndex]; selected[col.name] = { text: selectedColumn ? selectedColumn.text : null, value: selectedColumn ? selectedColumn.value : null, columnIndex: index, }; }); return selected; }; /** * @return {?} */ PickerCmp.prototype.ngOnDestroy = function () { (void 0) /* assert */; this._gestureBlocker.destroy(); }; PickerCmp.decorators = [ { type: Component, args: [{ selector: 'ion-picker-cmp', template: "\n <ion-backdrop (click)=\"bdClick()\"></ion-backdrop>\n <div class=\"picker-wrapper\">\n <div class=\"picker-toolbar\">\n <div *ngFor=\"let b of d.buttons\" class=\"picker-toolbar-button\" [ngClass]=\"b.cssRole\">\n <button ion-button (click)=\"btnClick(b)\" [ngClass]=\"b.cssClass\" class=\"picker-button\" clear>\n {{b.text}}\n </button>\n </div>\n </div>\n <div class=\"picker-columns\">\n <div class=\"picker-above-highlight\"></div>\n <div *ngFor=\"let c of d.columns\" [col]=\"c\" class=\"picker-col\" (ionChange)=\"_colChange($event)\"></div>\n <div class=\"picker-below-highlight\"></div>\n </div>\n </div>\n ", host: { 'role': 'dialog' }, encapsulation: ViewEncapsulation.None, },] }, ]; /** @nocollapse */ PickerCmp.ctorParameters = function () { return [ { type: ViewController, }, { type: ElementRef, }, { type: Config, }, { type: Platform, }, { type: GestureController, }, { type: NavParams, }, { type: Renderer, }, ]; }; PickerCmp.propDecorators = { '_cols': [{ type: ViewChildren, args: [PickerColumnCmp,] },], '_keyUp': [{ type: HostListener, args: ['body:keyup', ['$event'],] },], }; return PickerCmp; }()); function PickerCmp_tsickle_Closure_declarations() { /** @type {?} */ PickerCmp.decorators; /** * @nocollapse * @type {?} */ PickerCmp.ctorParameters; /** @type {?} */ PickerCmp.propDecorators; /** @type {?} */ PickerCmp.prototype._cols; /** @type {?} */ PickerCmp.prototype.d; /** @type {?} */ PickerCmp.prototype.enabled; /** @type {?} */ PickerCmp.prototype.lastClick; /** @type {?} */ PickerCmp.prototype.id; /** @type {?} */ PickerCmp.prototype.mode; /** @type {?} */ PickerCmp.prototype._gestureBlocker; /** @type {?} */ PickerCmp.prototype._viewCtrl; /** @type {?} */ PickerCmp.prototype._elementRef; /** @type {?} */ PickerCmp.prototype._plt; } var /** @type {?} */ pickerIds = -1; var /** @type {?} */ PICKER_OPT_SELECTED = 'picker-opt-selected'; var /** @type {?} */ DECELERATION_FRICTION = 0.97; var /** @type {?} */ FRAME_MS = (1000 / 60); var /** @type {?} */ MAX_PICKER_SPEED = 60; //# sourceMappingURL=picker-component.js.map