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 e0bb7510ea7..d7d8e27a07f 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'; @@ -30,27 +30,37 @@ import { Debouncer } from '../../util/debouncer'; selector: 'ion-searchbar', template: '
' + - '' + '
' + - '' + - '' + + '' + + '' + '
' + - '', + '', 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; + _spellcheck: boolean = false; + _autocomplete: string = 'off'; + _autocorrect: string = 'off'; _isActive: boolean = false; - _searchbarInput: ElementRef; _debouncer: Debouncer = new Debouncer(250); /** @@ -98,23 +108,37 @@ 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"`. */ @Input() type: string = 'search'; + /** + * @input {string|boolean} Configures if the searchbar is animated or no. By default, animation is disabled. + */ + @Input() animated: string | boolean = false; + /** * @output {event} When the Searchbar input has changed including cleared. */ @@ -161,30 +185,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; @@ -200,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; + } + } } /** @@ -217,7 +224,7 @@ export class Searchbar extends Ion { * @private * After View Checked position the elements */ - ngAfterViewChecked() { + ngAfterContentInit() { this.positionElements(); } @@ -227,26 +234,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,32 +285,34 @@ 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 * 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); }); @@ -342,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 + // wait 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; } @@ -370,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(); } @@ -399,4 +417,8 @@ export class Searchbar extends Ion { registerOnTouched(fn: () => {}): void { this.onTouched = fn; } + + setFocus() { + this._renderer.invokeElementMethod(this._searchbarInput.nativeElement, 'focus'); + } } diff --git a/src/components/searchbar/test/basic/main.html b/src/components/searchbar/test/basic/main.html index 02309d58241..54eee134b75 100644 --- a/src/components/searchbar/test/basic/main.html +++ b/src/components/searchbar/test/basic/main.html @@ -1,6 +1,9 @@
Search - Default
- + + +
Search - Animated
+

defaultSearch: {{ defaultSearch }} 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 8aa8be3a5bd..a3b301b4f05 100644 --- a/src/components/searchbar/test/nav/search.html +++ b/src/components/searchbar/test/nav/search.html @@ -1,21 +1,24 @@ - Searchbar + + - - + Searchbar + + + - - - +

+ +