From 2e1bb4bbde9f1f559fa4e684c70fb9ec76001d11 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Thu, 22 Sep 2016 16:14:12 +0200 Subject: [PATCH 1/5] fix(searchbar): ENTER does not clear when it is inside a form fixes #7010 --- src/components/searchbar/searchbar.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/searchbar/searchbar.ts b/src/components/searchbar/searchbar.ts index 13358193c89..3c77aa472d6 100644 --- a/src/components/searchbar/searchbar.ts +++ b/src/components/searchbar/searchbar.ts @@ -30,14 +30,14 @@ import { Debouncer } from '../../util/debouncer'; selector: 'ion-searchbar', template: '
' + - '' + '
' + '' + - '' + + '' + '
' + - '', + '', host: { '[class.searchbar-has-value]': '_value', '[class.searchbar-active]': '_isActive', From d03182e34e65c624904d8a4289916da0e76c6902 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Thu, 22 Sep 2016 16:15:24 +0200 Subject: [PATCH 2/5] perf(searchbar): searchbar animation is disabled by default it can be reenabled by`` fixes #6023 --- src/components/searchbar/searchbar.ios.scss | 49 +++++++----- src/components/searchbar/searchbar.scss | 1 + src/components/searchbar/searchbar.ts | 78 ++++++++++++------- src/components/searchbar/test/basic/main.html | 3 + src/components/searchbar/test/nav/search.html | 5 +- 5 files changed, 84 insertions(+), 52 deletions(-) diff --git a/src/components/searchbar/searchbar.ios.scss b/src/components/searchbar/searchbar.ios.scss index b1cea8f18fb..697e5431f87 100644 --- a/src/components/searchbar/searchbar.ios.scss +++ b/src/components/searchbar/searchbar.ios.scss @@ -66,8 +66,6 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; background-repeat: no-repeat; background-size: $searchbar-ios-input-search-icon-size; - - transition: $searchbar-ios-input-transition; } @@ -87,8 +85,6 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; color: $searchbar-ios-input-text-color; background-color: $searchbar-ios-input-background-color; - - transition: $searchbar-ios-input-transition; } @@ -119,7 +115,6 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; flex-shrink: 0; - margin-right: -100%; margin-left: 0; padding: 0; padding-left: 8px; @@ -127,20 +122,8 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; height: 30px; cursor: pointer; - - opacity: 0; - - transform: translate3d(0, 0, 0); - transition: $searchbar-ios-cancel-transition; - - pointer-events: none; } -.searchbar-ios.searchbar-show-cancel .searchbar-ios-cancel { - display: block; -} - - // Searchbar Left Aligned (iOS only) // ----------------------------------------- @@ -156,10 +139,8 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; // Searchbar Has Focus // ----------------------------------------- -.searchbar-ios.searchbar-has-focus .searchbar-ios-cancel { - opacity: 1; - - pointer-events: auto; +.searchbar-ios.searchbar-show-cancel.searchbar-has-focus .searchbar-ios-cancel { + display: block; } @@ -225,3 +206,29 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; } } + +// Searchbar animation +// ----------------------------------------- + +.searchbar-ios.searchbar-animated .searchbar-search-icon, +.searchbar-ios.searchbar-animated .searchbar-input { + transition: $searchbar-ios-input-transition; +} + +.searchbar-animated.searchbar-has-focus .searchbar-ios-cancel { + opacity: 1; + + pointer-events: auto; +} + +.searchbar-animated .searchbar-ios-cancel { + display: block; + + margin-right: -100%; + + opacity: 0; + transform: translate3d(0, 0, 0); + transition: $searchbar-ios-cancel-transition; + + pointer-events: none; +} diff --git a/src/components/searchbar/searchbar.scss b/src/components/searchbar/searchbar.scss index 9d68e30b5cc..0a67f402dde 100644 --- a/src/components/searchbar/searchbar.scss +++ b/src/components/searchbar/searchbar.scss @@ -48,3 +48,4 @@ ion-searchbar { .searchbar-has-value.searchbar-has-focus .searchbar-clear-icon { display: block; } + diff --git a/src/components/searchbar/searchbar.ts b/src/components/searchbar/searchbar.ts index 3c77aa472d6..03377ba02b2 100644 --- a/src/components/searchbar/searchbar.ts +++ b/src/components/searchbar/searchbar.ts @@ -3,7 +3,7 @@ import { NgControl } from '@angular/forms'; import { Config } from '../../config/config'; import { Ion } from '../ion'; -import { isPresent } from '../../util/util'; +import { isPresent, isTrueProperty } from '../../util/util'; import { Debouncer } from '../../util/debouncer'; @@ -39,16 +39,19 @@ import { Debouncer } from '../../util/debouncer'; '' + '', host: { + '[class.searchbar-animated]': 'animated', '[class.searchbar-has-value]': '_value', '[class.searchbar-active]': '_isActive', '[class.searchbar-show-cancel]': 'showCancelButton', - '[class.searchbar-left-aligned]': 'shouldAlignLeft()' + '[class.searchbar-left-aligned]': '_shouldAlignLeft' }, encapsulation: ViewEncapsulation.None }) export class Searchbar extends Ion { _value: string|number = ''; _shouldBlur: boolean = true; + _shouldAlignLeft: boolean = true; + _isCancelVisible: boolean = false; _isActive: boolean = false; _searchbarInput: ElementRef; _debouncer: Debouncer = new Debouncer(250); @@ -115,6 +118,11 @@ export class Searchbar extends Ion { */ @Input() type: string = 'search'; + /** + * @input {string|boolean} Set the input's spellcheck property. Values: `true`, `false`. Default `false`. + */ + @Input() animated: string | boolean = false; + /** * @output {event} When the Searchbar input has changed including cleared. */ @@ -217,7 +225,7 @@ export class Searchbar extends Ion { * @private * After View Checked position the elements */ - ngAfterViewChecked() { + ngAfterContentInit() { this.positionElements(); } @@ -227,26 +235,31 @@ export class Searchbar extends Ion { * based on the input value and if it is focused. (ios only) */ positionElements() { - if (this._config.get('mode') !== 'ios') return; + let isAnimated = isTrueProperty(this.animated); + let prevAlignLeft = this._shouldAlignLeft; + let shouldAlignLeft = (!isAnimated || (this._value && this._value.toString().trim() !== '') || this._sbHasFocus === true); + this._shouldAlignLeft = shouldAlignLeft; - // Position the input placeholder & search icon - if (this._searchbarInput && this._searchbarIcon) { - this.positionInputPlaceholder(this._searchbarInput.nativeElement, this._searchbarIcon.nativeElement); + if (this._config.get('mode') !== 'ios') { + return; } - // Position the cancel button - if (this._cancelButton && this._cancelButton.nativeElement) { - this.positionCancelButton(this._cancelButton.nativeElement); + if (prevAlignLeft !== shouldAlignLeft) { + this.positionPlaceholder(); + } + if (isAnimated) { + this.positionCancelButton(); } } - /** - * @private - * Calculates the amount of padding/margin left for the elements - * in order to center them based on the placeholder width - */ - positionInputPlaceholder(inputEle: HTMLElement, iconEle: HTMLElement) { - if (this.shouldAlignLeft()) { + positionPlaceholder() { + if (!this._searchbarInput || !this._searchbarIcon) { + return; + } + let inputEle = this._searchbarInput.nativeElement; + let iconEle = this._searchbarIcon.nativeElement; + + if (this._shouldAlignLeft) { inputEle.removeAttribute('style'); iconEle.removeAttribute('style'); } else { @@ -273,23 +286,26 @@ export class Searchbar extends Ion { * @private * Show the iOS Cancel button on focus, hide it offscreen otherwise */ - positionCancelButton(cancelButtonEle: HTMLElement) { - if (cancelButtonEle.offsetWidth > 0) { - if (this._sbHasFocus) { - cancelButtonEle.style.marginRight = '0'; + positionCancelButton() { + if (!this._cancelButton || !this._cancelButton.nativeElement) { + return; + } + let showShowCancel = this._sbHasFocus; + if (showShowCancel !== this._isCancelVisible) { + let cancelStyleEle = this._cancelButton.nativeElement; + let cancelStyle = cancelStyleEle.style; + this._isCancelVisible = showShowCancel; + if (showShowCancel) { + cancelStyle.marginRight = '0'; } else { - cancelButtonEle.style.marginRight = -cancelButtonEle.offsetWidth + 'px'; + let offset = cancelStyleEle.offsetWidth; + if (offset > 0) { + cancelStyle.marginRight = -offset + 'px'; + } } } } - /** - * @private - * Align the input placeholder left on focus or if a value exists - */ - shouldAlignLeft() { - return ( (this._value && this._value.toString().trim() !== '') || this._sbHasFocus === true ); - } /** * @private @@ -399,4 +415,8 @@ export class Searchbar extends Ion { registerOnTouched(fn: () => {}): void { this.onTouched = fn; } + + focus() { + this.getNativeElement().focus(); + } } diff --git a/src/components/searchbar/test/basic/main.html b/src/components/searchbar/test/basic/main.html index 02309d58241..682b34e8aed 100644 --- a/src/components/searchbar/test/basic/main.html +++ b/src/components/searchbar/test/basic/main.html @@ -2,6 +2,9 @@
Search - Default
+
Search - Animated
+ +

defaultSearch: {{ defaultSearch }}

diff --git a/src/components/searchbar/test/nav/search.html b/src/components/searchbar/test/nav/search.html index 8aa8be3a5bd..6daff2c3716 100644 --- a/src/components/searchbar/test/nav/search.html +++ b/src/components/searchbar/test/nav/search.html @@ -14,8 +14,9 @@ - - +
+ +
' + '
' + - '' + + '' + '' + '' + '', @@ -52,8 +58,10 @@ export class Searchbar extends Ion { _shouldBlur: boolean = true; _shouldAlignLeft: boolean = true; _isCancelVisible: boolean = false; + _spellcheck: boolean = false; + _autocomplete: string = 'off'; + _autocorrect: string = 'off'; _isActive: boolean = false; - _searchbarInput: ElementRef; _debouncer: Debouncer = new Debouncer(250); /** @@ -101,17 +109,26 @@ export class Searchbar extends Ion { /** * @input {string} Set the input's autocomplete property. Values: `"on"`, `"off"`. Default `"off"`. */ - @Input() autocomplete: string; + @Input() + set autocomplete(val: string) { + this._autocomplete = (val === '' || val === 'on') ? 'on' : this._config.get('autocomplete', 'off'); + } /** * @input {string} Set the input's autocorrect property. Values: `"on"`, `"off"`. Default `"off"`. */ - @Input() autocorrect: string; + @Input() + set autocorrect(val: string) { + this._autocorrect = (val === '' || val === 'on') ? 'on' : this._config.get('autocorrect', 'off'); + } /** * @input {string|boolean} Set the input's spellcheck property. Values: `true`, `false`. Default `false`. */ - @Input() spellcheck: string|boolean; + @Input() + set spellcheck(val: string | boolean) { + this._spellcheck = (val === '' || val === 'true' || val === true) ? true : this._config.getBoolean('spellcheck', false); + } /** * @input {string} Set the type of the input. Values: `"text"`, `"password"`, `"email"`, `"number"`, `"search"`, `"tel"`, `"url"`. Default `"search"`. @@ -169,30 +186,7 @@ export class Searchbar extends Ion { } } - /** - * @private - */ - @ViewChild('searchbarInput') - set searchbarInput(searchbarInput: ElementRef) { - this._searchbarInput = searchbarInput; - - let inputEle = searchbarInput.nativeElement; - - // By defalt set autocomplete="off" unless specified by the input - let autoComplete = (this.autocomplete === '' || this.autocomplete === 'on') ? 'on' : this._config.get('autocomplete', 'off'); - inputEle.setAttribute('autocomplete', autoComplete); - - // by default set autocorrect="off" unless specified by the input - let autoCorrect = (this.autocorrect === '' || this.autocorrect === 'on') ? 'on' : this._config.get('autocorrect', 'off'); - inputEle.setAttribute('autocorrect', autoCorrect); - - // by default set spellcheck="false" unless specified by the input - let spellCheck = (this.spellcheck === '' || this.spellcheck === 'true' || this.spellcheck === true) ? true : this._config.getBoolean('spellcheck', false); - inputEle.setAttribute('spellcheck', spellCheck); - - // by default set type="search" unless specified by the input - inputEle.setAttribute('type', this.type); - } + @ViewChild('searchbarInput') _searchbarInput: ElementRef; @ViewChild('searchbarIcon') _searchbarIcon: ElementRef; diff --git a/src/components/searchbar/test/basic/main.html b/src/components/searchbar/test/basic/main.html index 682b34e8aed..54eee134b75 100644 --- a/src/components/searchbar/test/basic/main.html +++ b/src/components/searchbar/test/basic/main.html @@ -1,6 +1,6 @@
Search - Default
- +
Search - Animated
From 6aaa6010a7912e067173d9c2bb2cc1aaf61130bd Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 26 Sep 2016 17:25:59 +0200 Subject: [PATCH 4/5] fix(searchbar): clear button makes keyboard dismissal fail on iOS fixes #7527 --- src/components/searchbar/searchbar.ts | 32 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/searchbar/searchbar.ts b/src/components/searchbar/searchbar.ts index 5275c21ab72..eb6d1218b73 100644 --- a/src/components/searchbar/searchbar.ts +++ b/src/components/searchbar/searchbar.ts @@ -34,13 +34,12 @@ import { Debouncer } from '../../util/debouncer'; '' + '' + '
' + - '' + + '[attr.spellcheck]="_spellcheck">' + '' + '' + '', @@ -202,6 +201,12 @@ export class Searchbar extends Ion { set value(val) { this._value = val; + if (this._searchbarInput) { + let ele = this._searchbarInput.nativeElement; + if (ele) { + ele.value = val; + } + } } /** @@ -306,9 +311,8 @@ export class Searchbar extends Ion { * Update the Searchbar input value when the input changes */ inputChanged(ev: any) { - let value = ev.target.value; + this._value = ev.target.value; this._debouncer.debounce(() => { - this._value = value; this.onChange(this._value); this.ionInput.emit(ev); }); @@ -352,12 +356,16 @@ export class Searchbar extends Ion { clearInput(ev: UIEvent) { this.ionClear.emit(ev); - if (isPresent(this._value) && this._value !== '') { - this._value = ''; - this.onChange(this._value); - this.ionInput.emit(ev); - } - + // setTimeout() fixes https://github.com/driftyco/ionic/issues/7527 + // way for 4 frames + setTimeout(() => { + let value = this._value; + if (isPresent(value) && value !== '') { + this.value = ''; // DOM WRITE + this.onChange(this._value); + this.ionInput.emit(ev); + } + }, 16 * 4); this._shouldBlur = false; } @@ -380,7 +388,7 @@ export class Searchbar extends Ion { * Write a new value to the element. */ writeValue(val: any) { - this._value = val; + this.value = val; this.positionElements(); } From 1a6c511ff7f900a7de9de82cee417973a1b32beb Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Mon, 26 Sep 2016 20:12:48 +0200 Subject: [PATCH 5/5] test(searchbar): searchbar + modal --- src/components/searchbar/searchbar.ts | 8 +++---- .../searchbar/test/nav/app-module.ts | 21 +++++++++++++++++-- src/components/searchbar/test/nav/modal.html | 21 +++++++++++++++++++ src/components/searchbar/test/nav/search.html | 10 +++++---- 4 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 src/components/searchbar/test/nav/modal.html diff --git a/src/components/searchbar/searchbar.ts b/src/components/searchbar/searchbar.ts index eb6d1218b73..f769e77a5b7 100644 --- a/src/components/searchbar/searchbar.ts +++ b/src/components/searchbar/searchbar.ts @@ -135,7 +135,7 @@ export class Searchbar extends Ion { @Input() type: string = 'search'; /** - * @input {string|boolean} Set the input's spellcheck property. Values: `true`, `false`. Default `false`. + * @input {string|boolean} Configures if the searchbar is animated or no. By default, animation is disabled. */ @Input() animated: string | boolean = false; @@ -357,7 +357,7 @@ export class Searchbar extends Ion { this.ionClear.emit(ev); // setTimeout() fixes https://github.com/driftyco/ionic/issues/7527 - // way for 4 frames + // wait for 4 frames setTimeout(() => { let value = this._value; if (isPresent(value) && value !== '') { @@ -418,7 +418,7 @@ export class Searchbar extends Ion { this.onTouched = fn; } - focus() { - this.getNativeElement().focus(); + setFocus() { + this._renderer.invokeElementMethod(this._searchbarInput.nativeElement, 'focus'); } } diff --git a/src/components/searchbar/test/nav/app-module.ts b/src/components/searchbar/test/nav/app-module.ts index f75d1de3fa7..946da836762 100644 --- a/src/components/searchbar/test/nav/app-module.ts +++ b/src/components/searchbar/test/nav/app-module.ts @@ -1,5 +1,5 @@ import { Component, NgModule } from '@angular/core'; -import { IonicApp, IonicModule, NavController, NavParams } from '../../../..'; +import { IonicApp, IonicModule, ModalController, NavController, NavParams, ViewController } from '../../../..'; @Component({ @@ -13,13 +13,23 @@ export class MainPage { } } +@Component({ + templateUrl: 'modal.html' +}) +export class ModalPage { + constructor(public viewCtrl: ViewController) {} + close() { + this.viewCtrl.dismiss(); + } +} + @Component({ templateUrl: 'search.html' }) export class SearchPage { items: string[]; - constructor(public navCtrl: NavController) { + constructor(public navCtrl: NavController, public modalCtrl: ModalController) { this.initializeItems(); } @@ -88,6 +98,11 @@ export class SearchPage { return false; }); } + + openModal() { + let modal = this.modalCtrl.create(ModalPage); + modal.present(); + } } @Component({ @@ -121,6 +136,7 @@ export class E2EApp { E2EApp, MainPage, SearchPage, + ModalPage, DetailPage, TabsPage ], @@ -132,6 +148,7 @@ export class E2EApp { E2EApp, MainPage, SearchPage, + ModalPage, DetailPage, TabsPage ] diff --git a/src/components/searchbar/test/nav/modal.html b/src/components/searchbar/test/nav/modal.html new file mode 100644 index 00000000000..7db91bfa285 --- /dev/null +++ b/src/components/searchbar/test/nav/modal.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + Close the modal with the button and the searchbar SHOULD NOT become focused. + + diff --git a/src/components/searchbar/test/nav/search.html b/src/components/searchbar/test/nav/search.html index 6daff2c3716..a3b301b4f05 100644 --- a/src/components/searchbar/test/nav/search.html +++ b/src/components/searchbar/test/nav/search.html @@ -1,17 +1,19 @@ - Searchbar + + - - + Searchbar + + + -