From 51c398d61432cfa48a79f3b956cabbb138ffb3f4 Mon Sep 17 00:00:00 2001 From: Michael Asimakopoulos Date: Mon, 30 Jan 2017 12:07:41 +0200 Subject: [PATCH 01/43] fix(virtualscroll): Populate nodes at correct height --- .../virtual-scroll/virtual-scroll.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/virtual-scroll/virtual-scroll.ts b/src/components/virtual-scroll/virtual-scroll.ts index ee733f77e7..983ff6a82f 100644 --- a/src/components/virtual-scroll/virtual-scroll.ts +++ b/src/components/virtual-scroll/virtual-scroll.ts @@ -432,7 +432,8 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy { writeUpdate() { console.debug(`virtual-scroll, writeUpdate`); - processRecords(this._data.renderHeight, + const stopAtHeight = ((this._data.scrollTop || 0) + this._data.renderHeight); + processRecords(stopAtHeight, this._records, this._cells, this._hdrFn, @@ -458,16 +459,17 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy { const records = this._records; // initialize nodes with the correct cell data - data.topCell = 0; + adjustRendered(cells, data); data.bottomCell = (cells.length - 1); - populateNodeData(0, data.bottomCell, - data.viewWidth, true, - cells, records, nodes, - this._itmTmp.viewContainer, - this._itmTmp.templateRef, - this._hdrTmp && this._hdrTmp.templateRef, - this._ftrTmp && this._ftrTmp.templateRef, true); + populateNodeData(data.topCell || 0, + data.bottomCell, + data.viewWidth, true, + cells, records, nodes, + this._itmTmp.viewContainer, + this._itmTmp.templateRef, + this._hdrTmp && this._hdrTmp.templateRef, + this._ftrTmp && this._ftrTmp.templateRef, true); // ******** DOM WRITE **************** this._cd.detectChanges(); From fd53e04a470ca7139f6ae1b23f3f5ca2fb65649d Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 20 Feb 2017 18:30:11 -0800 Subject: [PATCH 02/43] fix(infinite-scroll):infinite-scroll can be placed in child componenets, fixes #6531 --- src/components/infinite-scroll/infinite-scroll.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index cf2008ab81..0af5a05112 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -155,7 +155,7 @@ export class InfiniteScroll { @Output() ionInfinite: EventEmitter = new EventEmitter(); constructor( - @Host() private _content: Content, + private _content: Content, private _zone: NgZone, private _elementRef: ElementRef, private _dom: DomController From d666e8b8e556a41958eb0ed8645dd70b7302496b Mon Sep 17 00:00:00 2001 From: Tom Demulier Date: Wed, 1 Mar 2017 15:54:07 +0100 Subject: [PATCH 03/43] feat(alert): add attributes min & max to alert inputs --- src/components/alert/alert-component.ts | 4 +++- src/components/alert/alert-options.ts | 2 ++ src/components/alert/alert.ts | 2 ++ src/components/alert/test/basic/app.module.ts | 12 ++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/alert/alert-component.ts b/src/components/alert/alert-component.ts index 9199169853..8ac7a06b1a 100644 --- a/src/components/alert/alert-component.ts +++ b/src/components/alert/alert-component.ts @@ -51,7 +51,7 @@ import { ViewController } from '../../navigation/view-controller'; '' + @@ -157,6 +157,8 @@ export class AlertCmp { disabled: !!input.disabled, id: isPresent(input.id) ? input.id : `alert-input-${this.id}-${index}`, handler: isPresent(input.handler) ? input.handler : null, + min: isPresent(input.min) ? input.min : null, + max: isPresent(input.max) ? input.max : null }; }); diff --git a/src/components/alert/alert-options.ts b/src/components/alert/alert-options.ts index 5000a7edd6..c8a5546011 100644 --- a/src/components/alert/alert-options.ts +++ b/src/components/alert/alert-options.ts @@ -19,4 +19,6 @@ export interface AlertInputOptions { checked?: boolean; disabled?: boolean; id?: string; + min?: string; + max?: string; } diff --git a/src/components/alert/alert.ts b/src/components/alert/alert.ts index 7fa69656a3..758eb4ef3b 100644 --- a/src/components/alert/alert.ts +++ b/src/components/alert/alert.ts @@ -244,6 +244,8 @@ export class Alert extends ViewController { * | label | `string` | The input's label (only for radio/checkbox inputs) | * | checked | `boolean` | Whether or not the input is checked. | * | id | `string` | The input's id. | + * | min | `string` | The input's minimum authorized value (only for date inputs) | + * | max | `string` | The input's maximum authorized value (only for date inputs) | * * Button options * diff --git a/src/components/alert/test/basic/app.module.ts b/src/components/alert/test/basic/app.module.ts index fd0a8d8cf7..b6d534c355 100644 --- a/src/components/alert/test/basic/app.module.ts +++ b/src/components/alert/test/basic/app.module.ts @@ -109,6 +109,18 @@ export class E2EPage { type: 'url', placeholder: 'Favorite site ever' }); + // input date with min & max + alert.addInput({ + name: 'name4', + type: 'date', + min: '2017-03-01', + max: '2018-01-12' + }); + // input date without min nor max + alert.addInput({ + name: 'name5', + type: 'date' + }); alert.addButton({ text: 'Cancel', handler: (data: any) => { From 28754926b57e23521d7ec9a1b077fe55d8087c3e Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 4 Mar 2017 19:03:28 +0100 Subject: [PATCH 04/43] refactor(content): implemented the angular way --- src/components/content/content.ts | 87 ++++++++++++++++--------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/components/content/content.ts b/src/components/content/content.ts index 00eee2aef6..0d5ea2df82 100644 --- a/src/components/content/content.ts +++ b/src/components/content/content.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Optional, Output, Renderer, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; import { App } from '../app/app'; import { Config } from '../../config/config'; @@ -110,10 +110,10 @@ export { ScrollEvent } from '../../util/scroll-view'; @Component({ selector: 'ion-content', template: - '
' + + '
' + '' + '
' + - '
' + + '
' + '' + '
' + '', @@ -165,10 +165,6 @@ export class Content extends Ion implements OnDestroy, OnInit { /** @internal */ _dirty: boolean; /** @internal */ - _scrollEle: HTMLElement; - /** @internal */ - _fixedEle: HTMLElement; - /** @internal */ _imgs: Img[] = []; /** @internal */ _viewCtrlReadSub: any; @@ -182,6 +178,12 @@ export class Content extends Ion implements OnDestroy, OnInit { /** @private */ statusbarPadding: boolean; + /** @internal */ + @ViewChild('fixedContent', { read: ElementRef }) _fixedContent: ElementRef; + + /** @internal */ + @ViewChild('scrollContent', { read: ElementRef }) _scrollContent: ElementRef; + /** * Content height of the viewable area. This does not include content * which is outside the overflow area, or content area which is under @@ -363,15 +365,12 @@ export class Content extends Ion implements OnDestroy, OnInit { * @private */ ngOnInit() { - if (this._scrollEle) return; - - const children = this._elementRef.nativeElement.children; - assert(children && children.length >= 2, 'content needs at least two children'); + assert(this.getFixedElement(), 'fixed element was not found'); + assert(this.getScrollElement(), 'scroll element was not found'); const scroll = this._scroll; - - scroll.ev.fixedElement = this._fixedEle = children[0]; - scroll.ev.scrollElement = this._scrollEle = children[1]; + scroll.ev.fixedElement = this.getFixedElement(); + scroll.ev.scrollElement = this.getScrollElement(); // subscribe to the scroll start scroll.scrollStart.subscribe(ev => { @@ -406,21 +405,28 @@ export class Content extends Ion implements OnDestroy, OnInit { this._viewCtrlWriteSub && this._viewCtrlWriteSub.unsubscribe(); this._viewCtrlReadSub = this._viewCtrlWriteSub = null; this._scroll && this._scroll.destroy(); - this._scrollEle = this._fixedEle = this._footerEle = this._scLsn = this._scroll = null; + this._footerEle = this._scLsn = this._scroll = null; } /** * @private */ getScrollElement(): HTMLElement { - return this._scrollEle; + return this._scrollContent.nativeElement; + } + + /** + * @private + */ + getFixedElement(): HTMLElement { + return this._fixedContent.nativeElement; } /** * @private */ onScrollElementTransitionEnd(callback: {(ev: TransitionEvent): void}) { - this._plt.transitionEnd(this._scrollEle, callback); + this._plt.transitionEnd(this.getScrollElement(), callback); } /** @@ -498,14 +504,10 @@ export class Content extends Ion implements OnDestroy, OnInit { * DOM WRITE */ setScrollElementStyle(prop: string, val: any) { - if (this._scrollEle) { + const scrollEle = this.getScrollElement(); + if (scrollEle) { this._dom.write(() => { - // double check here as the scroll element - // could have been destroyed in the 16ms it took - // for this dom write to happen - if (this._scrollEle) { - (this._scrollEle.style)[prop] = val; - } + (scrollEle.style)[prop] = val; }); } } @@ -527,7 +529,7 @@ export class Content extends Ion implements OnDestroy, OnInit { * {number} dimensions.scrollRight scroll scrollLeft + scrollWidth */ getContentDimensions(): ContentDimensions { - const scrollEle = this._scrollEle; + const scrollEle = this.getScrollElement(); const parentElement = scrollEle.parentElement; return { @@ -558,11 +560,10 @@ export class Content extends Ion implements OnDestroy, OnInit { console.debug(`content, addScrollPadding, newPadding: ${newPadding}, this._scrollPadding: ${this._scrollPadding}`); this._scrollPadding = newPadding; - if (this._scrollEle) { + var scrollEle = this.getScrollElement(); + if (scrollEle) { this._dom.write(() => { - if (this._scrollEle) { - this._scrollEle.style.paddingBottom = (newPadding > 0) ? newPadding + 'px' : ''; - } + scrollEle.style.paddingBottom = (newPadding > 0) ? newPadding + 'px' : ''; }); } } @@ -600,15 +601,15 @@ export class Content extends Ion implements OnDestroy, OnInit { * DOM READ */ private _readDimensions() { - let cachePaddingTop = this._pTop; - let cachePaddingRight = this._pRight; - let cachePaddingBottom = this._pBottom; - let cachePaddingLeft = this._pLeft; - let cacheHeaderHeight = this._hdrHeight; - let cacheFooterHeight = this._ftrHeight; - let cacheTabsPlacement = this._tabsPlacement; - let scrollEvent: ScrollEvent; + const cachePaddingTop = this._pTop; + const cachePaddingRight = this._pRight; + const cachePaddingBottom = this._pBottom; + const cachePaddingLeft = this._pLeft; + const cacheHeaderHeight = this._hdrHeight; + const cacheFooterHeight = this._ftrHeight; + const cacheTabsPlacement = this._tabsPlacement; let tabsTop = 0; + let scrollEvent: ScrollEvent; this._pTop = 0; this._pRight = 0; this._pBottom = 0; @@ -622,11 +623,13 @@ export class Content extends Ion implements OnDestroy, OnInit { // In certain cases this._scroll is undefined // if that is the case then we should just return - if (!this._scroll) return; + if (!this._scroll) { + return; + } scrollEvent = this._scroll.ev; - let ele: HTMLElement = this._elementRef.nativeElement; + let ele: HTMLElement = this.getNativeElement(); if (!ele) { assert(false, 'ele should be valid'); return; @@ -735,7 +738,7 @@ export class Content extends Ion implements OnDestroy, OnInit { this._cBottom !== this.contentBottom ); - this._scroll.init(this._scrollEle, this._cTop, this._cBottom); + this._scroll.init(this.getScrollElement(), this._cTop, this._cBottom); // initial imgs refresh this.imgsUpdate(); @@ -751,13 +754,13 @@ export class Content extends Ion implements OnDestroy, OnInit { return; } - const scrollEle = this._scrollEle; + const scrollEle = this.getScrollElement(); if (!scrollEle) { assert(false, 'this._scrollEle should be valid'); return; } - const fixedEle = this._fixedEle; + const fixedEle = this.getFixedElement(); if (!fixedEle) { assert(false, 'this._fixedEle should be valid'); return; From b53219a67cf006bc3ecce17ea9fec264829e20a7 Mon Sep 17 00:00:00 2001 From: Tom Demulier Date: Mon, 6 Mar 2017 09:55:03 +0100 Subject: [PATCH 05/43] feat(alert): add attributes min & max to alert inputs of type number --- src/components/alert/alert-options.ts | 4 ++-- src/components/alert/alert.ts | 8 ++++++-- src/components/alert/test/basic/app.module.ts | 10 ++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/alert/alert-options.ts b/src/components/alert/alert-options.ts index c8a5546011..1b0a6b389c 100644 --- a/src/components/alert/alert-options.ts +++ b/src/components/alert/alert-options.ts @@ -19,6 +19,6 @@ export interface AlertInputOptions { checked?: boolean; disabled?: boolean; id?: string; - min?: string; - max?: string; + min?: string | number; + max?: string | number; } diff --git a/src/components/alert/alert.ts b/src/components/alert/alert.ts index 758eb4ef3b..ec528f996d 100644 --- a/src/components/alert/alert.ts +++ b/src/components/alert/alert.ts @@ -244,8 +244,12 @@ export class Alert extends ViewController { * | label | `string` | The input's label (only for radio/checkbox inputs) | * | checked | `boolean` | Whether or not the input is checked. | * | id | `string` | The input's id. | - * | min | `string` | The input's minimum authorized value (only for date inputs) | - * | max | `string` | The input's maximum authorized value (only for date inputs) | + * | min | `string | number` | The input's minimum authorized value (string only for date inputs, number + * only for number inputs) + * | + * | max | `string | number` | The input's maximum authorized value (string only for date inputs, number + * only for number inputs) + * | * * Button options * diff --git a/src/components/alert/test/basic/app.module.ts b/src/components/alert/test/basic/app.module.ts index b6d534c355..a125ce7b32 100644 --- a/src/components/alert/test/basic/app.module.ts +++ b/src/components/alert/test/basic/app.module.ts @@ -121,6 +121,16 @@ export class E2EPage { name: 'name5', type: 'date' }); + alert.addInput({ + name: 'name6', + type: 'number', + min: -5, + max: 10 + }); + alert.addInput({ + name: 'name7', + type: 'number' + }); alert.addButton({ text: 'Cancel', handler: (data: any) => { From 0cd87f10788bdf1a32b2cfe1ad56e8e71561b868 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 8 Mar 2017 14:38:31 -0500 Subject: [PATCH 06/43] chore(ionic): release 2.2.0 --- CHANGELOG.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56025ef19e..9db9661b12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,71 @@ + +# [2.2.0](https://github.com/driftyco/ionic/compare/v2.1.0...v2.2.0) (2017-03-08) + +### Updating to 2.2.0 + +1. Update your `package.json` to match the following dependencies, remove the existing `node_modules` directory, and then run `npm install`: + + ``` + "dependencies": { + "@angular/common": "2.4.8", + "@angular/compiler": "2.4.8", + "@angular/compiler-cli": "2.4.8", + "@angular/core": "2.4.8", + "@angular/forms": "2.4.8", + "@angular/http": "2.4.8", + "@angular/platform-browser": "2.4.8", + "@angular/platform-browser-dynamic": "2.4.8", + "@angular/platform-server": "2.4.8", + "@ionic/storage": "2.0.0", + "ionic-angular": "2.2.0", + "ionic-native": "2.4.1", + "ionicons": "3.0.0", + "rxjs": "5.0.1", + "sw-toolbox": "3.4.0", + "zone.js": "0.7.2" + }, + "devDependencies": { + "@ionic/app-scripts": "1.1.4", + "typescript": "2.0.9" + } + ``` + Note: If you are using `ionic-storage`, you need to update it to `2.0.0` or you will run into an error similar to this: `Error: Can't resolve all parameters for Storage: (?, ?).`. For more information, see the [Storage Documentation](https://ionicframework.com/docs/v2/storage/). + +### What's new + +#### Split Pane +As part of our initiative to improve desktop support we have introduced a new component called [Split Pane](http://ionicframework.com/docs/v2/api/components/split-pane/SplitPane/). Split Pane makes it possible to easily create multi-view layouts. It allows elements, such as a menu or another navigation pane, to be displayed on large viewports. Split Pane can be used to achieve a layout similar to the Gmail (Android) or Mail (Apple) applications. + +#### Angular 2.4.8 +Ionic has been updated to depend on Angular 2.4.8, which is the latest version that we have tested and confirmed to be compatible with Ionic. This means that updating to the 2.2.0 release of Ionic will automatically work with all of the performance updates, bug fixes and features in Angular 2.4.8! + +### Ionic Storage + +We recently released the 2.0.0 version of `ionic-storage`. If you are using Ionic Storage in your application, you need to update to this version of `ionic-storage`. Attempting to use an older version of `ionic-storage` with Ionic 2.2.0 will cause errors. You can read about how to update to `ionic-storage` 2.0.0 [here](https://github.com/driftyco/ionic-storage/releases/tag/v2.0.0). + + +### Bug Fixes + +* **components:** clean up event listeners to stop memory leaks ([8d9f374](https://github.com/driftyco/ionic/commit/8d9f374)), closes [#10459](https://github.com/driftyco/ionic/issues/10459) [#10416](https://github.com/driftyco/ionic/issues/10416) [#10286](https://github.com/driftyco/ionic/issues/10286) +* **infinite-scroll:** use icon color from Sass var and add var for text color ([7b97fb7](https://github.com/driftyco/ionic/commit/7b97fb7)), closes [#10574](https://github.com/driftyco/ionic/issues/10574) +* **menu:** disable the menus when they should be ([dc53c8e](https://github.com/driftyco/ionic/commit/dc53c8e)) +* **menu:** don't hide menuToggle outside navbar ([e56bad9](https://github.com/driftyco/ionic/commit/e56bad9)) +* **radio:** calculate radio-inner width/height with border width ([#10495](https://github.com/driftyco/ionic/issues/10495)) ([176aa23](https://github.com/driftyco/ionic/commit/176aa23)) +* **refresher:** don't destroy events manager ([9308694](https://github.com/driftyco/ionic/commit/9308694)), ([1dd8883](https://github.com/driftyco/ionic/commit/1dd8883)), closes [#10652](https://github.com/driftyco/ionic/issues/10652) +* **refresher:** use refresher icon color from Sass var ([116ae38](https://github.com/driftyco/ionic/commit/116ae38)), closes [#10479](https://github.com/driftyco/ionic/issues/10479) +* **tabs:** emit ionChange after the tab is selected ([ac1a886](https://github.com/driftyco/ionic/commit/ac1a886)), closes [#10538](https://github.com/driftyco/ionic/issues/10538) +* **tabs:** catch the rejected promise with popToRoot ([7385158](https://github.com/driftyco/ionic/commit/7385158)) +* **view-controller:** set navigation so dimiss() will work synchronously. ([61a5317](https://github.com/driftyco/ionic/commit/61a5317)), closes [#10654](https://github.com/driftyco/ionic/issues/10654) + + +### Features + +* **alert:** add ability to set the mode on alert ([f577e54](https://github.com/driftyco/ionic/commit/f577e54)) +* **split-pane:** split pane support for ion-nav and ion-menu ([#10343](https://github.com/driftyco/ionic/issues/10343)) ([9e4c3a6](https://github.com/driftyco/ionic/commit/9e4c3a6)) +* **typography:** add text-wrap attribute for all elements ([2c2b87b](https://github.com/driftyco/ionic/commit/2c2b87b)), closes [#7051](https://github.com/driftyco/ionic/issues/7051) + + + # [2.1.0](https://github.com/driftyco/ionic/compare/v2.0.1...v2.1.0) (2017-02-23) diff --git a/package.json b/package.json index 1369bdecda..c73aaa7dfd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "ionic2", - "version": "2.1.0", + "version": "2.2.0", "description": "A powerful framework for building mobile and progressive web apps with JavaScript and Angular 2", "keywords": [ "ionic", From 1d68d1f91f1063c2e28836bed1286713b9ab9d7b Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 8 Mar 2017 15:46:35 -0500 Subject: [PATCH 07/43] chore(npm): update ionic-angular readme to remove 2.x --- scripts/npm/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/npm/README.md b/scripts/npm/README.md index 3310457539..36e6202a03 100644 --- a/scripts/npm/README.md +++ b/scripts/npm/README.md @@ -1,10 +1,10 @@ -## Ionic Framework 2.x +## Ionic Framework -The official npm package for [Ionic 2](http://ionicframework.com/), complete with pre-built ES5 bundles, TypeScript definitions, Sass files, CommonJS ES5 files, and more. +The official npm package for [Ionic](http://ionicframework.com/), complete with pre-built ES5 bundles, TypeScript definitions, Sass files, CommonJS ES5 files, and more. -To get started with Ionic 2, please read the [Installation Guide](http://ionicframework.com/docs/v2/getting-started/installation/). +To get started with Ionic, please read the [Installation Guide](http://ionicframework.com/docs/v2/intro/installation/). -[Ionic 2 Documentation](http://ionicframework.com/docs/v2/) +[Ionic Documentation](http://ionicframework.com/docs/) ### Source files @@ -22,7 +22,6 @@ Minified and unminified CommonJS and System.register module format bundles, as w ### Installation and More -To use Ionic 2, we recommend installing and utilizing the [Ionic CLI](http://ionicframework.com/docs/v2/getting-started/installation/) which will help you create pre-configured Ionic apps. - -For full instructions on using Ionic 2, please visit the [Ionic 2 Documentation](http://ionicframework.com/docs/v2/) +To use Ionic, we recommend installing and utilizing the [Ionic CLI](http://ionicframework.com/docs/v2/intro/installation/) which will help you create pre-configured Ionic apps. +For full instructions on using Ionic, please visit the [Ionic Documentation](http://ionicframework.com/docs/) From 58beea30f587dda6147310898def96ea540e5402 Mon Sep 17 00:00:00 2001 From: Felix Livni Date: Sat, 4 Mar 2017 15:39:02 -0800 Subject: [PATCH 08/43] fix(datetime): picker.refresh() in generate(...) called too early --- src/components/datetime/datetime.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/datetime/datetime.ts b/src/components/datetime/datetime.ts index ae724028ab..aa1d5b4b02 100644 --- a/src/components/datetime/datetime.ts +++ b/src/components/datetime/datetime.ts @@ -508,6 +508,7 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces picker.ionChange.subscribe(() => { this.validate(picker); + picker.refresh(); }); picker.present(pickerOptions); @@ -516,6 +517,8 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces picker.onDidDismiss(() => { this._isOpen = false; }); + + picker.refresh(); } /** @@ -673,8 +676,6 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces } } } - - picker.refresh(); } /** From 05859dba401dd0afd8e679ed47c2450d135898d9 Mon Sep 17 00:00:00 2001 From: Mohsen Sarkar Date: Wed, 15 Feb 2017 17:21:36 +0330 Subject: [PATCH 09/43] fix(searchbar): add IE support remove() as a method on HTMLElements is not supported on IE. --- src/components/searchbar/searchbar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/searchbar/searchbar.ts b/src/components/searchbar/searchbar.ts index 5305612428..01c393d8f0 100644 --- a/src/components/searchbar/searchbar.ts +++ b/src/components/searchbar/searchbar.ts @@ -289,7 +289,7 @@ export class Searchbar extends Ion { // Get the width of the span then remove it var textWidth = tempSpan.offsetWidth; - tempSpan.remove(); + doc.body.removeChild(tempSpan); // Set the input padding left var inputLeft = 'calc(50% - ' + (textWidth / 2) + 'px)'; From b5418327498563e064efe0c5ccd9ccd668dc19e5 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Fri, 3 Mar 2017 23:49:01 +0100 Subject: [PATCH 10/43] fix(range): bar width works as expected fixes #10150 --- src/components/range/range.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/components/range/range.ts b/src/components/range/range.ts index 8e557e7e71..eaf95c33c7 100644 --- a/src/components/range/range.ts +++ b/src/components/range/range.ts @@ -428,18 +428,14 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O // update which knob is pressed this._pressed = isPressed; - + let valChanged = false; if (this._activeB) { // when the pointer down started it was determined // that knob B was the one they were interacting with this._pressedB = isPressed; this._pressedA = false; this._ratioB = ratio; - - if (val === this._valB) { - // hasn't changed - return false; - } + valChanged = val === this._valB; this._valB = val; } else { @@ -447,13 +443,13 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O this._pressedA = isPressed; this._pressedB = false; this._ratioA = ratio; - - if (val === this._valA) { - // hasn't changed - return false; - } + valChanged = val === this._valA; this._valA = val; } + this._updateBar(); + if (valChanged) { + return false; + } // value has been updated if (this._dual) { @@ -478,8 +474,6 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O this.ionChange.emit(this); }); - this._updateBar(); - return true; } From d53824517848d3c9d0752fdb4fc0ef66db5e0ae0 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Fri, 3 Mar 2017 23:56:59 +0100 Subject: [PATCH 11/43] fix(range): knob B can only be actived if range is dual --- src/components/range/range.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/range/range.ts b/src/components/range/range.ts index eaf95c33c7..3a9cd5b68f 100644 --- a/src/components/range/range.ts +++ b/src/components/range/range.ts @@ -368,7 +368,7 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O // figure out which knob they started closer to const ratio = clamp(0, (current.x - rect.left) / (rect.width), 1); - this._activeB = (Math.abs(ratio - this._ratioA) > Math.abs(ratio - this._ratioB)); + this._activeB = this._dual && (Math.abs(ratio - this._ratioA) > Math.abs(ratio - this._ratioB)); // update the active knob's position this._update(current, rect, true); From bee75f7d0049e3fd920a9551d275ce8135d99c89 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sun, 4 Dec 2016 13:46:53 +0100 Subject: [PATCH 12/43] feat(overlay): method chaining pattern to configure overlays --- src/components/action-sheet/action-sheet.ts | 13 ++- .../action-sheet/test/basic/app.module.ts | 98 +++++++++---------- src/components/alert/alert.ts | 22 +++-- src/components/alert/test/basic/app.module.ts | 14 ++- src/components/loading/loading.ts | 43 +++++++- .../loading/test/basic/app.module.ts | 19 ++-- src/components/modal/test/basic/app.module.ts | 10 +- src/components/toast/test/basic/app.module.ts | 9 +- src/components/toast/toast.ts | 43 +++++++- 9 files changed, 168 insertions(+), 103 deletions(-) diff --git a/src/components/action-sheet/action-sheet.ts b/src/components/action-sheet/action-sheet.ts index c1539077eb..36bbd5063a 100644 --- a/src/components/action-sheet/action-sheet.ts +++ b/src/components/action-sheet/action-sheet.ts @@ -25,7 +25,7 @@ export class ActionSheet extends ViewController { /** * @private */ - getTransitionName(direction: string) { + getTransitionName(direction: string): string { let key = 'actionSheet' + (direction === 'back' ? 'Leave' : 'Enter'); return this._nav && this._nav.config.get(key); } @@ -33,22 +33,25 @@ export class ActionSheet extends ViewController { /** * @param {string} title Action sheet title */ - setTitle(title: string) { + setTitle(title: string): ActionSheet { this.data.title = title; + return this; } /** * @param {string} subTitle Action sheet subtitle */ - setSubTitle(subTitle: string) { + setSubTitle(subTitle: string): ActionSheet { this.data.subTitle = subTitle; + return this; } /** * @param {object} button Action sheet button */ - addButton(button: any) { + addButton(button: any): ActionSheet { this.data.buttons.push(button); + return this; } /** @@ -57,7 +60,7 @@ export class ActionSheet extends ViewController { * @param {NavOptions} [opts={}] Nav options to go with this transition. * @returns {Promise} Returns a promise which is resolved when the transition has completed. */ - present(navOptions: NavOptions = {}) { + present(navOptions: NavOptions = {}): Promise { navOptions.minClickBlockDuration = navOptions.minClickBlockDuration || 400; return this._app.present(this, navOptions); } diff --git a/src/components/action-sheet/test/basic/app.module.ts b/src/components/action-sheet/test/basic/app.module.ts index 3ba7f53090..3ef5d71b73 100644 --- a/src/components/action-sheet/test/basic/app.module.ts +++ b/src/components/action-sheet/test/basic/app.module.ts @@ -12,59 +12,55 @@ export class E2EPage { presentActionSheet1() { this.result = ''; - let actionSheet = this.actionSheetCtrl.create({ - title: 'Albums', - buttons: [ - { - text: 'Delete', - role: 'destructive', - icon: 'trash', - handler: () => { - console.log('Delete clicked'); - this.result = 'Deleted'; - } - }, - { - text: 'Share', - icon: 'share', - handler: () => { - console.log('Share clicked'); - this.result = 'Shared'; - } - }, - { - text: 'Play (open modal)', - icon: 'arrow-dropright-circle', - handler: () => { - this.result = 'Play (open modal)'; - let modal = this.modalCtrl.create(ModalPage); - modal.present(); - - // returning false does not allow the actionsheet to be closed - return false; - } - }, - { - text: 'Favorite', - icon: !this.plt.is('ios') ? 'heart' : null, - handler: () => { - console.log('Favorite clicked'); - this.result = 'Favorited'; - } - }, - { - text: 'Cancel', - role: 'cancel', // will always sort to be on the bottom - icon: !this.plt.is('ios') ? 'close' : null, - handler: () => { - console.log('Cancel clicked'); - this.result = 'Canceled'; - } + this.actionSheetCtrl.create() + .setTitle('Albums') + .addButton({ + text: 'Delete', + role: 'destructive', + icon: 'trash', + handler: () => { + console.log('Delete clicked'); + this.result = 'Deleted'; } - ] - }); + }) + .addButton({ + text: 'Share', + icon: 'share', + handler: () => { + console.log('Share clicked'); + this.result = 'Shared'; + } + }) + .addButton({ + text: 'Play (open modal)', + icon: 'arrow-dropright-circle', + handler: () => { + this.result = 'Play (open modal)'; + let modal = this.modalCtrl.create(ModalPage); + modal.present(); - actionSheet.present(); + // returning false does not allow the actionsheet to be closed + return false; + } + }) + .addButton({ + text: 'Favorite', + icon: !this.plt.is('ios') ? 'heart' : null, + handler: () => { + console.log('Favorite clicked'); + this.result = 'Favorited'; + } + }) + .addButton({ + text: 'Cancel', + role: 'cancel', // will always sort to be on the bottom + icon: !this.plt.is('ios') ? 'close' : null, + handler: () => { + console.log('Cancel clicked'); + this.result = 'Canceled'; + } + }) + .present(); } presentActionSheet2() { diff --git a/src/components/alert/alert.ts b/src/components/alert/alert.ts index 7fa69656a3..1457b7e04a 100644 --- a/src/components/alert/alert.ts +++ b/src/components/alert/alert.ts @@ -27,7 +27,7 @@ export class Alert extends ViewController { /** * @private */ - getTransitionName(direction: string) { + getTransitionName(direction: string): string { let key = (direction === 'back' ? 'alertLeave' : 'alertEnter'); return this._nav && this._nav.config.get(key); } @@ -35,43 +35,49 @@ export class Alert extends ViewController { /** * @param {string} title Alert title */ - setTitle(title: string) { + setTitle(title: string): Alert { this.data.title = title; + return this; } /** * @param {string} subTitle Alert subtitle */ - setSubTitle(subTitle: string) { + setSubTitle(subTitle: string): Alert { this.data.subTitle = subTitle; + return this; } /** * @param {string} message Alert message content */ - setMessage(message: string) { + setMessage(message: string): Alert { this.data.message = message; + return this; } /** * @param {object} input Alert input */ - addInput(input: AlertInputOptions) { + addInput(input: AlertInputOptions): Alert { this.data.inputs.push(input); + return this; } /** * @param {any} button Alert button */ - addButton(button: any) { + addButton(button: any): Alert { this.data.buttons.push(button); + return this; } /** * @param {string} cssClass Set the CSS class names on the alert's outer wrapper. */ - setCssClass(cssClass: string) { + setCssClass(cssClass: string): Alert { this.data.cssClass = cssClass; + return this; } /** @@ -87,7 +93,7 @@ export class Alert extends ViewController { * @param {NavOptions} [opts={}] Nav options to go with this transition. * @returns {Promise} Returns a promise which is resolved when the transition has completed. */ - present(navOptions: NavOptions = {}) { + present(navOptions: NavOptions = {}): Promise { navOptions.minClickBlockDuration = navOptions.minClickBlockDuration || 400; return this._app.present(this, navOptions); } diff --git a/src/components/alert/test/basic/app.module.ts b/src/components/alert/test/basic/app.module.ts index d9e0d7a033..164024832f 100644 --- a/src/components/alert/test/basic/app.module.ts +++ b/src/components/alert/test/basic/app.module.ts @@ -18,14 +18,12 @@ export class E2EPage { constructor(private alertCtrl: AlertController, private modalCtrl: ModalController) { } doAlert() { - let alert = this.alertCtrl.create({ - title: 'Alert', - subTitle: 'Subtitle', - message: 'This is an alert message.', - buttons: ['OK'] - }); - - alert.present(); + this.alertCtrl.create() + .setTitle('Alert') + .setSubTitle('Subtitle') + .setMessage('This is an alert message.') + .addButton('OK') + .present(); } doConfirm() { diff --git a/src/components/loading/loading.ts b/src/components/loading/loading.ts index 10de595a47..e9bbef7b4d 100644 --- a/src/components/loading/loading.ts +++ b/src/components/loading/loading.ts @@ -26,16 +26,49 @@ export class Loading extends ViewController { /** * @private */ - getTransitionName(direction: string) { + getTransitionName(direction: string): string { let key = (direction === 'back' ? 'loadingLeave' : 'loadingEnter'); return this._nav && this._nav.config.get(key); } /** - * @param {string} content loading message content + * @param {string} sets the html content for the loading indicator. */ - setContent(content: string) { + setContent(content: string): Loading { this.data.content = content; + return this; + } + + /** + * @param {string} sets the name of the SVG spinner for the loading indicator. + */ + setSpinner(spinner: string): Loading { + this.data.spinner = spinner; + return this; + } + + /** + * @param {string} sets additional classes for custom styles, separated by spaces. + */ + setCssClass(cssClass: string): Loading { + this.data.cssClass = cssClass; + return this; + } + + /** + * @param {string} sets whether to show the backdrop. + */ + setShowBackdrop(showBackdrop: boolean): Loading { + this.data.showBackdrop = showBackdrop; + return this; + } + + /** + * @param {string} how many milliseconds to wait before hiding the indicator. + */ + setDuration(dur: number): Loading { + this.data.duration = dur; + return this; } /** @@ -44,7 +77,7 @@ export class Loading extends ViewController { * @param {NavOptions} [opts={}] Nav options to go with this transition. * @returns {Promise} Returns a promise which is resolved when the transition has completed. */ - present(navOptions: NavOptions = {}) { + present(navOptions: NavOptions = {}): Promise { return this._app.present(this, navOptions, AppPortal.LOADING); } @@ -175,7 +208,7 @@ export class LoadingController { * @param {LoadingOptions} opts Loading options * @returns {Loading} Returns a Loading Instance */ - create(opts: LoadingOptions = {}) { + create(opts: LoadingOptions = {}): Loading { return new Loading(this._app, opts); } diff --git a/src/components/loading/test/basic/app.module.ts b/src/components/loading/test/basic/app.module.ts index d1bfc53645..38f319bec3 100644 --- a/src/components/loading/test/basic/app.module.ts +++ b/src/components/loading/test/basic/app.module.ts @@ -127,13 +127,11 @@ export class E2EPage { } presentLoadingCrescent() { - let loading = this.loadingCtrl.create({ - spinner: 'crescent', - content: 'Please wait...', - duration: 1000 - }); - - loading.present(); + this.loadingCtrl.create() + .setSpinner('crescent') + .setContent('Please wait...') + .setDuration(1000) + .present(); } // Pass the fixed-spinner class so we can turn off @@ -207,10 +205,9 @@ export class E2EPage { loading2.present(); }, 1000); - let loading3 = this.loadingCtrl.create({ - spinner: 'hide', - content: 'Loading 3 Please Wait...' - }); + let loading3 = this.loadingCtrl.create() + .setSpinner('hide') + .setContent('Loading 3 Please Wait...'); setTimeout(() => { loading3.present(); diff --git a/src/components/modal/test/basic/app.module.ts b/src/components/modal/test/basic/app.module.ts index 16c5a279e4..eb785562a9 100644 --- a/src/components/modal/test/basic/app.module.ts +++ b/src/components/modal/test/basic/app.module.ts @@ -229,11 +229,11 @@ export class ModalPassData { this.called.ionViewCanLeave++; return new Promise((resolve: any, reject: any) => { - let alert = this.alertCtrl.create(); - alert.setTitle('Do you want to submit?'); - alert.addButton({ text: 'Submit', handler: resolve }); - alert.addButton({ text: 'Cancel', role: 'cancel', handler: reject }); - alert.present(); + this.alertCtrl.create() + .setTitle('Do you want to submit?') + .addButton({ text: 'Submit', handler: resolve }) + .addButton({ text: 'Cancel', role: 'cancel', handler: reject }) + .present(); }); } diff --git a/src/components/toast/test/basic/app.module.ts b/src/components/toast/test/basic/app.module.ts index 47ab701213..d9f7a82091 100644 --- a/src/components/toast/test/basic/app.module.ts +++ b/src/components/toast/test/basic/app.module.ts @@ -45,11 +45,10 @@ export class E2EPage { } showLongToast() { - const toast = this.toastCtrl.create({ - message: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea voluptatibus quibusdam eum nihil optio, ullam accusamus magni, nobis suscipit reprehenderit, sequi quam amet impedit. Accusamus dolorem voluptates laborum dolor obcaecati.', - duration: 5000, - cssClass: 'custom-class my-toast' - }); + const toast = this.toastCtrl.create() + .setMessage('Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea voluptatibus quibusdam eum nihil optio, ullam accusamus magni, nobis suscipit reprehenderit, sequi quam amet impedit. Accusamus dolorem voluptates laborum dolor obcaecati.') + .setDuration(5000) + .setCssClass('custom-class my-toast'); toast.onDidDismiss(this.dismissHandler); toast.present(); diff --git a/src/components/toast/toast.ts b/src/components/toast/toast.ts index 128a165ac6..56a946d7cb 100644 --- a/src/components/toast/toast.ts +++ b/src/components/toast/toast.ts @@ -30,7 +30,7 @@ export class Toast extends ViewController { /** * @private */ - getTransitionName(direction: string) { + getTransitionName(direction: string): string { let key = 'toast' + (direction === 'back' ? 'Leave' : 'Enter'); return this._nav && this._nav.config.get(key); } @@ -38,15 +38,48 @@ export class Toast extends ViewController { /** * @private */ - isValidPosition(position: string) { + isValidPosition(position: string): boolean { return position === TOAST_POSITION_TOP || position === TOAST_POSITION_MIDDLE || position === TOAST_POSITION_BOTTOM; } /** * @param {string} message Toast message content */ - setMessage(message: string) { + setMessage(message: string): Toast { this.data.message = message; + return this; + } + + /** + * @param {string} message Toast message content + */ + setDuration(dur: number): Toast { + this.data.duration = dur; + return this; + } + + /** + * @param {string} message Toast message content + */ + setPosition(pos: 'top' | 'middle' | 'bottom'): Toast { + this.data.position = pos; + return this; + } + + /** + * @param {string} message Toast message content + */ + setCssClass(cssClass: string): Toast { + this.data.cssClass = cssClass; + return this; + } + + /** + * @param {string} message Toast message content + */ + setShowCloseButton(closeButton: boolean): Toast { + this.data.showCloseButton = closeButton; + return this; } /** @@ -55,7 +88,7 @@ export class Toast extends ViewController { * @param {NavOptions} [opts={}] Nav options to go with this transition. * @returns {Promise} Returns a promise which is resolved when the transition has completed. */ - present(navOptions: NavOptions = {}) { + present(navOptions: NavOptions = {}): Promise { navOptions.disableApp = false; return this._app.present(this, navOptions, AppPortal.TOAST); } @@ -142,7 +175,7 @@ export class ToastController { * Create a new toast component. See options below * @param {ToastOptions} opts Toast options. See the below table for available options. */ - create(opts: ToastOptions = {}) { + create(opts: ToastOptions = {}): Toast { return new Toast(this._app, opts); } From e90d692b1f0cdfd6712d46ecb1a4ab0934d5b249 Mon Sep 17 00:00:00 2001 From: Ahmad Amri Date: Fri, 10 Feb 2017 17:50:42 +0400 Subject: [PATCH 13/43] fix(slides): fix rtl support. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix rtl functionalities in slides when attribute (dir=“rtl”) added to ion-slides. - e2e test added: ‘slides/test/rtl’ --- src/components/slides/slides.ts | 10 ++++- src/components/slides/test/rtl/app.module.ts | 46 ++++++++++++++++++++ src/components/slides/test/rtl/main.html | 21 +++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/components/slides/test/rtl/app.module.ts create mode 100644 src/components/slides/test/rtl/main.html diff --git a/src/components/slides/slides.ts b/src/components/slides/slides.ts index 86a9e7aac8..b0ed29741f 100644 --- a/src/components/slides/slides.ts +++ b/src/components/slides/slides.ts @@ -139,7 +139,7 @@ import { ViewController } from '../../navigation/view-controller'; @Component({ selector: 'ion-slides', template: - '
' + + '
' + '
' + '' + '
' + @@ -249,6 +249,14 @@ export class Slides extends Ion { } private _pager = false; +/** + * @input {string} If dir attribute is equal to rtl, set interal _rtl to true; + */ + @Input() + set dir(val: string) { + this._rtl = (val.toLowerCase() === 'rtl'); + } + /** * @input {string} Type of pagination. Possible values are: * `bullets`, `fraction`, `progress`. Default: `bullets`. diff --git a/src/components/slides/test/rtl/app.module.ts b/src/components/slides/test/rtl/app.module.ts new file mode 100644 index 0000000000..7abaf4c317 --- /dev/null +++ b/src/components/slides/test/rtl/app.module.ts @@ -0,0 +1,46 @@ +import { Component, ViewChild, NgModule } from '@angular/core'; +import { IonicApp, IonicModule, Slides } from '../../../../../ionic-angular'; + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EPage { + @ViewChild(Slides) slider: Slides; + + onSlideWillChange(s: Slides) { + console.log(`onSlideWillChange: ${s}`); + } + + onSlideDidChange(s: Slides) { + console.log(`onSlideDidChange: ${s}`); + } + + onSlideDrag(s: Slides) { + console.log(`onSlideDrag: ${s}`); + } + +} + +@Component({ + template: '' +}) +export class E2EApp { + root = E2EPage; +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage + ], + imports: [ + IonicModule.forRoot(E2EApp) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage + ] +}) +export class AppModule {} \ No newline at end of file diff --git a/src/components/slides/test/rtl/main.html b/src/components/slides/test/rtl/main.html new file mode 100644 index 0000000000..7cae21d80e --- /dev/null +++ b/src/components/slides/test/rtl/main.html @@ -0,0 +1,21 @@ + + + +

شريحة ١

+
+ + +

شريحة ٢

+
+ + +

شريحة ٣

+
+ +
\ No newline at end of file From 30980b6798576e5cd98ccb1518d854d2d6f768c6 Mon Sep 17 00:00:00 2001 From: patricknolan Date: Wed, 1 Mar 2017 03:00:07 +0700 Subject: [PATCH 14/43] fix(toggle/checkbox): trigger ui update when using virtalScroll with Angular Reactive Forms --- src/components/checkbox/checkbox.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index 5e704bf46b..a62d90c801 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -184,6 +184,7 @@ export class Checkbox extends Ion implements IonicTapInput, AfterContentInit, Co fn(isChecked); this._setChecked(isChecked); this.onTouched(); + this._cd.detectChanges(); }; } From 84e25a17c2b0fa820dbd60cfdd0002eb5141ae75 Mon Sep 17 00:00:00 2001 From: Sergii Stotskyi Date: Sat, 22 Oct 2016 19:29:30 +0300 Subject: [PATCH 15/43] feat(infinite-scroll): add `waitFor` method to InfiniteScroll This allows to use `$event.waitFor(request())` inside template within `infinite` event. So that, user components do not depend on InifiniteScroll inside javascript. --- .../infinite-scroll/infinite-scroll.ts | 59 ++++++++++++++++++- .../infinite-scroll/test/basic/app.module.ts | 5 +- .../infinite-scroll/test/basic/main.html | 2 +- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index cf2008ab81..0cba70d9c5 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -57,6 +57,52 @@ import { DomController } from '../../platform/dom-controller'; * } * ``` * + * ## `waitFor` method of InfiniteScroll + * + * In case if your async operation returns promise you can utilize + * `waitFor` method inside your template. + * + * ```html + * + * + * + * {{item}} + * + * + * + * + * + * + * + * ``` + * + * ```ts + * @Component({...}) + * export class NewsFeedPage { + * items = []; + * + * constructor() { + * for (var i = 0; i < 30; i++) { + * this.items.push( this.items.length ); + * } + * } + * + * doInfinite(): Promise { + * console.log('Begin async operation'); + * + * return new Promise((resolve) => { + * setTimeout(() => { + * for (var i = 0; i < 30; i++) { + * this.items.push( this.items.length ); + * } + * + * console.log('Async operation has ended'); + * resolve(); + * }, 500); + * }) + * } + * } + * ``` * * ## Infinite Scroll Content * @@ -221,7 +267,18 @@ export class InfiniteScroll { * to `enabled`. */ complete() { - this.state = STATE_ENABLED; + if (this.state === STATE_LOADING) { + this.state = STATE_ENABLED; + } + } + + /** + * Pass a promise inside `waitFor()` within the `infinite` output event handler in order to + * change state of infiniteScroll to "complete" + */ + waitFor(action: Promise) { + const enable = this.complete.bind(this); + action.then(enable, enable); } /** diff --git a/src/components/infinite-scroll/test/basic/app.module.ts b/src/components/infinite-scroll/test/basic/app.module.ts index da67d0183f..a1b42f3684 100644 --- a/src/components/infinite-scroll/test/basic/app.module.ts +++ b/src/components/infinite-scroll/test/basic/app.module.ts @@ -16,16 +16,15 @@ export class E2EPage1 { } } - doInfinite(infiniteScroll: InfiniteScroll) { + doInfinite(): Promise { console.log('Begin async operation'); - getAsyncData().then(newData => { + return getAsyncData().then(newData => { for (var i = 0; i < newData.length; i++) { this.items.push( this.items.length ); } console.log('Finished receiving data, async operation complete'); - infiniteScroll.complete(); if (this.items.length > 90) { this.enabled = false; diff --git a/src/components/infinite-scroll/test/basic/main.html b/src/components/infinite-scroll/test/basic/main.html index c287b7a60d..bcb3278046 100644 --- a/src/components/infinite-scroll/test/basic/main.html +++ b/src/components/infinite-scroll/test/basic/main.html @@ -23,7 +23,7 @@ - + From 5b1126f7f213d0d230ac01d5b1b7c8e9f4fdcddb Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 4 Mar 2017 00:30:11 +0100 Subject: [PATCH 16/43] style(slides): adds missing final linebreak --- src/components/slides/test/rtl/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/slides/test/rtl/app.module.ts b/src/components/slides/test/rtl/app.module.ts index 7abaf4c317..9c40c106c6 100644 --- a/src/components/slides/test/rtl/app.module.ts +++ b/src/components/slides/test/rtl/app.module.ts @@ -43,4 +43,4 @@ export class E2EApp { E2EPage ] }) -export class AppModule {} \ No newline at end of file +export class AppModule { } From 62bf2bee288f5d5c5ab7408fd918e7291f1b8219 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 4 Mar 2017 00:31:38 +0100 Subject: [PATCH 17/43] style(infinite-scroll): add missing type --- src/components/infinite-scroll/infinite-scroll.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index 0cba70d9c5..72fc71189c 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -276,7 +276,7 @@ export class InfiniteScroll { * Pass a promise inside `waitFor()` within the `infinite` output event handler in order to * change state of infiniteScroll to "complete" */ - waitFor(action: Promise) { + waitFor(action: Promise) { const enable = this.complete.bind(this); action.then(enable, enable); } From f4c9ba6614b23adb44354e5cb5894912c8e5b3ef Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Tue, 7 Mar 2017 19:02:23 +0100 Subject: [PATCH 18/43] test(datetime): min/max failing cases --- .../datetime/test/issues/app.module.ts | 33 ++++++++++++++ src/components/datetime/test/issues/main.html | 45 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/components/datetime/test/issues/app.module.ts create mode 100644 src/components/datetime/test/issues/main.html diff --git a/src/components/datetime/test/issues/app.module.ts b/src/components/datetime/test/issues/app.module.ts new file mode 100644 index 0000000000..1e0379b623 --- /dev/null +++ b/src/components/datetime/test/issues/app.module.ts @@ -0,0 +1,33 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule } from '../../../../../ionic-angular'; + + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EPage { +} + + +@Component({ + template: '' +}) +export class E2EApp { + root = E2EPage; +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage + ], + imports: [ + IonicModule.forRoot(E2EApp) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EPage + ] +}) +export class AppModule {} diff --git a/src/components/datetime/test/issues/main.html b/src/components/datetime/test/issues/main.html new file mode 100644 index 0000000000..f1aecb3828 --- /dev/null +++ b/src/components/datetime/test/issues/main.html @@ -0,0 +1,45 @@ + + + + Datetime + + + + + + + + + + min="2017-07-01" + max="2017-12-01" + + + + + + + min="2017-01-01" + max="2017-12-01" + + + + + + + min="2017-01-15" + max="2017-01-30" + + + + + From ba3530657beb83612807511fff45a6efea289881 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Tue, 7 Mar 2017 19:02:33 +0100 Subject: [PATCH 19/43] fix(picker): selectionIndex always initialized --- src/components/datetime/datetime.ts | 1 + src/components/picker/picker-component.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/datetime/datetime.ts b/src/components/datetime/datetime.ts index aa1d5b4b02..477b8cbbf7 100644 --- a/src/components/datetime/datetime.ts +++ b/src/components/datetime/datetime.ts @@ -563,6 +563,7 @@ export class DateTime extends Ion implements AfterContentInit, ControlValueAcces let column: PickerColumn = { name: key, + selectedIndex: 0, options: values.map(val => { return { value: val, diff --git a/src/components/picker/picker-component.ts b/src/components/picker/picker-component.ts index 313fc9ac33..8f72414629 100644 --- a/src/components/picker/picker-component.ts +++ b/src/components/picker/picker-component.ts @@ -512,7 +512,7 @@ export class PickerCmp { if (!isPresent(column.options)) { column.options = []; } - + column.selectedIndex = 0; column.options = column.options.map(inputOpt => { let opt: PickerColumnOption = { text: '', From 67cbcdea3be898f71fbb54d8aa7c935493956cbc Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 9 Mar 2017 09:35:59 -0600 Subject: [PATCH 20/43] chore(toolbar): move button strong to toolbar sass property --- src/components/toolbar/toolbar.ios.scss | 5 ++++- src/components/toolbar/toolbar.md.scss | 5 ++++- src/components/toolbar/toolbar.wp.scss | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/toolbar/toolbar.ios.scss b/src/components/toolbar/toolbar.ios.scss index b6698d5739..c23bf7cd8b 100644 --- a/src/components/toolbar/toolbar.ios.scss +++ b/src/components/toolbar/toolbar.ios.scss @@ -36,6 +36,9 @@ $toolbar-ios-button-color: color-contrast($colors-ios, $toolbar /// @prop - Border radius of the toolbar button $toolbar-ios-button-border-radius: 4px !default; +/// @prop - Font weight of the strong toolbar button +$toolbar-ios-button-strong-font-weight: 600 !default; + /// @prop - Height of the navigation bar $navbar-ios-height: $toolbar-ios-height !default; @@ -378,5 +381,5 @@ $navbar-ios-height: $toolbar-ios-height !default; // -------------------------------------------------- .bar-button-strong-ios { - font-weight: $button-ios-strong-font-weight; + font-weight: $toolbar-ios-button-strong-font-weight; } diff --git a/src/components/toolbar/toolbar.md.scss b/src/components/toolbar/toolbar.md.scss index 8cb295d52e..b4be73d565 100644 --- a/src/components/toolbar/toolbar.md.scss +++ b/src/components/toolbar/toolbar.md.scss @@ -30,6 +30,9 @@ $toolbar-md-button-color: $toolbar-md-title-text-color !default; /// @prop - Border radius of the toolbar button $toolbar-md-button-border-radius: 2px !default; +/// @prop - Font weight of the strong toolbar button +$toolbar-md-button-strong-font-weight: bold !default; + /// @prop - Height of the navigation bar $navbar-md-height: $toolbar-md-height !default; @@ -396,5 +399,5 @@ $navbar-md-height: $toolbar-md-height !default; // -------------------------------------------------- .bar-button-strong-md { - font-weight: $button-md-strong-font-weight; + font-weight: $toolbar-md-button-strong-font-weight; } diff --git a/src/components/toolbar/toolbar.wp.scss b/src/components/toolbar/toolbar.wp.scss index 5fa35c45cc..3dc8d9a4ad 100644 --- a/src/components/toolbar/toolbar.wp.scss +++ b/src/components/toolbar/toolbar.wp.scss @@ -39,6 +39,9 @@ $toolbar-wp-button-color: color-contrast($colors-wp, $toolbar-wp- /// @prop - Border radius of the toolbar button $toolbar-wp-button-border-radius: 2px !default; +/// @prop - Font weight of the strong toolbar button +$toolbar-wp-button-strong-font-weight: bold !default; + /// @prop - Height of the navigation bar $navbar-wp-height: $toolbar-wp-height !default; @@ -350,5 +353,5 @@ $navbar-wp-height: $toolbar-wp-height !default; // -------------------------------------------------- .bar-button-strong-wp { - font-weight: $button-wp-strong-font-weight; + font-weight: $toolbar-wp-button-strong-font-weight; } From 446e468b59312d90eac1c190ecf6e794f11a8dc3 Mon Sep 17 00:00:00 2001 From: alex-pl Date: Fri, 10 Mar 2017 16:41:32 +0100 Subject: [PATCH 21/43] docs(split-pane): copy editing (#10708) Fixed typo in word "also". --- src/components/split-pane/split-pane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/split-pane/split-pane.ts b/src/components/split-pane/split-pane.ts index 4c931470f7..9ee6620b2b 100644 --- a/src/components/split-pane/split-pane.ts +++ b/src/components/split-pane/split-pane.ts @@ -172,7 +172,7 @@ export class SplitPane extends Ion implements RootNode { /** * @input {string | boolean} When the split-pane should be shown. * Can be a CSS media query expression, or a shortcut expression. - * Can aslo be a boolean expression. + * Can also be a boolean expression. */ @Input() set when(query: string | boolean) { From 8c483f25293f231a4e815444361a2ac950bb1dc9 Mon Sep 17 00:00:00 2001 From: Ibrahim Ghazal Date: Tue, 14 Mar 2017 12:42:12 +0300 Subject: [PATCH 22/43] fix(select): make floating labels work for ion-select fixes #10751 --- src/components/label/label.scss | 4 ---- src/components/select/select.ts | 18 ++++++++++++++++++ .../select/test/multiple-value/main.html | 16 ++++++++++++++++ .../select/test/single-value/main.html | 8 ++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/components/label/label.scss b/src/components/label/label.scss index f81fa30dcb..6f3f7ebf8b 100644 --- a/src/components/label/label.scss +++ b/src/components/label/label.scss @@ -65,7 +65,3 @@ ion-label[floating] { max-width: 100%; } - -.item-select ion-label[floating] { - transform: translate3d(0, 0, 0) scale(.8); -} diff --git a/src/components/select/select.ts b/src/components/select/select.ts index fcf4c070a2..59e3a45754 100644 --- a/src/components/select/select.ts +++ b/src/components/select/select.ts @@ -387,6 +387,21 @@ export class Select extends Ion implements AfterContentInit, ControlValueAccesso return (this._multi ? this._texts : this._texts.join()); } + /** + * @private + */ + checkHasValue(inputValue: any) { + if (this._item) { + let hasValue: boolean; + if (Array.isArray(inputValue)) { + hasValue = inputValue.length > 0; + } else { + hasValue = !isBlank(inputValue); + } + this._item.setElementClass('input-has-value', hasValue); + } + } + /** * @private */ @@ -445,6 +460,7 @@ export class Select extends Ion implements AfterContentInit, ControlValueAccesso console.debug('select, writeValue', val); this._values = (Array.isArray(val) ? val : isBlank(val) ? [] : [val]); this._updOpts(); + this.checkHasValue(val); } /** @@ -464,6 +480,7 @@ export class Select extends Ion implements AfterContentInit, ControlValueAccesso fn(val); this._values = (Array.isArray(val) ? val : isBlank(val) ? [] : [val]); this._updOpts(); + this.checkHasValue(val); this.onTouched(); }; } @@ -481,6 +498,7 @@ export class Select extends Ion implements AfterContentInit, ControlValueAccesso console.debug('select, onChange w/out formControlName', val); this._values = (Array.isArray(val) ? val : isBlank(val) ? [] : [val]); this._updOpts(); + this.checkHasValue(val); this.onTouched(); } diff --git a/src/components/select/test/multiple-value/main.html b/src/components/select/test/multiple-value/main.html index d68b7cbf1b..cdf5b17269 100644 --- a/src/components/select/test/multiple-value/main.html +++ b/src/components/select/test/multiple-value/main.html @@ -83,4 +83,20 @@ + + Floating label + + Bacon + Black Olives + Extra Cheese + Green Peppers + Mushrooms + Onions + Pepperoni + Pineapple + Sausage + Spinach + + + diff --git a/src/components/select/test/single-value/main.html b/src/components/select/test/single-value/main.html index dcafc93d36..7db4558dbd 100644 --- a/src/components/select/test/single-value/main.html +++ b/src/components/select/test/single-value/main.html @@ -137,4 +137,12 @@ + + Floating label + + Female + Male + + + From f9f9a1b44159e183d514461a049aeeeda5859c99 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Tue, 14 Mar 2017 22:14:01 +0100 Subject: [PATCH 23/43] test(content): fix unit test by using new elementRef api --- src/components/content/content.ts | 2 +- src/components/img/test/img.spec.ts | 7 ++++--- .../infinite-scroll/test/infinite-scroll.spec.ts | 7 ++++--- src/components/refresher/test/refresher.spec.ts | 11 ++++------- src/util/mock-providers.ts | 11 +++++++++-- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/components/content/content.ts b/src/components/content/content.ts index 0d5ea2df82..91147b2af3 100644 --- a/src/components/content/content.ts +++ b/src/components/content/content.ts @@ -756,7 +756,7 @@ export class Content extends Ion implements OnDestroy, OnInit { const scrollEle = this.getScrollElement(); if (!scrollEle) { - assert(false, 'this._scrollEle should be valid'); + assert(false, 'this.getScrollElement() should be valid'); return; } diff --git a/src/components/img/test/img.spec.ts b/src/components/img/test/img.spec.ts index 1e89d46120..227cace262 100644 --- a/src/components/img/test/img.spec.ts +++ b/src/components/img/test/img.spec.ts @@ -2,7 +2,7 @@ import { ElementRef, Renderer } from '@angular/core'; import { Content } from '../../content/content'; import { DomController } from '../../../platform/dom-controller'; import { Img } from '../img'; -import { mockConfig, mockDomController, mockElementRef, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers'; +import { mockConfig, mockDomController, mockElementRef, mockElementRefEle, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers'; import { Platform } from '../../../platform/platform'; @@ -60,8 +60,9 @@ describe('Img', () => { contentElementRef = mockElementRef(); dom = mockDomController(); content = new Content(mockConfig(), mockPlatform(), dom, contentElementRef, mockRenderer(), null, null, mockZone(), null, null); - content._scrollEle = document.createElement('div'); - content._scrollEle.className = 'scroll-content'; + let ele = document.createElement('div'); + ele.className = 'scroll-content'; + content._scrollContent = mockElementRefEle(ele); elementRef = mockElementRef(); renderer = mockRenderer(); diff --git a/src/components/infinite-scroll/test/infinite-scroll.spec.ts b/src/components/infinite-scroll/test/infinite-scroll.spec.ts index ccf4ca299a..653d41d2e0 100644 --- a/src/components/infinite-scroll/test/infinite-scroll.spec.ts +++ b/src/components/infinite-scroll/test/infinite-scroll.spec.ts @@ -1,7 +1,7 @@ import { Content, ScrollEvent } from '../../content/content'; import { DomController } from '../../../platform/dom-controller'; import { InfiniteScroll } from '../infinite-scroll'; -import { mockConfig, mockDomController, mockElementRef, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers'; +import { mockConfig, mockDomController, mockElementRef, mockElementRefEle, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers'; describe('Infinite Scroll', () => { @@ -104,8 +104,9 @@ describe('Infinite Scroll', () => { contentElementRef = mockElementRef(); dom = mockDomController(); content = new Content(config, mockPlatform(), dom, contentElementRef, mockRenderer(), null, null, mockZone(), null, null); - content._scrollEle = document.createElement('div'); - content._scrollEle.className = 'scroll-content'; + let ele = document.createElement('div'); + ele.className = 'scroll-content'; + content._scrollContent = mockElementRefEle(ele); infiniteElementRef = mockElementRef(); diff --git a/src/components/refresher/test/refresher.spec.ts b/src/components/refresher/test/refresher.spec.ts index 824a214979..6d787c6de8 100644 --- a/src/components/refresher/test/refresher.spec.ts +++ b/src/components/refresher/test/refresher.spec.ts @@ -1,7 +1,7 @@ import { Refresher } from '../refresher'; import { Content } from '../../content/content'; import { GestureController } from '../../../gestures/gesture-controller'; -import { mockConfig, mockDomController, mockElementRef, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers'; +import { mockConfig, mockDomController, mockElementRef, mockElementRefEle, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers'; describe('Refresher', () => { @@ -234,8 +234,9 @@ describe('Refresher', () => { contentElementRef = mockElementRef(); dom = mockDomController(); content = new Content(mockConfig(), mockPlatform(), dom, contentElementRef, mockRenderer(), null, null, mockZone(), null, null); - content._scrollEle = document.createElement('div'); - content._scrollEle.className = 'scroll-content'; + let ele = document.createElement('div'); + ele.className = 'scroll-content'; + content._scrollContent = mockElementRefEle(ele); let gestureController = new GestureController(null); @@ -270,8 +271,4 @@ describe('Refresher', () => { }; } - // function getScrollElementStyles() { - // return content._scrollEle.style; - // } - }); diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts index 1bd1d33c69..5ec84974bc 100644 --- a/src/util/mock-providers.ts +++ b/src/util/mock-providers.ts @@ -229,7 +229,10 @@ export function mockChangeDetectorRef(): ChangeDetectorRef { } export class MockElementRef implements ElementRef { - nativeElement: any = new MockElement(); + nativeElement: any; + constructor(ele: any) { + this.nativeElement = ele; + } } export class MockElement { @@ -299,7 +302,11 @@ export class ClassList { } export function mockElementRef(): ElementRef { - return new MockElementRef(); + return new MockElementRef(new MockElement()); +} + +export function mockElementRefEle(ele: any): ElementRef { + return new MockElementRef(ele); } export class MockRenderer { From 6918275bd369db661c95d6f7f2857b18d3bf9c1d Mon Sep 17 00:00:00 2001 From: Job Date: Wed, 15 Mar 2017 14:58:45 +0100 Subject: [PATCH 24/43] feat(infinite): add scroll in opposite direction (#8099) * feat(infinite): add scroll in opposite direction fixes * test(infinite-scroll): opposite direction e2e test * fix(infinite-scroll): keep scroll position * feat(content): scroll down on load * fix(infinite-scroll): scroll the content down on load * Requested changes --- src/components/content/content.ts | 22 ++++- .../test/scroll-down-on-load/app.module.ts | 32 +++++++ .../test/scroll-down-on-load/main.html | 40 ++++++++ .../infinite-scroll/infinite-scroll.ts | 65 +++++++++++-- .../test/infinite-scroll.spec.ts | 23 +++++ .../test/position-top/app.module.ts | 95 +++++++++++++++++++ .../test/position-top/main.html | 31 ++++++ 7 files changed, 298 insertions(+), 10 deletions(-) create mode 100644 src/components/content/test/scroll-down-on-load/app.module.ts create mode 100644 src/components/content/test/scroll-down-on-load/main.html create mode 100644 src/components/infinite-scroll/test/position-top/app.module.ts create mode 100644 src/components/infinite-scroll/test/position-top/main.html diff --git a/src/components/content/content.ts b/src/components/content/content.ts index 91147b2af3..2a9101ee2b 100644 --- a/src/components/content/content.ts +++ b/src/components/content/content.ts @@ -170,6 +170,8 @@ export class Content extends Ion implements OnDestroy, OnInit { _viewCtrlReadSub: any; /** @internal */ _viewCtrlWriteSub: any; + /** @internal */ + _scrollDownOnLoad: boolean = false; private _imgReqBfr: number; private _imgRndBfr: number; @@ -478,13 +480,25 @@ export class Content extends Ion implements OnDestroy, OnInit { */ @Input() get fullscreen(): boolean { - return !!this._fullscreen; + return this._fullscreen; } set fullscreen(val: boolean) { this._fullscreen = isTrueProperty(val); } + /** + * @input {boolean} If true, the content will scroll down on load. + */ + @Input() + get scrollDownOnLoad(): boolean { + return this._scrollDownOnLoad; + } + + set scrollDownOnLoad(val: boolean) { + this._scrollDownOnLoad = isTrueProperty(val); + } + /** * @private */ @@ -830,6 +844,12 @@ export class Content extends Ion implements OnDestroy, OnInit { this._tabs.setTabbarPosition(-1, 0); } } + + // Scroll the page all the way down after setting dimensions + if (this._scrollDownOnLoad) { + this.scrollToBottom(0); + this._scrollDownOnLoad = false; + } } /** diff --git a/src/components/content/test/scroll-down-on-load/app.module.ts b/src/components/content/test/scroll-down-on-load/app.module.ts new file mode 100644 index 0000000000..ce9f7a3be7 --- /dev/null +++ b/src/components/content/test/scroll-down-on-load/app.module.ts @@ -0,0 +1,32 @@ +import { Component, NgModule } from '@angular/core'; +import { IonicApp, IonicModule } from '../../../../../ionic-angular'; + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EPage {} + + +@Component({ + template: '' +}) +export class E2EApp { + root = E2EPage; +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage, + ], + imports: [ + IonicModule.forRoot(E2EApp) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage, + ] +}) +export class AppModule {} diff --git a/src/components/content/test/scroll-down-on-load/main.html b/src/components/content/test/scroll-down-on-load/main.html new file mode 100644 index 0000000000..de8c7d97d6 --- /dev/null +++ b/src/components/content/test/scroll-down-on-load/main.html @@ -0,0 +1,40 @@ + + This page should scroll down on load +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi scelerisque dolor lacus, ut vehicula arcu dapibus id. Morbi iaculis fermentum blandit. Curabitur tempus, ante et vehicula tempor, urna velit rutrum massa, quis suscipit purus lacus eget est. Sed nisi nulla, tempus id dictum a, cursus ut felis. Aliquam orci magna, rutrum nec tempor ac, fermentum quis eros. Sed ullamcorper felis sit amet tristique sagittis. Nullam sed tempus mi. Morbi sit amet lacinia leo. Nunc facilisis orci id consectetur dignissim. Integer dictum consectetur enim. Vivamus auctor, turpis ut eleifend pharetra, purus magna mattis arcu, vel pharetra tellus orci eget ex. Integer blandit posuere vehicula. Ut ipsum lorem, efficitur vitae eleifend tincidunt, fermentum nec lacus. Ut nec fermentum dui. +

+ It worked! +
diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index 72fc71189c..3da7af31e2 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -8,7 +8,7 @@ import { DomController } from '../../platform/dom-controller'; * @name InfiniteScroll * @description * The Infinite Scroll allows you to perform an action when the user - * scrolls a specified distance from the bottom of the page. + * scrolls a specified distance from the bottom or top of the page. * * The expression assigned to the `infinite` event is called when * the user scrolls to the specified distance. When this expression @@ -148,6 +148,7 @@ export class InfiniteScroll { _thr: string = '15%'; _thrPx: number = 0; _thrPc: number = 0.15; + _position: string = POSITION_BOTTOM; _init: boolean = false; @@ -192,6 +193,23 @@ export class InfiniteScroll { this.enable(shouldEnable); } + /** + * @input {string} The position of the infinite scroll element. + * The value can be either `top` or `bottom`. + * Default is `bottom`. + */ + @Input() + get position(): string { + return this._position; + } + set position(val: string) { + if (val === POSITION_TOP || val === POSITION_BOTTOM) { + this._position = val; + } else { + console.error(`Invalid value for ion-infinite-scroll's position input. Its value should be '${POSITION_BOTTOM}' or '${POSITION_TOP}'.`); + } + } + /** * @output {event} Emitted when the scroll reaches * the threshold distance. From within your infinite handler, @@ -229,17 +247,20 @@ export class InfiniteScroll { // ******** DOM READ **************** const d = this._content.getContentDimensions(); + const height = d.contentHeight; - let reloadY = d.contentHeight; - if (this._thrPc) { - reloadY += (reloadY * this._thrPc); - } else { - reloadY += this._thrPx; - } + const threshold = this._thrPc ? (height * this._thrPc) : this._thrPx; // ******** DOM READS ABOVE / DOM WRITES BELOW **************** - const distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - reloadY; + let distanceFromInfinite: number; + + if (this._position === POSITION_BOTTOM) { + distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - height - threshold; + } else if (this._position === POSITION_TOP) { + distanceFromInfinite = d.scrollTop - infiniteHeight - threshold; + } + if (distanceFromInfinite < 0) { // ******** DOM WRITE **************** this._dom.write(() => { @@ -267,7 +288,26 @@ export class InfiniteScroll { * to `enabled`. */ complete() { - if (this.state === STATE_LOADING) { + if (this._position === POSITION_TOP) { + // ******** DOM READ **************** + // Save the current content dimensions before the UI updates + const prevDim = this._content.getContentDimensions(); + + // ******** DOM READ **************** + this._dom.read(() => { + // UI has updated, save the new content dimensions + const newDim = this._content.getContentDimensions(); + + // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around + const newScrollTop = newDim.scrollHeight - (prevDim.scrollHeight - prevDim.scrollTop); + + // ******** DOM WRITE **************** + this._dom.write(() => { + this._content.scrollTop = newScrollTop; + this.state = STATE_ENABLED; + }); + }); + } else { this.state = STATE_ENABLED; } } @@ -319,6 +359,10 @@ export class InfiniteScroll { ngAfterContentInit() { this._init = true; this._setListeners(this.state !== STATE_DISABLED); + + if (this._position === POSITION_TOP) { + this._content.scrollDownOnLoad = true; + } } /** @@ -333,3 +377,6 @@ export class InfiniteScroll { const STATE_ENABLED = 'enabled'; const STATE_DISABLED = 'disabled'; const STATE_LOADING = 'loading'; + +const POSITION_TOP = 'top'; +const POSITION_BOTTOM = 'bottom'; diff --git a/src/components/infinite-scroll/test/infinite-scroll.spec.ts b/src/components/infinite-scroll/test/infinite-scroll.spec.ts index 653d41d2e0..4eb4bb49e8 100644 --- a/src/components/infinite-scroll/test/infinite-scroll.spec.ts +++ b/src/components/infinite-scroll/test/infinite-scroll.spec.ts @@ -91,6 +91,29 @@ describe('Infinite Scroll', () => { }); + describe('position', () => { + + it('should default to bottom', () => { + expect(inf._position).toEqual('bottom'); + }); + + it('should set to top', () => { + inf.position = 'top'; + expect(inf._position).toEqual('top'); + }); + + it('should set to bottom', () => { + inf.position = 'bottom'; + expect(inf._position).toEqual('bottom'); + }); + + it('should not set to anything else', () => { + inf.position = 'derp'; + expect(inf._position).toEqual('bottom'); + }); + + }); + let config = mockConfig(); let inf: InfiniteScroll; diff --git a/src/components/infinite-scroll/test/position-top/app.module.ts b/src/components/infinite-scroll/test/position-top/app.module.ts new file mode 100644 index 0000000000..f1d8ac8f68 --- /dev/null +++ b/src/components/infinite-scroll/test/position-top/app.module.ts @@ -0,0 +1,95 @@ +import { Component, ViewChild, NgModule } from '@angular/core'; +import { Content, IonicApp, IonicModule, InfiniteScroll, NavController } from '../../../../../ionic-angular'; + + +@Component({ + templateUrl: 'main.html' +}) +export class E2EPage1 { + @ViewChild(InfiniteScroll) infiniteScroll: InfiniteScroll; + @ViewChild(Content) content: Content; + items: number[] = []; + enabled: boolean = true; + + constructor(public navCtrl: NavController) { + for (var i = 0; i < 30; i++) { + this.items.unshift( this.items.length ); + } + } + + doInfinite(infiniteScroll: InfiniteScroll) { + console.log('Begin async operation'); + + getAsyncData().then(newData => { + for (var i = 0; i < newData.length; i++) { + this.items.unshift( this.items.length ); + } + + console.log('Finished receiving data, async operation complete'); + infiniteScroll.complete(); + + if (this.items.length > 90) { + this.enabled = false; + } + }); + } + + goToPage2() { + this.navCtrl.push(E2EPage2); + } + + toggleInfiniteScroll() { + this.enabled = !this.enabled; + } +} + + +@Component({ + template: '' +}) +export class E2EPage2 { + constructor(public navCtrl: NavController) {} +} + + +@Component({ + template: '' +}) +export class E2EApp { + root = E2EPage1; +} + +@NgModule({ + declarations: [ + E2EApp, + E2EPage1, + E2EPage2 + ], + imports: [ + IonicModule.forRoot(E2EApp) + ], + bootstrap: [IonicApp], + entryComponents: [ + E2EApp, + E2EPage1, + E2EPage2 + ] +}) +export class AppModule {} + + +function getAsyncData(): Promise { + // async return mock data + return new Promise(resolve => { + + setTimeout(() => { + let data: number[] = []; + for (var i = 0; i < 30; i++) { + data.unshift(i); + } + + resolve(data); + }, 2000); + + }); +} diff --git a/src/components/infinite-scroll/test/position-top/main.html b/src/components/infinite-scroll/test/position-top/main.html new file mode 100644 index 0000000000..d8c27c8acb --- /dev/null +++ b/src/components/infinite-scroll/test/position-top/main.html @@ -0,0 +1,31 @@ + + + + Infinite Scroll + + + + + + + + + + + + + + + + +

+ InfiniteScroll is enabled: {{enabled}} +

+ + + +
From e53bad1bc30d5ffee3707252b1eb6ae62db51f5a Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Wed, 15 Mar 2017 15:06:42 +0100 Subject: [PATCH 25/43] docs(infinity-scroll): algorithm used when position is Top --- .../infinite-scroll/infinite-scroll.ts | 68 +++++++++++++------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index 3da7af31e2..4b68ad962f 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -2,7 +2,7 @@ import { Directive, ElementRef, EventEmitter, Host, Input, NgZone, Output } from import { Content, ScrollEvent } from '../content/content'; import { DomController } from '../../platform/dom-controller'; - +import { assert } from '../../util/util'; /** * @name InfiniteScroll @@ -257,7 +257,8 @@ export class InfiniteScroll { if (this._position === POSITION_BOTTOM) { distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - height - threshold; - } else if (this._position === POSITION_TOP) { + } else { + assert(this._position === POSITION_TOP, '_position should be top'); distanceFromInfinite = d.scrollTop - infiniteHeight - threshold; } @@ -288,28 +289,51 @@ export class InfiniteScroll { * to `enabled`. */ complete() { - if (this._position === POSITION_TOP) { - // ******** DOM READ **************** - // Save the current content dimensions before the UI updates - const prevDim = this._content.getContentDimensions(); - - // ******** DOM READ **************** - this._dom.read(() => { - // UI has updated, save the new content dimensions - const newDim = this._content.getContentDimensions(); - - // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around - const newScrollTop = newDim.scrollHeight - (prevDim.scrollHeight - prevDim.scrollTop); - - // ******** DOM WRITE **************** - this._dom.write(() => { - this._content.scrollTop = newScrollTop; - this.state = STATE_ENABLED; - }); - }); - } else { + if (this._position === POSITION_BOTTOM) { this.state = STATE_ENABLED; + return; } + + assert(this._position === POSITION_TOP, 'position should be top'); + /* New content is being added at the top, but the scrollTop position stays the same, + which causes a scroll jump visually. This algorithm makes sure to prevent this. + + (Frame 1) + complete() is called, but the UI hasn't had time to update yet. + Save the current content dimensions. + Wait for the next frame using _dom.read, so the UI will be updated. + + (Frame 2) + Read the new content dimensions. + Calculate the height difference and the new scroll position. + Delay the scroll position change until other possible dom reads are done using _dom.write to be performant. + + (Still frame 2, if I'm correct) + Change the scroll position (= visually maintain the scroll position). + Change the state to re-enable the InfiniteScroll. This should be after changing the scroll position, or it could cause the InfiniteScroll to be triggered again immediately. + + (Frame 3) + Done. + */ + + // ******** DOM READ **************** + // Save the current content dimensions before the UI updates + const prevDim = this._content.getContentDimensions(); + + // ******** DOM READ **************** + this._dom.read(() => { + // UI has updated, save the new content dimensions + const newDim = this._content.getContentDimensions(); + + // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around + const newScrollTop = newDim.scrollHeight - (prevDim.scrollHeight - prevDim.scrollTop); + + // ******** DOM WRITE **************** + this._dom.write(() => { + this._content.scrollTop = newScrollTop; + this.state = STATE_ENABLED; + }); + }); } /** From 46fe1ff53cfc2cfef152b560d01882eebca8c678 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Wed, 15 Mar 2017 16:48:15 +0100 Subject: [PATCH 26/43] fix(alert): inputs have id fixes #10603 --- src/components/alert/alert-component.ts | 23 +++++++------------ src/components/alert/alert-options.ts | 13 ++++++++--- src/components/alert/alert.ts | 4 ++-- src/components/alert/test/basic/app.module.ts | 1 + 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/alert/alert-component.ts b/src/components/alert/alert-component.ts index 9199169853..86afcde1ce 100644 --- a/src/components/alert/alert-component.ts +++ b/src/components/alert/alert-component.ts @@ -9,6 +9,7 @@ import { NavParams } from '../../navigation/nav-params'; import { NavOptions } from '../../navigation/nav-util'; import { Platform } from '../../platform/platform'; import { ViewController } from '../../navigation/view-controller'; +import { AlertInputOptions, AlertOptions, AlertButton } from './alert-options'; /** @@ -39,7 +40,7 @@ import { ViewController } from '../../navigation/view-controller'; '