From a91a68e198fa738ecf2fbe4db859b2d42537901a Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 12 Jun 2017 13:51:26 -0400 Subject: [PATCH 01/13] style(util): remove commented out test css --- src/themes/util.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/themes/util.scss b/src/themes/util.scss index dcd542b8946..305698d4fff 100755 --- a/src/themes/util.scss +++ b/src/themes/util.scss @@ -62,8 +62,6 @@ ion-input :focus { opacity: 0; - // background: red; - // opacity: .3; contain: strict; } From 47e3c70bf3fc7ec2d4408074ffeca7dc66931d55 Mon Sep 17 00:00:00 2001 From: Job Date: Mon, 12 Jun 2017 21:05:57 +0200 Subject: [PATCH 02/13] fix(refresher): border should only show when pulled (#12015) fixes #10994 --- src/components/content/content.ts | 10 +++++++++- src/components/refresher/refresher.ts | 2 +- src/components/refresher/test/refresher.spec.ts | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/content/content.ts b/src/components/content/content.ts index e579123e09e..df4708e169a 100644 --- a/src/components/content/content.ts +++ b/src/components/content/content.ts @@ -167,7 +167,8 @@ export class EventEmitterProxy extends EventEmitter { '' + '', host: { - '[class.statusbar-padding]': 'statusbarPadding' + '[class.statusbar-padding]': 'statusbarPadding', + '[class.has-refresher]': '_hasRefresher' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None @@ -212,6 +213,8 @@ export class Content extends Ion implements OnDestroy, AfterViewInit, IContent { /** @internal */ _fullscreen: boolean; /** @internal */ + _hasRefresher: boolean = false; + /** @internal */ _footerEle: HTMLElement; /** @internal */ _dirty: boolean; @@ -782,6 +785,11 @@ export class Content extends Ion implements OnDestroy, AfterViewInit, IContent { this._cBottom += this._tabbarHeight; } + // Refresher uses a border which should be hidden unless pulled + if (this._hasRefresher) { + this._cTop -= 1; + } + // Fixed content shouldn't include content padding this._fTop = this._cTop; this._fBottom = this._cBottom; diff --git a/src/components/refresher/refresher.ts b/src/components/refresher/refresher.ts index d22114311e7..04580f3fd34 100644 --- a/src/components/refresher/refresher.ts +++ b/src/components/refresher/refresher.ts @@ -202,7 +202,7 @@ export class Refresher { constructor(private _plt: Platform, @Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) { this._events = new UIEventManager(_plt); - _content.setElementClass('has-refresher', true); + _content._hasRefresher = true; this._gesture = gestureCtrl.createGesture({ name: GESTURE_REFRESHER, priority: GESTURE_PRIORITY_REFRESHER diff --git a/src/components/refresher/test/refresher.spec.ts b/src/components/refresher/test/refresher.spec.ts index 6d787c6de84..8f94b5aeb83 100644 --- a/src/components/refresher/test/refresher.spec.ts +++ b/src/components/refresher/test/refresher.spec.ts @@ -224,6 +224,9 @@ describe('Refresher', () => { }); + it('should set hasRefresher on content', () => { + expect(content._hasRefresher).toBeTruthy(); + }); let contentElementRef: any; let refresher: Refresher; From c10f72b1e2614292e547c68f6f26515efb1cd691 Mon Sep 17 00:00:00 2001 From: Manuel Mtz-Almeida Date: Thu, 8 Jun 2017 01:00:40 +0200 Subject: [PATCH 03/13] fix(keyboard): big keyboard/input refactor fixes #9699 fixes #11484 fixes #11389 fixes #11325 fixes #11291 fixes #10828 fixes #11291 fixes #10393 fixes #10257 fixes #9434 fixes #8933 fixes #7178 fixes #7047 fixes #10552 fixes #10393 fixes #10183 fixes #10187 fixes #10852 fixes #11578 --- scripts/gulp/tasks/demos.dev.ts | 6 +- scripts/gulp/tasks/e2e.dev.ts | 6 +- scripts/gulp/util.ts | 9 +- src/components/alert/alert-component.ts | 4 +- src/components/app/app.ts | 62 ++ src/components/checkbox/checkbox.ts | 11 +- src/components/datetime/datetime.ts | 5 +- src/components/input/input.scss | 23 +- src/components/input/input.ts | 988 ++++++++---------- src/components/input/native-input.ts | 242 ----- src/components/input/next-input.ts | 18 - .../highlight/pages/root-page/root-page.html | 14 +- src/components/input/test/text-input.spec.ts | 33 +- src/components/searchbar/searchbar.ts | 5 +- src/components/select/select.ts | 4 - src/components/tabs/tabs.scss | 4 + src/components/tabs/tabs.ts | 40 +- .../advanced/pages/tab1-page1/tab1-page1.html | 1 + src/components/toggle/toggle.ts | 9 +- src/index.ts | 2 - src/module.ts | 6 - src/platform/keyboard.ts | 84 +- src/platform/platform-registry.ts | 10 + src/util/base-input.ts | 107 +- src/util/form.ts | 34 +- src/util/input-tester.ts | 42 + src/util/scroll-view.ts | 35 +- 27 files changed, 847 insertions(+), 957 deletions(-) delete mode 100644 src/components/input/native-input.ts delete mode 100644 src/components/input/next-input.ts diff --git a/scripts/gulp/tasks/demos.dev.ts b/scripts/gulp/tasks/demos.dev.ts index f43f787223a..90575d99469 100644 --- a/scripts/gulp/tasks/demos.dev.ts +++ b/scripts/gulp/tasks/demos.dev.ts @@ -11,14 +11,14 @@ task('demos.watch', ['demos.prepare'], (done: Function) => { done(new Error(`Usage: gulp e2e.watch --folder modal`)); } - serveDemo(folderInfo.componentName).then(() => { + serveDemo(folderInfo.componentName, folderInfo.devApp).then(() => { done(); }).catch((err: Error) => { done(err); }); }); -function serveDemo(folderName: any) { +function serveDemo(folderName: any, devApp: boolean) { const ionicAngularDir = join(PROJECT_ROOT, 'src'); const srcTestRoot = join(DEMOS_ROOT, 'src', folderName); @@ -40,5 +40,5 @@ function serveDemo(folderName: any) { const appNgModulePath = join(srcTestRoot, 'app', 'app.module.ts'); const distDir = join(distDemoRoot, 'www'); - return runAppScriptsServe(folderName, appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, watchConfigPath); + return runAppScriptsServe(folderName, appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, watchConfigPath, devApp); } diff --git a/scripts/gulp/tasks/e2e.dev.ts b/scripts/gulp/tasks/e2e.dev.ts index e9a5237afca..5fd86d41c7d 100644 --- a/scripts/gulp/tasks/e2e.dev.ts +++ b/scripts/gulp/tasks/e2e.dev.ts @@ -13,14 +13,14 @@ task('e2e.watch', ['e2e.prepare'], (done: Function) => { return; } - serveTest(folderInfo).then(() => { + serveTest(folderInfo, folderInfo.devApp).then(() => { done(); }).catch((err: Error) => { done(err); }); }); -function serveTest(folderInfo: any) { +function serveTest(folderInfo: any, devApp: boolean) { const ionicAngularDir = join(PROJECT_ROOT, 'src'); const srcTestRoot = join(PROJECT_ROOT, 'src', 'components', folderInfo.componentName, 'test', folderInfo.componentTest); @@ -47,5 +47,5 @@ function serveTest(folderInfo: any) { const appNgModulePath = join(dirname(appEntryPoint), 'app.module.ts'); const distDir = join(distTestRoot, 'www'); - return runAppScriptsServe(join(folderInfo.componentName, folderInfo.componentTest), appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, null); + return runAppScriptsServe(join(folderInfo.componentName, folderInfo.componentTest), appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, null, devApp); } diff --git a/scripts/gulp/util.ts b/scripts/gulp/util.ts index e87bf09db32..65a0cdb3747 100644 --- a/scripts/gulp/util.ts +++ b/scripts/gulp/util.ts @@ -190,7 +190,7 @@ export function runWebpack(pathToWebpackConfig: string, done: Function) { }); } -export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, watchConfigPath: string) { +export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, watchConfigPath: string, devApp: boolean) { console.log('Running ionic-app-scripts serve with', testOrDemoName); const deepLinksDir = dirname(dirname(appNgModulePath)); let scriptArgs = [ @@ -207,6 +207,9 @@ export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string '--copy', copyConfigPath, '--enableLint', 'false' ]; + if (devApp) { + scriptArgs.push('--bonjour'); + } if (watchConfigPath) { scriptArgs.push('--watch'); @@ -349,9 +352,11 @@ export function getFolderInfo() { componentName = folderSplit[0]; componentTest = (folderSplit.length > 1 ? folderSplit[1] : 'basic'); } + const devApp = argv.devapp !== undefined; return { componentName: componentName, - componentTest: componentTest + componentTest: componentTest, + devApp: devApp }; } diff --git a/src/components/alert/alert-component.ts b/src/components/alert/alert-component.ts index 836f34f7255..abb1771b18f 100644 --- a/src/components/alert/alert-component.ts +++ b/src/components/alert/alert-component.ts @@ -84,6 +84,7 @@ export class AlertCmp { msgId: string; subHdrId: string; mode: string; + keyboardResizes: boolean; gestureBlocker: BlockerDelegate; constructor( @@ -99,6 +100,7 @@ export class AlertCmp { this.gestureBlocker = gestureCtrl.createBlocker(BLOCK_ALL); this.d = params.data; this.mode = this.d.mode || config.get('mode'); + this.keyboardResizes = config.getBoolean('keyboardResizes', false); _renderer.setElementClass(_elementRef.nativeElement, `alert-${this.mode}`, true); if (this.d.cssClass) { @@ -178,7 +180,7 @@ export class AlertCmp { } const hasTextInput = (this.d.inputs.length && this.d.inputs.some(i => !(NON_TEXT_INPUT_REGEX.test(i.type)))); - if (hasTextInput && this._plt.is('mobile')) { + if (!this.keyboardResizes && hasTextInput && this._plt.is('mobile')) { // this alert has a text input and it's on a mobile device so we should align // the alert up high because we need to leave space for the virtual keboard // this also helps prevent the layout getting all messed up from diff --git a/src/components/app/app.ts b/src/components/app/app.ts index aa76a863759..3b10041b9cf 100644 --- a/src/components/app/app.ts +++ b/src/components/app/app.ts @@ -30,6 +30,7 @@ export class App { private _titleSrv: Title = new Title(DOCUMENT); private _rootNav: NavController = null; private _disableScrollAssist: boolean; + private _didScroll = false; /** * @hidden @@ -87,6 +88,11 @@ export class App { _plt.registerBackButtonAction(this.goBack.bind(this)); this._disableScrollAssist = _config.getBoolean('disableScrollAssist', false); + const blurring = _config.getBoolean('inputBlurring', false); + if (blurring) { + this._enableInputBlurring(); + } + runInDev(() => { // During developement, navPop can be triggered by calling const win = _plt.win(); @@ -179,6 +185,7 @@ export class App { */ setScrolling() { this._scrollTime = Date.now() + ACTIVE_SCROLLING_TIME; + this._didScroll = true; } /** @@ -289,6 +296,60 @@ export class App { return recursivePop(this.getActiveNav()); } + /** + * @hidden + */ + _enableInputBlurring() { + console.debug('App: _enableInputBlurring'); + let focused = true; + const self = this; + const platform = this._plt; + + platform.registerListener(platform.doc(), 'focusin', onFocusin, { capture: true, zone: false, passive: true }); + platform.registerListener(platform.doc(), 'touchend', onTouchend, { capture: false, zone: false, passive: true }); + + function onFocusin(ev: any) { + focused = true; + } + function onTouchend(ev: any) { + // if app did scroll return early + if (self._didScroll) { + self._didScroll = false; + return; + } + const active = self._plt.getActiveElement(); + if (!active) { + return; + } + // only blur if the active element is a text-input or a textarea + if (SKIP_BLURRING.indexOf(active.tagName) === -1) { + return; + } + + // if the selected target is the active element, do not blur + const tapped = ev.target; + if (tapped === active) { + return; + } + if (SKIP_BLURRING.indexOf(tapped.tagName) >= 0) { + return; + } + + // skip if div is a cover + if (tapped.classList.contains('input-cover')) { + return; + } + + focused = false; + // TODO: find a better way, why 50ms? + platform.timeout(() => { + if (!focused) { + active.blur(); + } + }, 50); + } + } + } function recursivePop(nav: any): Promise { @@ -322,5 +383,6 @@ function findTopNav(nav: NavController) { return nav; } +const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA']; const ACTIVE_SCROLLING_TIME = 100; const CLICK_BLOCK_BUFFER_IN_MILLIS = 64; diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index d8aa685e2f3..be8fcc18c2e 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -118,13 +118,6 @@ export class Checkbox extends BaseInput implements IonicTapInput, OnDes super(config, elementRef, renderer, 'checkbox', false, form, item, null); } - /** - * @hidden - */ - initFocus() { - this._elementRef.nativeElement.querySelector('button').focus(); - } - /** * @hidden */ @@ -145,8 +138,8 @@ export class Checkbox extends BaseInput implements IonicTapInput, OnDes /** * @hidden */ - _inputCheckHasValue(val: boolean) { - this._item && this._item.setElementClass('item-checkbox-checked', val); + _inputUpdated() { + this._item && this._item.setElementClass('item-checkbox-checked', this._value); } } diff --git a/src/components/datetime/datetime.ts b/src/components/datetime/datetime.ts index 27cf3a3b2cf..00b7ec47df6 100644 --- a/src/components/datetime/datetime.ts +++ b/src/components/datetime/datetime.ts @@ -448,6 +448,7 @@ export class DateTime extends BaseInput implements AfterContentIni * @hidden */ _inputUpdated() { + super._inputUpdated(); this.updateText(); } @@ -475,10 +476,6 @@ export class DateTime extends BaseInput implements AfterContentIni @HostListener('click', ['$event']) _click(ev: UIEvent) { - // do not continue if the click event came from a form submit - if (ev.detail === 0) { - return; - } ev.preventDefault(); ev.stopPropagation(); this.open(); diff --git a/src/components/input/input.scss b/src/components/input/input.scss index 4576fed2d40..119a87e8333 100644 --- a/src/components/input/input.scss +++ b/src/components/input/input.scss @@ -93,6 +93,8 @@ input.text-input:-webkit-autofill { width: 100%; height: 100%; + + touch-action: manipulation; } .input[disabled] .input-cover { @@ -127,27 +129,6 @@ input.text-input:-webkit-autofill { } -// Scroll Assist Input -// -------------------------------------------------- -// This input is used to help the app handle -// Next and Previous input tabbing - -[next-input] { - @include padding(0); - - position: absolute; - bottom: 20px; - - width: 1px; - height: 1px; - - border: 0; - background: transparent; - - pointer-events: none; -} - - // Clear Input Icon // -------------------------------------------------- diff --git a/src/components/input/input.ts b/src/components/input/input.ts index f3f7980d77b..7e095188baa 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -1,19 +1,21 @@ -import { Component, Optional, ElementRef, EventEmitter, Input, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + Component, ChangeDetectionStrategy, Optional, ElementRef, EventEmitter, + Input, Output, Renderer, ViewChild, ViewEncapsulation +} from '@angular/core'; import { NgControl } from '@angular/forms'; +import { Subject } from 'rxjs/Subject'; +import 'rxjs/add/operator/takeUntil'; + import { App } from '../app/app'; import { Config } from '../../config/config'; -import { Content, ContentDimensions, ScrollEvent } from '../content/content'; -import { copyInputAttributes, PointerCoordinates, hasPointerMoved, pointerCoord } from '../../util/dom'; +import { Content, ContentDimensions } from '../content/content'; +import { hasPointerMoved, pointerCoord } from '../../util/dom'; import { DomController } from '../../platform/dom-controller'; import { Form, IonicFormInput } from '../../util/form'; -import { Ion } from '../ion'; -import { isString, isTrueProperty } from '../../util/util'; +import { BaseInput } from '../../util/base-input'; +import { isTrueProperty, assert } from '../../util/util'; import { Item } from '../item/item'; -import { NativeInput } from './native-input'; -import { NextInput } from './next-input'; -import { NavController } from '../../navigation/nav-controller'; -import { NavControllerBase } from '../../navigation/nav-controller-base'; import { Platform } from '../../platform/platform'; @@ -84,92 +86,65 @@ import { Platform } from '../../platform/platform'; @Component({ selector: 'ion-input,ion-textarea', template: - '' + - '' + - '' + - '' + - '
', + '' + + + '' + + + '' + + + '
', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush }) -export class TextInput extends Ion implements IonicFormInput { - _autoComplete: string; - _autoCorrect: string; +export class TextInput extends BaseInput implements IonicFormInput { + _autoFocusAssist: string; _clearInput: boolean = false; _clearOnEdit: boolean; - _coord: PointerCoordinates; _didBlurAfterEdit: boolean; - _disabled: boolean = false; _readonly: boolean = false; - _isTouch: boolean; _keyboardHeight: number; - _min: any; - _max: any; - _step: any; - _native: NativeInput; - _nav: NavControllerBase; - _scrollStart: any; - _scrollEnd: any; _type: string = 'text'; - _useAssist: boolean; - _usePadding: boolean; - _value: any = ''; - - /** @hidden */ - inputControl: NgControl; - - constructor( - config: Config, - private _plt: Platform, - private _form: Form, - private _app: App, - elementRef: ElementRef, - renderer: Renderer, - @Optional() private _content: Content, - @Optional() private _item: Item, - @Optional() nav: NavController, - @Optional() public ngControl: NgControl, - private _dom: DomController - ) { - super(config, elementRef, renderer, 'input'); - - this._nav = nav; - - this._autoFocusAssist = config.get('autoFocusAssist', 'delay'); - this._autoComplete = config.get('autocomplete', 'off'); - this._autoCorrect = config.get('autocorrect', 'off'); - this._keyboardHeight = config.getNumber('keyboardHeight'); - this._useAssist = config.getBoolean('scrollAssist', false); - this._usePadding = config.getBoolean('scrollPadding', this._useAssist); - - if (elementRef.nativeElement.tagName === 'ION-TEXTAREA') { - this._type = TEXTAREA; - } - - if (ngControl) { - ngControl.valueAccessor = this; - this.inputControl = ngControl; - } - - _form.register(this); - - // only listen to content scroll events if there is content - if (_content) { - this._scrollStart = _content.ionScrollStart.subscribe((ev: ScrollEvent) => { - this.scrollHideFocus(ev, true); - }); - this._scrollEnd = _content.ionScrollEnd.subscribe((ev: ScrollEvent) => { - this.scrollHideFocus(ev, false); - }); - } - - this.mode = config.get('mode'); - } - - /** - * @input {string} Instructional text that shows before the input has a value. - */ - @Input() placeholder: string = ''; + _scrollData: ScrollData; + _isTextarea: boolean = false; + _onDestroy = new Subject(); + _coord: any; + _isTouch: boolean; + _useAssist = false; + _relocated: boolean = false; /** * @input {boolean} If true, a clear icon will appear in the input when there is a value. Clicking it clears the input. @@ -179,67 +154,21 @@ export class TextInput extends Ion implements IonicFormInput { return this._clearInput; } set clearInput(val: any) { - this._clearInput = (this._type !== TEXTAREA && isTrueProperty(val)); - } - - /** - * @input {string} The text value of the input. - */ - @Input() - get value() { - return this._value; - } - set value(val: any) { - this._value = val; - this.checkHasValue(val); + this._clearInput = (!this._isTextarea && isTrueProperty(val)); } /** - * @input {string} The type of control to display. The default type is text. Possible values are: `"text"`, `"password"`, `"email"`, `"number"`, `"search"`, `"tel"`, or `"url"`. + * @input {string} The type of control to display. The default type is text. + * Possible values are: `"text"`, `"password"`, `"email"`, `"number"`, `"search"`, `"tel"`, or `"url"`. */ @Input() get type() { - return this._type; + return (this._isTextarea) + ? 'text' + : this._type; } set type(val: any) { - if (this._type !== TEXTAREA) { - this._type = 'text'; - - if (isString(val)) { - val = val.toLowerCase(); - - if (TEXT_TYPE_REGEX.test(val)) { - this._type = val; - } - } - } - } - - /** - * @input {boolean} If true, the user cannot interact with this element. - */ - @Input() - get disabled(): boolean { - return this._disabled; - } - set disabled(val: boolean) { - this.setDisabled(this._disabled = isTrueProperty(val)); - } - - /** - * @hidden - */ - setDisabled(val: boolean) { - this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', val ? '' : null); - this._item && this._item.setElementClass('item-input-disabled', val); - this._native && this._native.isDisabled(val); - } - - /** - * @hidden - */ - setDisabledState(isDisabled: boolean) { - this.disabled = isDisabled; + this._type = val; } /** @@ -254,7 +183,8 @@ export class TextInput extends Ion implements IonicFormInput { } /** - * @input {boolean} If true, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types. + * @input {boolean} If true, the value will be cleared after focus upon edit. + * Defaults to `true` when `type` is `"password"`, `false` for all other types. */ @Input() get clearOnEdit() { @@ -265,375 +195,382 @@ export class TextInput extends Ion implements IonicFormInput { } /** - * @input {any} The minimum value, which must not be greater than its maximum (max attribute) value. + * @hidden */ - @Input() - get min() { - return this._min; - } - set min(val: any) { - this.setMin(this._min = val); - } + @ViewChild('textInput', { read: ElementRef }) _native: ElementRef; /** - * @hidden + * @input {string} Instructional text that shows before the input has a value. */ - setMin(val: any) { - this._native && this._native.setMin(val); - } + @Input() autocomplete: string = ''; /** - * @input {any} The maximum value, which must not be less than its minimum (min attribute) value. + * @input {string} Instructional text that shows before the input has a value. */ - @Input() - get max() { - return this._max; - } - set max(val: any) { - this.setMax(this._max = val); - } + @Input() autocorrect: string = ''; /** - * @hidden + * @input {string} Specifies whether the element is to have its spelling + * and grammar checked or not. */ - setMax(val: any) { - this._native && this._native.setMax(val); - } + @Input() spellcheck: string = null; /** - * @input {any} Works with the min and max attributes to limit the increments at which a value can be set. + * @input {string} controls whether and how the text value for textual form control descendants should be automatically capitalized as it is entered/edited by the user. */ - @Input() - get step() { - return this._step; - } - set step(val: any) { - this.setStep(this._step = val); - } + @Input() autocapitalize: string = null; /** - * @hidden + * @input {string} Instructional text that shows before the input has a value. */ - setStep(val: any) { - this._native && this._native.setStep(val); - } + @Input() placeholder: string = ''; /** - * @hidden + * @input {string} The name attribute is used to reference elements in a JavaScript, + * or to reference form data after a form is submitted. */ - @ViewChild('input', { read: NativeInput }) - set _nativeInput(nativeInput: NativeInput) { - if (this.type !== TEXTAREA) { - this.setNativeInput(nativeInput); - } - } + @Input() name: string = null; /** - * @hidden + * @input {any} The minimum value, which must not be greater than its maximum (max attribute) value. */ - @ViewChild('textarea', { read: NativeInput }) - set _nativeTextarea(nativeInput: NativeInput) { - if (this.type === TEXTAREA) { - this.setNativeInput(nativeInput); - } - } + @Input() min: number | string = null; /** - * @hidden + * @input {any} The maximum value, which must not be less than its minimum (min attribute) value. */ - @ViewChild(NextInput) - set _nextInput(nextInput: NextInput) { - if (nextInput) { - nextInput.focused.subscribe(() => { - this._form.tabFocus(this); - }); - } - } + @Input() max: number | string = null; + + /** + * @input {any} Works with the min and max attributes to limit the increments at which a value can be set. + */ + @Input() step: number | string = null; /** - * @output {event} Emitted when the input no longer has focus. + * @input {any} Specifies the maximum number of characters allowed in the element. */ - @Output() blur: EventEmitter = new EventEmitter(); + @Input() maxlength: number | string = null; /** - * @output {event} Emitted when the input has focus. + * @hidden */ - @Output() focus: EventEmitter = new EventEmitter(); + @Output() input = new EventEmitter(); /** * @hidden */ - setNativeInput(nativeInput: NativeInput) { - this._native = nativeInput; - nativeInput.setValue(this._value); - nativeInput.setMin(this._min); - nativeInput.setMax(this._max); - nativeInput.setStep(this._step); - nativeInput.isDisabled(this.disabled); - - if (this._item && this._item.labelId !== null) { - nativeInput.labelledBy(this._item.labelId); + @Output() blur = new EventEmitter(); + + /** + * @hidden + */ + @Output() focus = new EventEmitter(); + + constructor( + config: Config, + private _plt: Platform, + private form: Form, + private _app: App, + elementRef: ElementRef, + renderer: Renderer, + @Optional() private _content: Content, + @Optional() private item: Item, + @Optional() public ngControl: NgControl, + private _dom: DomController + ) { + super(config, elementRef, renderer, + elementRef.nativeElement.tagName === 'ION-TEXTAREA' ? 'textarea' : 'input', '', form, item, ngControl); + + this.autocomplete = config.get('autocomplete', 'off'); + this.autocorrect = config.get('autocorrect', 'off'); + this._autoFocusAssist = config.get('autoFocusAssist', 'delay'); + this._keyboardHeight = config.getNumber('keyboardHeight'); + this._isTextarea = !!(elementRef.nativeElement.tagName === 'ION-TEXTAREA'); + + // If not inside content, let's disable all the hacks + if (!_content) { + return; } - nativeInput.valueChange.subscribe((inputValue: any) => { - this.onChange(inputValue); - this.checkHasValue(inputValue); - }); + const blurOnScroll = config.getBoolean('hideCaretOnScroll', false); + if (blurOnScroll) { + this._enableHideCaretOnScroll(); + } - nativeInput.keydown.subscribe((inputValue: any) => { - this.onKeydown(inputValue); - }); + const resizeAssist = config.getBoolean('resizeAssist', false); + if (resizeAssist) { + this._keyboardHeight = 60; + this._enableResizeAssist(); - this.focusChange(this.hasFocus()); - nativeInput.focusChange.subscribe((textInputHasFocus: any) => { - this.focusChange(textInputHasFocus); - this.checkHasValue(nativeInput.getValue()); - if (!textInputHasFocus) { - this.onTouched(textInputHasFocus); - } - }); + } else { + this._useAssist = config.getBoolean('scrollAssist', false); - this.checkHasValue(nativeInput.getValue()); + const usePadding = config.getBoolean('scrollPadding', this._useAssist); + if (usePadding) { + this._enableScrollPadding(); + } + } + } - var ionInputEle: HTMLElement = this._elementRef.nativeElement; - var nativeInputEle: HTMLElement = nativeInput.element(); + ngAfterContentInit() { } - // copy ion-input attributes to the native input element - copyInputAttributes(ionInputEle, nativeInputEle); + /** + * @hidden + */ + ngAfterViewInit() { + assert(this._native && this._native.nativeElement, 'input element must be valid'); + // By default, password inputs clear after focus when they have content + if (this.clearOnEdit !== false && this.type === 'password') { + this.clearOnEdit = true; + } + const ionInputEle: HTMLElement = this._elementRef.nativeElement; if (ionInputEle.hasAttribute('autofocus')) { // the ion-input element has the autofocus attributes + const nativeInputEle: HTMLElement = this._native.nativeElement; ionInputEle.removeAttribute('autofocus'); - - if (this._autoFocusAssist === 'immediate') { - // config says to immediate focus on the input - // works best on android devices - nativeInputEle.focus(); - - } else if (this._autoFocusAssist === 'delay') { - // config says to chill out a bit and focus on the input after transitions - // works best on desktop - this._plt.timeout(() => { + switch (this._autoFocusAssist) { + case 'immediate': + // config says to immediate focus on the input + // works best on android devices nativeInputEle.focus(); - }, 650); + break; + case 'delay': + // config says to chill out a bit and focus on the input after transitions + // works best on desktop + this._plt.timeout(() => nativeInputEle.focus(), 650); + break; } - // traditionally iOS has big issues with autofocus on actual devices // autoFocus is disabled by default with the iOS mode config } + this._initialize(); - // by default set autocomplete="off" unless specified by the input - if (ionInputEle.hasAttribute('autocomplete')) { - this._autoComplete = ionInputEle.getAttribute('autocomplete'); + if (this.focus.observers.length > 0) { + console.warn('(focus) is deprecated in ion-input, use (ionFocus) instead'); } - nativeInputEle.setAttribute('autocomplete', this._autoComplete); - - // by default set autocorrect="off" unless specified by the input - if (ionInputEle.hasAttribute('autocorrect')) { - this._autoCorrect = ionInputEle.getAttribute('autocorrect'); + if (this.blur.observers.length > 0) { + console.warn('(blur) is deprecated in ion-input, use (ionBlur) instead'); } - nativeInputEle.setAttribute('autocorrect', this._autoCorrect); } /** * @hidden */ - initFocus() { - // begin the process of setting focus to the inner input element - const app = this._app; - const content = this._content; - const nav = this._nav; - const nativeInput = this._native; - - console.debug(`input-base, initFocus(), scrollView: ${!!content}`); - - if (content) { - // this input is inside of a scroll view - // find out if text input should be manually scrolled into view - - // get container of this input, probably an ion-item a few nodes up - var ele: HTMLElement = this._elementRef.nativeElement; - ele = ele.closest('ion-item,[ion-item]') || ele; - - var scrollData = getScrollData(ele.offsetTop, ele.offsetHeight, content.getContentDimensions(), this._keyboardHeight, this._plt.height()); - if (Math.abs(scrollData.scrollAmount) < 4) { - // the text input is in a safe position that doesn't - // require it to be scrolled into view, just set focus now - this.setFocus(); - - // all good, allow clicks again - app.setEnabled(true); - nav && nav.setTransitioning(false); - - if (this._usePadding) { - content.clearScrollPaddingFocusOut(); - } - return; - } - - if (this._usePadding) { - // add padding to the bottom of the scroll view (if needed) - content.addScrollPadding(scrollData.scrollPadding); - } - - // manually scroll the text input to the top - // do not allow any clicks while it's scrolling - var scrollDuration = getScrollAssistDuration(scrollData.scrollAmount); - app.setEnabled(false, scrollDuration); - nav && nav.setTransitioning(true); - - // temporarily move the focus to the focus holder so the browser - // doesn't freak out while it's trying to get the input in place - // at this point the native text input still does not have focus - nativeInput.beginFocus(true, scrollData.inputSafeY); - - // scroll the input into place - content.scrollTo(0, scrollData.scrollTo, scrollDuration, () => { - console.debug(`input-base, scrollTo completed, scrollTo: ${scrollData.scrollTo}, scrollDuration: ${scrollDuration}`); - // the scroll view is in the correct position now - // give the native text input focus - nativeInput.beginFocus(false, 0); - - // ensure this is the focused input - this.setFocus(); - - // all good, allow clicks again - app.setEnabled(true); - nav && nav.setTransitioning(false); - - if (this._usePadding) { - content.clearScrollPaddingFocusOut(); - } - }); - - } else { - // not inside of a scroll view, just focus it - this.setFocus(); - } + ngOnDestroy() { + super.ngOnDestroy(); + this._onDestroy.next(); + this._onDestroy = null; } /** * @hidden */ - setFocus() { - // immediately set focus - this._form.setAsFocused(this); - - // set focus on the actual input element - console.debug(`input-base, setFocus ${this._native.element().value}`); - this._native.setFocus(); - - // ensure the body hasn't scrolled down - this._dom.write(() => { - this._plt.doc().body.scrollTop = 0; - }); + initFocus() { + this.setFocus(); } /** * @hidden */ - scrollHideFocus(ev: ScrollEvent, shouldHideFocus: boolean) { - // do not continue if there's no nav, or it's transitioning - if (this._nav && this.hasFocus()) { - // if it does have focus, then do the dom write - this._dom.write(() => { - this._native.hideFocus(shouldHideFocus); - }); + setFocus() { + // let's set focus to the element + // but only if it does not already have focus + if (!this.isFocus()) { + this._native.nativeElement.focus(); } } /** * @hidden */ - inputBlurred(ev: UIEvent) { - this.blur.emit(ev); + setBlur() { + if (this.isFocus()) { + this._native.nativeElement.blur(); + } } /** * @hidden */ - inputFocused(ev: UIEvent) { - this.focus.emit(ev); + onInput(ev: any) { + this.value = ev.target.value; + + // TODO: deprecate this + this.input.emit(ev); } /** * @hidden */ - writeValue(val: any) { - this._value = val; - this.checkHasValue(val); + onBlur(ev: UIEvent) { + this._fireBlur(); + + // TODO: deprecate this (06/07/2017) + this.blur.emit(ev); + + this._scrollData = null; + if (this._clearOnEdit && this.hasValue()) { + this._didBlurAfterEdit = true; + } } /** * @hidden */ - onChange(val: any) { - this.checkHasValue(val); + onFocus(ev: UIEvent) { + this._fireFocus(); + + // TODO: deprecate this (06/07/2017) + this.focus.emit(ev); } /** * @hidden */ - onKeydown(val: any) { - if (this._clearOnEdit) { - this.checkClearOnEdit(val); + onKeydown(ev: any) { + if (ev && this._clearOnEdit) { + this.checkClearOnEdit(ev.target.value); } } /** * @hidden */ - onTouched(val: any) {} + _inputUpdated() { + super._inputUpdated(); + const inputEle = this._native.nativeElement; + const value = this._value; + if (inputEle.value !== value) { + inputEle.value = value; + } + } /** * @hidden */ - hasFocus(): boolean { - // check if an input has focus or not - return this._plt.hasFocus(this._native.element()); + clearTextInput() { + this.value = ''; } /** - * @hidden - */ - hasValue(): boolean { - const inputValue = this._value; - return (inputValue !== null && inputValue !== undefined && inputValue !== ''); + * Check if we need to clear the text input if clearOnEdit is enabled + * @hidden + */ + checkClearOnEdit(inputValue: string) { + if (!this._clearOnEdit) { + return; + } + + // Did the input value change after it was blurred and edited? + if (this._didBlurAfterEdit && this.hasValue()) { + // Clear the input + this.clearTextInput(); + } + + // Reset the flag + this._didBlurAfterEdit = false; } - /** - * @hidden - */ - checkHasValue(inputValue: any) { - if (this._item) { - var hasValue = (inputValue !== null && inputValue !== undefined && inputValue !== ''); - // TODO remove all uses of input-has-value in v4 - this._item.setElementClass('input-has-value', hasValue); - this._item.setElementClass('item-input-has-value', hasValue); + _getScrollData(): ScrollData { + if (!this._content) { + return newScrollData(); + } + + // get container of this input, probably an ion-item a few nodes up + if (this._scrollData) { + return this._scrollData; } + let ele: HTMLElement = this._elementRef.nativeElement; + ele = ele.closest('ion-item,[ion-item]') || ele; + return this._scrollData = getScrollData( + ele.offsetTop, ele.offsetHeight, + this._content.getContentDimensions(), this._keyboardHeight, this._plt.height()); } - /** - * @hidden - */ - focusChange(inputHasFocus: boolean) { - if (this._item) { - console.debug(`input-base, focusChange, inputHasFocus: ${inputHasFocus}, ${this._item.getNativeElement().nodeName}.${this._item.getNativeElement().className}`); - // TODO remove input-has-focus for v4 - this._item.setElementClass('input-has-focus', inputHasFocus); - this._item.setElementClass('item-input-has-focus', inputHasFocus); + _relocateInput(shouldRelocate: boolean) { + if (this._relocated === shouldRelocate) { + return; } + const platform = this._plt; + const componentEle = this.getNativeElement(); + const focusedInputEle = this._native.nativeElement; + console.debug(`native-input, hideCaret, shouldHideCaret: ${shouldRelocate}, input value: ${focusedInputEle.value}`); + if (shouldRelocate) { + // this allows for the actual input to receive the focus from + // the user's touch event, but before it receives focus, it + // moves the actual input to a location that will not screw + // up the app's layout, and does not allow the native browser + // to attempt to scroll the input into place (messing up headers/footers) + // the cloned input fills the area of where native input should be + // while the native input fakes out the browser by relocating itself + // before it receives the actual focus event + // We hide the focused input (with the visible caret) invisiable by making it scale(0), + cloneInputComponent(platform, componentEle, focusedInputEle); + const inputRelativeY = this._getScrollData().inputSafeY; + focusedInputEle.style[platform.Css.transform] = `translate3d(-9999px,${inputRelativeY}px,0)`; + focusedInputEle.style.opacity = '0'; - // If clearOnEdit is enabled and the input blurred but has a value, set a flag - if (this._clearOnEdit && !inputHasFocus && this.hasValue()) { - this._didBlurAfterEdit = true; + } else { + removeClone(platform, componentEle, focusedInputEle); } + this._relocated = shouldRelocate; } - /** - * @hidden - */ - pointerStart(ev: UIEvent) { + _enableScrollPadding() { + assert(this._content, 'content is undefined'); + + console.debug('Input: enableScrollPadding'); + + this.ionFocus.subscribe(() => { + const content = this._content; + + // add padding to the bottom of the scroll view (if needed) + content.addScrollPadding(this._getScrollData().scrollPadding); + content.clearScrollPaddingFocusOut(); + }); + } + + _enableHideCaretOnScroll() { + assert(this._content, 'content is undefined'); + + const content = this._content; + console.debug('Input: enableHideCaretOnScroll'); + + content.ionScrollStart + .takeUntil(this._onDestroy) + .subscribe(() => scrollHideCaret(true)); + + content.ionScrollEnd + .takeUntil(this._onDestroy) + .subscribe(() => scrollHideCaret(false)); + + this.ionBlur.subscribe(() => this._relocateInput(false)); + + const self = this; + function scrollHideCaret(shouldHideCaret: boolean) { + // if it does have focus, then do the dom write + if (self.isFocus()) { + self._dom.write(() => self._relocateInput(shouldHideCaret)); + } + } + } + + _enableResizeAssist() { + assert(this._content, 'content is undefined'); + + console.debug('Input: enableAutoScroll'); + this.ionFocus.subscribe(() => { + const scrollData = this._getScrollData(); + if (Math.abs(scrollData.scrollAmount) > 100) { + this._content.scrollTo(0, scrollData.scrollTo, scrollData.scrollDuration); + } + }); + } + + _pointerStart(ev: UIEvent) { + assert(this._content, 'content is undefined'); + // input cover touchstart if (ev.type === 'touchstart') { this._isTouch = true; @@ -647,10 +584,9 @@ export class TextInput extends Ion implements IonicFormInput { console.debug(`input-base, pointerStart, type: ${ev.type}`); } - /** - * @hidden - */ - pointerEnd(ev: UIEvent) { + _pointerEnd(ev: UIEvent) { + assert(this._content, 'content is undefined'); + // input cover touchend/mouseup console.debug(`input-base, pointerEnd, type: ${ev.type}`); @@ -662,130 +598,56 @@ export class TextInput extends Ion implements IonicFormInput { } else if (this._coord) { // get where the touchend/mouseup ended - let endCoord = pointerCoord(ev); + var endCoord = pointerCoord(ev); // focus this input if the pointer hasn't moved XX pixels // and the input doesn't already have focus - if (!hasPointerMoved(8, this._coord, endCoord) && !this.hasFocus()) { + if (!hasPointerMoved(8, this._coord, endCoord) && !this.isFocus()) { ev.preventDefault(); ev.stopPropagation(); // begin the input focus process - this.initFocus(); + this._jsSetFocus(); } } this._coord = null; } - /** - * @hidden - */ - setItemInputControlCss() { - let item = this._item; - let nativeInput = this._native; - let inputControl = this.inputControl; - - // Set the control classes on the item - if (item && inputControl) { - setControlCss(item, inputControl); - } - // Set the control classes on the native input - if (nativeInput && inputControl) { - setControlCss(nativeInput, inputControl); - } - } + _jsSetFocus() { + assert(this._content, 'content is undefined'); - /** - * @hidden - */ - ngOnInit() { - const item = this._item; - if (item) { - if (this.type === TEXTAREA) { - item.setElementClass('item-textarea', true); - } - item.setElementClass('item-input', true); - item.registerInput(this.type); - } - - // By default, password inputs clear after focus when they have content - if (this.type === 'password' && this.clearOnEdit !== false) { - this.clearOnEdit = true; - } - } - - /** - * @hidden - */ - ngAfterContentChecked() { - this.setItemInputControlCss(); - } - - /** - * @hidden - */ - ngOnDestroy() { - this._form.deregister(this); - - // only stop listening to content scroll events if there is content - if (this._content) { - this._scrollStart.unsubscribe(); - this._scrollEnd.unsubscribe(); + // begin the process of setting focus to the inner input element + const content = this._content; + console.debug(`input-base, initFocus(), scrollView: ${!!content}`); + if (!content) { + // not inside of a scroll view, just focus it + this.setFocus(); } - } - /** - * @hidden - */ - clearTextInput() { - console.debug('Should clear input'); - this._value = ''; - this.onChange(this._value); - this.writeValue(this._value); - } - - /** - * Check if we need to clear the text input if clearOnEdit is enabled - * @hidden - */ - checkClearOnEdit(inputValue: string) { - if (!this._clearOnEdit) { + var scrollData = this._getScrollData(); + if (Math.abs(scrollData.scrollAmount) < 4) { + // the text input is in a safe position that doesn't + // require it to be scrolled into view, just set focus now + this.setFocus(); return; } - // Did the input value change after it was blurred and edited? - if (this._didBlurAfterEdit && this.hasValue()) { - // Clear the input - this.clearTextInput(); - } - - // Reset the flag - this._didBlurAfterEdit = false; - } - - /** - * @hidden - * Angular2 Forms API method called by the view (formControlName) to register the - * onChange event handler that updates the model (Control). - * @param {Function} fn the onChange event handler. - */ - registerOnChange(fn: any) { this.onChange = fn; } - - /** - * @hidden - * Angular2 Forms API method called by the view (formControlName) to register - * the onTouched event handler that marks model (Control) as touched. - * @param {Function} fn onTouched event handler. - */ - registerOnTouched(fn: any) { this.onTouched = fn; } + // temporarily move the focus to the focus holder so the browser + // doesn't freak out while it's trying to get the input in place + // at this point the native text input still does not have focus + this._relocateInput(true); + this.setFocus(); + // scroll the input into place + content.scrollTo(0, scrollData.scrollTo, scrollData.scrollDuration, () => { + // the scroll view is in the correct position now + // give the native text input focus + this._relocateInput(false); - /** - * @hidden - */ - focusNext() { - this._form.tabFocus(this); + // ensure this is the focused input + this.setFocus(); + }); } } @@ -837,29 +699,41 @@ export class TextInput extends Ion implements IonicFormInput { const SCROLL_ASSIST_SPEED = 0.3; -const TEXTAREA = 'textarea'; -const TEXT_TYPE_REGEX = /password|email|number|search|tel|url|date|month|time|week/; +function newScrollData(): ScrollData { + return { + scrollAmount: 0, + scrollTo: 0, + scrollPadding: 0, + scrollDuration: 0, + inputSafeY: 0 + }; +} /** * @hidden */ -export function getScrollData(inputOffsetTop: number, inputOffsetHeight: number, scrollViewDimensions: ContentDimensions, keyboardHeight: number, plaformHeight: number) { +export function getScrollData( + inputOffsetTop: number, + inputOffsetHeight: number, + scrollViewDimensions: ContentDimensions, + keyboardHeight: number, + plaformHeight: number): ScrollData { // compute input's Y values relative to the body - let inputTop = (inputOffsetTop + scrollViewDimensions.contentTop - scrollViewDimensions.scrollTop); - let inputBottom = (inputTop + inputOffsetHeight); + const inputTop = (inputOffsetTop + scrollViewDimensions.contentTop - scrollViewDimensions.scrollTop); + const inputBottom = (inputTop + inputOffsetHeight); // compute the safe area which is the viewable content area when the soft keyboard is up - let safeAreaTop = scrollViewDimensions.contentTop; - let safeAreaHeight = (plaformHeight - keyboardHeight - safeAreaTop) / 2; - let safeAreaBottom = safeAreaTop + safeAreaHeight; + const safeAreaTop = scrollViewDimensions.contentTop; + const safeAreaHeight = (plaformHeight - keyboardHeight - safeAreaTop) / 2; + const safeAreaBottom = safeAreaTop + safeAreaHeight; // figure out if each edge of teh input is within the safe area - let inputTopWithinSafeArea = (inputTop >= safeAreaTop && inputTop <= safeAreaBottom); - let inputTopAboveSafeArea = (inputTop < safeAreaTop); - let inputTopBelowSafeArea = (inputTop > safeAreaBottom); - let inputBottomWithinSafeArea = (inputBottom >= safeAreaTop && inputBottom <= safeAreaBottom); - let inputBottomBelowSafeArea = (inputBottom > safeAreaBottom); + const inputTopWithinSafeArea = (inputTop >= safeAreaTop && inputTop <= safeAreaBottom); + const inputTopAboveSafeArea = (inputTop < safeAreaTop); + const inputTopBelowSafeArea = (inputTop > safeAreaBottom); + const inputBottomWithinSafeArea = (inputBottom >= safeAreaTop && inputBottom <= safeAreaBottom); + const inputBottomBelowSafeArea = (inputBottom > safeAreaBottom); /* Text Input Scroll To Scenarios @@ -873,12 +747,7 @@ export function getScrollData(inputOffsetTop: number, inputOffsetHeight: number, 7) Input top below safe area, no room to scroll, input larger than safe area */ - const scrollData: ScrollData = { - scrollAmount: 0, - scrollTo: 0, - scrollPadding: 0, - inputSafeY: 0 - }; + const scrollData: ScrollData = newScrollData(); if (inputTopWithinSafeArea && inputBottomWithinSafeArea) { // Input top within safe area, bottom within safe area @@ -924,46 +793,73 @@ export function getScrollData(inputOffsetTop: number, inputOffsetHeight: number, // to have the padding already rendered so there's no jank scrollData.scrollPadding = keyboardHeight; - // var safeAreaEle: HTMLElement = (window).safeAreaEle; - // if (!safeAreaEle) { - // safeAreaEle = (window).safeAreaEle = document.createElement('div'); - // safeAreaEle.style.cssText = 'position:absolute; padding:1px 5px; left:0; right:0; font-weight:bold; font-size:10px; font-family:Courier; text-align:right; background:rgba(0, 128, 0, 0.8); text-shadow:1px 1px white; pointer-events:none;'; - // document.body.appendChild(safeAreaEle); - // } - // safeAreaEle.style.top = safeAreaTop + 'px'; - // safeAreaEle.style.height = safeAreaHeight + 'px'; - // safeAreaEle.innerHTML = ` - //
scrollTo: ${scrollData.scrollTo}
- //
scrollAmount: ${scrollData.scrollAmount}
- //
scrollPadding: ${scrollData.scrollPadding}
- //
inputSafeY: ${scrollData.inputSafeY}
- //
scrollHeight: ${scrollViewDimensions.scrollHeight}
- //
scrollTop: ${scrollViewDimensions.scrollTop}
- //
contentHeight: ${scrollViewDimensions.contentHeight}
- //
plaformHeight: ${plaformHeight}
- // `; + // calculate animation duration + const distance = Math.abs(scrollData.scrollAmount); + const duration = distance / SCROLL_ASSIST_SPEED; + scrollData.scrollDuration = Math.min(400, Math.max(150, duration)); return scrollData; } -function setControlCss(element: any, control: NgControl) { - element.setElementClass('ng-untouched', control.untouched); - element.setElementClass('ng-touched', control.touched); - element.setElementClass('ng-pristine', control.pristine); - element.setElementClass('ng-dirty', control.dirty); - element.setElementClass('ng-valid', control.valid); - element.setElementClass('ng-invalid', !control.valid); +function cloneInputComponent(plt: Platform, srcComponentEle: HTMLInputElement, srcNativeInputEle: HTMLInputElement) { + // Make sure we kill all the clones before creating new ones + // It is a defensive, removeClone() should do nothing + // removeClone(plt, srcComponentEle, srcNativeInputEle); + assert(srcComponentEle.parentElement.querySelector('.cloned-input') === null, 'leaked cloned input'); + // given a native or ' + From 0812da9146d4de9a31d7fca326791230be7b9f65 Mon Sep 17 00:00:00 2001 From: Amit Moryossef Date: Tue, 13 Jun 2017 14:29:24 +0300 Subject: [PATCH 06/13] fix(border): border & border-width mixins for rtl --- .scss-lint.yml | 42 ++++++++++++------- scripts/demos/demos.shared.css | 20 ++++----- scripts/e2e/e2e.shared.css | 4 +- .../action-sheet/action-sheet.ios.scss | 6 +-- src/components/alert/alert.ios.scss | 23 ++++------ src/components/alert/alert.md.scss | 18 ++++---- src/components/alert/alert.scss | 2 +- src/components/alert/alert.wp.scss | 13 +++--- src/components/button/button.ios.scss | 6 +-- src/components/button/button.md.scss | 6 +-- src/components/button/button.scss | 4 +- src/components/button/button.wp.scss | 9 ++-- src/components/card/card.ios.scss | 4 +- src/components/card/card.md.scss | 4 +- src/components/card/card.wp.scss | 4 +- src/components/checkbox/checkbox.ios.scss | 6 +-- src/components/checkbox/checkbox.md.scss | 6 +-- src/components/checkbox/checkbox.wp.scss | 6 +-- src/components/chip/chip.wp.scss | 2 +- src/components/datetime/datetime.wp.scss | 2 +- src/components/input/input.scss | 3 +- src/components/input/input.wp.scss | 2 +- src/components/item/item.ios.scss | 7 ++-- src/components/item/item.md.scss | 11 +++-- src/components/item/item.scss | 8 ++-- src/components/item/item.wp.scss | 7 ++-- src/components/list/list.ios.scss | 27 ++++++------ src/components/list/list.md.scss | 23 +++++----- src/components/list/list.wp.scss | 22 +++++----- src/components/picker/picker.ios.scss | 14 +++---- src/components/picker/picker.md.scss | 10 ++--- src/components/picker/picker.wp.scss | 14 +++---- src/components/popover/popover.wp.scss | 2 +- src/components/radio/radio.ios.scss | 4 +- src/components/radio/radio.md.scss | 2 +- src/components/radio/radio.wp.scss | 2 +- src/components/range/range.md.scss | 2 +- src/components/refresher/refresher.scss | 2 +- src/components/searchbar/searchbar.ios.scss | 6 +-- src/components/searchbar/searchbar.scss | 3 +- src/components/searchbar/searchbar.wp.scss | 2 +- src/components/segment/segment.ios.scss | 19 ++------- src/components/segment/segment.md.scss | 3 +- src/components/select/select.ios.scss | 4 +- src/components/select/select.md.scss | 4 +- src/components/select/select.wp.scss | 7 ++-- src/components/slides/slides.scss | 2 +- src/components/split-pane/split-pane.ios.scss | 8 ++-- src/components/split-pane/split-pane.md.scss | 8 ++-- src/components/split-pane/split-pane.wp.scss | 8 ++-- src/components/tabs/tabs.ios.scss | 6 +-- src/components/tabs/tabs.scss | 3 +- src/components/tabs/tabs.wp.scss | 5 +-- src/components/toggle/toggle.wp.scss | 2 +- src/components/toolbar/toolbar.ios.scss | 14 ++++--- src/components/toolbar/toolbar.md.scss | 5 ++- src/components/toolbar/toolbar.scss | 2 +- src/components/toolbar/toolbar.wp.scss | 5 ++- src/themes/ionic.mixins.scss | 36 ++++++++++++++++ 59 files changed, 242 insertions(+), 259 deletions(-) diff --git a/.scss-lint.yml b/.scss-lint.yml index 49282b348db..78a91e5028c 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -72,29 +72,13 @@ linters: - column-width # Border - - - border - - border-top - - border-right - - border-bottom - - border-left - - border-width - - border-top-width - - border-right-width - - border-bottom-width - - border-left-width - - border-style - border-top-style - - border-right-style - border-bottom-style - - border-left-style - border-color - border-top-color - - border-right-color - border-bottom-color - - border-left-color - outline - outline-color @@ -190,24 +174,50 @@ linters: - contain disabled_properties: - background-position + - direction + - right - left + - float + - padding - padding-left - padding-right - padding-top - padding-bottom + - margin - margin-left - margin-right - margin-top - margin-bottom + - border-radius - border-top-left-radius - border-top-right-radius - border-bottom-right-radius - border-bottom-left-radius + + - border + - border-top + - border-right + - border-bottom + - border-left + + - border-right-style + - border-left-style + + - border-right-color + - border-left-color + + - border-width + - border-top-width + - border-right-width + - border-bottom-width + - border-left-width + - text-align + - transform-origin \ No newline at end of file diff --git a/scripts/demos/demos.shared.css b/scripts/demos/demos.shared.css index 5c3217923e1..ba64e61811a 100644 --- a/scripts/demos/demos.shared.css +++ b/scripts/demos/demos.shared.css @@ -105,21 +105,21 @@ page-api-radio-popover ion-col { */ .ios .popover-text-smaller { - border-right: 1px solid #c8c7cc; + @include border(null, 1px solid #c8c7cc, null, null); } .ios .popover-row-dots { - border-bottom: 1px solid #c8c7cc; + @include border(null, null, 1px solid #c8c7cc, null); } .ios .popover-dot { - border: 1px solid #c8c7cc; + @include border(1px solid #c8c7cc); } .hairlines .popover-text-smaller, .hairlines .popover-row-dots, .hairlines .popover-dot { - border-width: 0.55px; + @include border-width(0.55px); } /* @@ -127,15 +127,15 @@ page-api-radio-popover ion-col { */ .md .popover-text-smaller { - border-right: 1px solid #dedede; + @include border(null, 1px solid #dedede, null, null); } .md .popover-row-dots { - border-bottom: 1px solid #dedede; + @include border(null, null, 1px solid #dedede, null); } .md .popover-dot { - border: 1px solid #dedede; + @include border(1px solid #dedede); } /* @@ -143,7 +143,7 @@ page-api-radio-popover ion-col { */ .wp .popover-dot { - border: 2px solid #ccc; + @include border(2px solid #ccc); } /* @@ -196,12 +196,12 @@ page-api-radio-popover ion-col { @-webkit-keyframes pulse { 50% { - border-width: 20px; + @include border-width(20px); } } @keyframes pulse { 50% { - border-width: 20px; + @include border-width(20px); } } diff --git a/scripts/e2e/e2e.shared.css b/scripts/e2e/e2e.shared.css index e7c03050f3e..c48460f3ef6 100644 --- a/scripts/e2e/e2e.shared.css +++ b/scripts/e2e/e2e.shared.css @@ -162,11 +162,11 @@ e2e-popover-basic .text-smaller { } .popover-ios e2e-popover-basic .text-smaller { - border-right: 1px solid #c8c7cc; + @include border(null, 1px solid #c8c7cc, null, null); } .popover-md e2e-popover-basic .text-smaller { - border-right: 1px solid #dedede; + @include border(null, 1px solid #dedede, null, null); } e2e-popover-basic .text-larger { diff --git a/src/components/action-sheet/action-sheet.ios.scss b/src/components/action-sheet/action-sheet.ios.scss index 682c854831b..30d2138579f 100644 --- a/src/components/action-sheet/action-sheet.ios.scss +++ b/src/components/action-sheet/action-sheet.ios.scss @@ -112,8 +112,8 @@ $action-sheet-ios-button-cancel-font-weight: 600 !default; @include padding($action-sheet-ios-title-padding); @include text-align($action-sheet-ios-text-align); @include border-radius($action-sheet-ios-title-border-radius); + @include border(null, null, $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-border-color, null); - border-bottom: $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-border-color; font-size: $action-sheet-ios-title-font-size; font-weight: $action-sheet-ios-title-font-weight; color: $action-sheet-ios-title-color; @@ -122,10 +122,10 @@ $action-sheet-ios-button-cancel-font-weight: 600 !default; .action-sheet-ios .action-sheet-button { @include margin(0); @include padding($action-sheet-ios-button-padding); + @include border(null, null, $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-border-color, null); min-height: $action-sheet-ios-button-min-height; - border-bottom: $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-border-color; font-size: $action-sheet-ios-button-font-size; color: $action-sheet-ios-button-text-color; background: $action-sheet-ios-button-background; @@ -137,8 +137,8 @@ $action-sheet-ios-button-cancel-font-weight: 600 !default; .action-sheet-ios .action-sheet-button.activated { @include margin(-$action-sheet-ios-button-border-width, null, null, null); + @include border($action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-button-background-activated, null, null, null); - border-top: $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-button-background-activated; border-bottom-color: $action-sheet-ios-button-background-activated; background: $action-sheet-ios-button-background-activated; } diff --git a/src/components/alert/alert.ios.scss b/src/components/alert/alert.ios.scss index 6ff9f588974..b3f4498a2e9 100644 --- a/src/components/alert/alert.ios.scss +++ b/src/components/alert/alert.ios.scss @@ -373,8 +373,8 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; @include appearance(none); @include margin($alert-ios-input-margin-top, null, null, null); @include border-radius($alert-ios-input-border-radius); + @include border($alert-ios-input-border); - border: $alert-ios-input-border; background-color: $alert-ios-input-background-color; @include deprecated-variable(padding, $alert-ios-input-padding) { @@ -388,14 +388,13 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; .alert-ios .alert-radio-group, .alert-ios .alert-checkbox-group { + @include border($alert-ios-list-border-top, null, null, null); + overflow: scroll; max-height: $alert-ios-content-max-height; - border-top: $alert-ios-list-border-top; - -webkit-overflow-scrolling: touch; - } .alert-ios .alert-tappable { @@ -451,15 +450,13 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; .alert-ios [aria-checked=true] .alert-radio-inner { @include position($alert-ios-radio-icon-top, null, null, $alert-ios-radio-icon-start); + @include border-width(0, $alert-ios-radio-icon-border-width, $alert-ios-radio-icon-border-width, 0); position: absolute; width: $alert-ios-radio-icon-width; height: $alert-ios-radio-icon-height; - border-width: $alert-ios-radio-icon-border-width; - border-top-width: 0; - border-left-width: 0; border-style: $alert-ios-radio-icon-border-style; border-color: $alert-ios-radio-icon-border-color; transform: $alert-ios-radio-icon-transform; @@ -493,13 +490,13 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; .alert-ios .alert-checkbox-icon { @include border-radius($alert-ios-checkbox-border-radius); + @include border-width($alert-ios-checkbox-border-width); position: relative; width: $alert-ios-checkbox-size; height: $alert-ios-checkbox-size; - border-width: $alert-ios-checkbox-border-width; border-style: $alert-ios-checkbox-border-style; border-color: $alert-ios-checkbox-border-color-off; background-color: $alert-ios-checkbox-background-color-off; @@ -524,15 +521,13 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; .alert-ios [aria-checked=true] .alert-checkbox-inner { @include position($alert-ios-checkbox-icon-top, null, null, $alert-ios-checkbox-icon-start); + @include border-width(0, $alert-ios-checkbox-icon-border-width, $alert-ios-checkbox-icon-border-width, 0); position: absolute; width: $alert-ios-checkbox-icon-width; height: $alert-ios-checkbox-icon-height; - border-width: $alert-ios-checkbox-icon-border-width; - border-top-width: 0; - border-left-width: 0; border-style: $alert-ios-checkbox-icon-border-style; border-color: $alert-ios-checkbox-icon-border-color; transform: $alert-ios-checkbox-icon-transform; @@ -551,6 +546,8 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; .alert-ios .alert-button { @include margin($alert-ios-button-margin); @include border-radius($alert-ios-button-border-radius); + @include border($alert-ios-button-border-width $alert-ios-button-border-style $alert-ios-button-border-color, + $alert-ios-button-border-width $alert-ios-button-border-style $alert-ios-button-border-color, null, null); overflow: hidden; @@ -559,15 +556,13 @@ $alert-ios-checkbox-icon-transform: rotate(45deg) !default; min-width: $alert-ios-button-min-width; height: $alert-ios-button-min-height; - border-top: $alert-ios-button-border-width $alert-ios-button-border-style $alert-ios-button-border-color; - border-right: $alert-ios-button-border-width $alert-ios-button-border-style $alert-ios-button-border-color; font-size: $alert-ios-button-font-size; color: $alert-ios-button-text-color; background-color: $alert-ios-button-background-color; } .alert-ios .alert-button:last-child { - border-right: 0; + @include border(null, 0, null, null); font-weight: $alert-ios-button-main-font-weight; } diff --git a/src/components/alert/alert.md.scss b/src/components/alert/alert.md.scss index 17529faf6c7..b44b629636b 100644 --- a/src/components/alert/alert.md.scss +++ b/src/components/alert/alert.md.scss @@ -408,15 +408,14 @@ $alert-md-checkbox-icon-transform: rotate(45deg) !default; .alert-md .alert-input { @include margin($alert-md-input-margin-top, $alert-md-input-margin-end, $alert-md-input-margin-bottom, $alert-md-input-margin-start); + @include border(null, null, $alert-md-input-border-width $alert-md-input-border-style $alert-md-input-border-color, null); - border-bottom: $alert-md-input-border-width $alert-md-input-border-style $alert-md-input-border-color; color: $alert-md-input-text-color; } .alert-md .alert-input:focus { @include margin(null, null, $alert-md-input-margin-bottom - 1, null); - - border-bottom: $alert-md-input-border-width-focused $alert-md-input-border-style-focused $alert-md-input-border-color-focused; + @include border(null, null, $alert-md-input-border-width-focused $alert-md-input-border-style-focused $alert-md-input-border-color-focused, null); } @@ -425,13 +424,12 @@ $alert-md-checkbox-icon-transform: rotate(45deg) !default; .alert-md .alert-radio-group, .alert-md .alert-checkbox-group { + @include border($alert-md-list-border-top, null, $alert-md-list-border-bottom, null); + position: relative; overflow: auto; max-height: $alert-md-content-max-height; - - border-top: $alert-md-list-border-top; - border-bottom: $alert-md-list-border-bottom; } .alert-md .alert-tappable { @@ -469,6 +467,7 @@ $alert-md-checkbox-icon-transform: rotate(45deg) !default; .alert-md .alert-radio-icon { @include position($alert-md-radio-top, null, null, $alert-md-radio-left); @include border-radius($alert-md-radio-border-radius); + @include border-width($alert-md-radio-border-width); position: relative; display: block; @@ -476,7 +475,6 @@ $alert-md-checkbox-icon-transform: rotate(45deg) !default; width: $alert-md-radio-width; height: $alert-md-radio-height; - border-width: $alert-md-radio-border-width; border-style: $alert-md-radio-border-style; border-color: $alert-md-radio-border-color-off; } @@ -543,13 +541,13 @@ $alert-md-checkbox-icon-transform: rotate(45deg) !default; .alert-md .alert-checkbox-icon { @include position($alert-md-checkbox-top, null, null, $alert-md-checkbox-left); @include border-radius($alert-md-checkbox-border-radius); + @include border-width($alert-md-checkbox-border-width); position: relative; width: $alert-md-checkbox-width; height: $alert-md-checkbox-height; - border-width: $alert-md-checkbox-border-width; border-style: $alert-md-checkbox-border-style; border-color: $alert-md-checkbox-border-color-off; } @@ -564,15 +562,13 @@ $alert-md-checkbox-icon-transform: rotate(45deg) !default; .alert-md [aria-checked=true] .alert-checkbox-inner { @include position($alert-md-checkbox-icon-top, null, null, $alert-md-checkbox-icon-start); + @include border-width(0, $alert-md-checkbox-icon-border-width, $alert-md-checkbox-icon-border-width, 0); position: absolute; width: $alert-md-checkbox-icon-width; height: $alert-md-checkbox-icon-height; - border-width: $alert-md-checkbox-icon-border-width; - border-top-width: 0; - border-left-width: 0; border-style: $alert-md-checkbox-icon-border-style; border-color: $alert-md-checkbox-icon-border-color; transform: $alert-md-checkbox-icon-transform; diff --git a/src/components/alert/alert.scss b/src/components/alert/alert.scss index 3d0f7fadfcc..b3c350a34f3 100644 --- a/src/components/alert/alert.scss +++ b/src/components/alert/alert.scss @@ -79,8 +79,8 @@ ion-alert input { .alert-input { @include placeholder($alert-input-placeholder-color); @include padding(10px, 0); + @include border(0); - border: 0; background: inherit; } diff --git a/src/components/alert/alert.wp.scss b/src/components/alert/alert.wp.scss index 9a97cff3086..ab403c78a84 100644 --- a/src/components/alert/alert.wp.scss +++ b/src/components/alert/alert.wp.scss @@ -360,11 +360,11 @@ $alert-wp-checkbox-icon-transform: rotate(45deg) !default; .alert-wp .alert-wrapper { @include border-radius($alert-wp-border-radius); + @include border($alert-wp-border-width $alert-wp-border-style $alert-wp-border-color); width: $alert-wp-width; max-width: $alert-wp-max-width; - border: $alert-wp-border-width $alert-wp-border-style $alert-wp-border-color; background: $alert-wp-background; } @@ -418,7 +418,8 @@ $alert-wp-checkbox-icon-transform: rotate(45deg) !default; // -------------------------------------------------- .alert-wp .alert-input { - border: $alert-wp-input-border-width $alert-wp-input-border-style $alert-wp-input-border-color; + @include border($alert-wp-input-border-width $alert-wp-input-border-style $alert-wp-input-border-color); + line-height: $alert-wp-input-line-height; color: $alert-wp-input-text-color; @@ -483,6 +484,7 @@ $alert-wp-checkbox-icon-transform: rotate(45deg) !default; @include position($alert-wp-radio-top, null, null, $alert-wp-radio-left); @include margin(0); @include border-radius($alert-wp-radio-border-radius); + @include border-width($alert-wp-radio-border-width); position: relative; display: block; @@ -490,7 +492,6 @@ $alert-wp-checkbox-icon-transform: rotate(45deg) !default; width: $alert-wp-radio-width; height: $alert-wp-radio-height; - border-width: $alert-wp-radio-border-width; border-style: $alert-wp-radio-border-style; border-color: $alert-wp-radio-border-color; } @@ -555,13 +556,13 @@ $alert-wp-checkbox-icon-transform: rotate(45deg) !default; .alert-wp .alert-checkbox-icon { @include position($alert-wp-checkbox-top, null, null, $alert-wp-checkbox-left); @include border-radius($alert-wp-checkbox-border-radius); + @include border-width($alert-wp-checkbox-border-width); position: relative; width: $alert-wp-checkbox-width; height: $alert-wp-checkbox-height; - border-width: $alert-wp-checkbox-border-width; border-style: $alert-wp-checkbox-border-style; border-color: $alert-wp-checkbox-border-color; background: $alert-wp-checkbox-background-off; @@ -580,15 +581,13 @@ $alert-wp-checkbox-icon-transform: rotate(45deg) !default; .alert-wp [aria-checked=true] .alert-checkbox-inner { @include position($alert-wp-checkbox-icon-top, null, null, $alert-wp-checkbox-icon-start); + @include border-width(0, $alert-wp-checkbox-icon-border-width, $alert-wp-checkbox-icon-border-width, 0); position: absolute; width: $alert-wp-checkbox-icon-width; height: $alert-wp-checkbox-icon-height; - border-width: $alert-wp-checkbox-icon-border-width; - border-top-width: 0; - border-left-width: 0; border-style: $alert-wp-checkbox-icon-border-style; border-color: $alert-wp-checkbox-icon-border-color; transform: $alert-wp-checkbox-icon-transform; diff --git a/src/components/button/button.ios.scss b/src/components/button/button.ios.scss index 0b53a53b3a1..1345f2d6e1d 100644 --- a/src/components/button/button.ios.scss +++ b/src/components/button/button.ios.scss @@ -284,9 +284,7 @@ $button-ios-strong-font-weight: 600 !default; .button-full-ios { @include margin-horizontal(0); @include border-radius(0); - - border-right-width: 0; - border-left-width: 0; + @include border-width(null, 0, null, 0); } // iOS Outline Button @@ -294,8 +292,8 @@ $button-ios-strong-font-weight: 600 !default; .button-outline-ios { @include border-radius($button-ios-outline-border-radius); + @include border-width($button-ios-outline-border-width); - border-width: $button-ios-outline-border-width; border-style: $button-ios-outline-border-style; border-color: $button-ios-outline-border-color; color: $button-ios-outline-text-color; diff --git a/src/components/button/button.md.scss b/src/components/button/button.md.scss index dc4ca05fc51..f694d23bc06 100644 --- a/src/components/button/button.md.scss +++ b/src/components/button/button.md.scss @@ -342,16 +342,14 @@ $button-md-strong-font-weight: bold !default; .button-full-md { @include margin-horizontal(0); @include border-radius(0); - - border-right-width: 0; - border-left-width: 0; + @include border-width(null, 0, null, 0); } // Material Design Outline Button // -------------------------------------------------- .button-outline-md { - border-width: $button-md-outline-border-width; + @include border-width($button-md-outline-border-width); border-style: $button-md-outline-border-style; border-color: $button-md-outline-border-color; color: $button-md-outline-text-color; diff --git a/src/components/button/button.scss b/src/components/button/button.scss index b806a5fc1f9..ec4a5ab2a62 100644 --- a/src/components/button/button.scss +++ b/src/components/button/button.scss @@ -106,7 +106,5 @@ button[disabled], .button-full.button-outline { @include border-radius(0); - - border-right-width: 0; - border-left-width: 0; + @include border-width(null, 0, null, 0); } diff --git a/src/components/button/button.wp.scss b/src/components/button/button.wp.scss index d5b67f1610b..a22f33ccb54 100644 --- a/src/components/button/button.wp.scss +++ b/src/components/button/button.wp.scss @@ -189,11 +189,10 @@ $button-wp-strong-font-weight: bold !default; .button-wp { @include border-radius($button-wp-border-radius); + @include border($button-wp-border-width $button-wp-border-style $button-wp-border-color); height: $button-wp-height; - border: $button-wp-border-width $button-wp-border-style $button-wp-border-color; - font-size: $button-wp-font-size; color: $button-wp-text-color; @@ -282,16 +281,14 @@ $button-wp-strong-font-weight: bold !default; .button-full-wp { @include margin-horizontal(0); @include border-radius(0); - - border-right-width: 0; - border-left-width: 0; + @include border-width(null, 0, null, 0); } // Windows Outline Button // -------------------------------------------------- .button-outline-wp { - border-width: $button-wp-outline-border-width; + @include border-width($button-wp-outline-border-width); border-style: $button-wp-outline-border-style; border-color: $button-wp-outline-border-color; color: $button-wp-outline-text-color; diff --git a/src/components/card/card.ios.scss b/src/components/card/card.ios.scss index 6147b151a32..b4a5b11e5f2 100644 --- a/src/components/card/card.ios.scss +++ b/src/components/card/card.ios.scss @@ -139,11 +139,11 @@ $card-ios-header-color: #333 !default; .card-ios > .item:last-child, .card-ios > .item:last-child .item-inner, .card-ios > .item-wrapper:last-child .item { - border-bottom: 0; + @include border(null, null, 0, null); } .card-ios .item-ios.item-block .item-inner { - border: 0; + @include border(0); } .card-content-ios { diff --git a/src/components/card/card.md.scss b/src/components/card/card.md.scss index 1258a1eef04..21e27ee4886 100644 --- a/src/components/card/card.md.scss +++ b/src/components/card/card.md.scss @@ -144,11 +144,11 @@ $card-md-header-color: #222 !default; .card-md > .item:last-child, .card-md > .item:last-child .item-inner, .card-md > .item-wrapper:last-child .item { - border-bottom: 0; + @include border(null, null, 0, null); } .card-md .item-md.item-block .item-inner { - border: 0; + @include border(0); } .card-content-md { diff --git a/src/components/card/card.wp.scss b/src/components/card/card.wp.scss index 703a9648a59..b80d8c1030c 100644 --- a/src/components/card/card.wp.scss +++ b/src/components/card/card.wp.scss @@ -145,11 +145,11 @@ $card-wp-header-color: #222 !default; .card-wp > .item:last-child, .card-wp > .item:last-child .item-inner, .card-wp > .item-wrapper:last-child .item { - border-bottom: 0; + @include border(null, null, 0, null); } .card-wp .item-wp.item-block .item-inner { - border: 0; + @include border(0); } .card-content-wp { diff --git a/src/components/checkbox/checkbox.ios.scss b/src/components/checkbox/checkbox.ios.scss index 7116598c753..8e09da691c3 100644 --- a/src/components/checkbox/checkbox.ios.scss +++ b/src/components/checkbox/checkbox.ios.scss @@ -81,13 +81,13 @@ $checkbox-ios-item-end-margin-start: 0 !default; .checkbox-ios .checkbox-icon { @include border-radius($checkbox-ios-icon-border-radius); + @include border-width($checkbox-ios-icon-border-width); position: relative; width: $checkbox-ios-icon-size; height: $checkbox-ios-icon-size; - border-width: $checkbox-ios-icon-border-width; border-style: $checkbox-ios-icon-border-style; border-color: $checkbox-ios-icon-border-color-off; background-color: $checkbox-ios-background-color-off; @@ -108,15 +108,13 @@ $checkbox-ios-item-end-margin-start: 0 !default; .checkbox-ios .checkbox-checked .checkbox-inner { @include position(4px, null, null, 7px); + @include border-width(0, $checkbox-ios-icon-checkmark-width, $checkbox-ios-icon-checkmark-width, 0); position: absolute; width: 4px; height: 9px; - border-width: $checkbox-ios-icon-checkmark-width; - border-top-width: 0; - border-left-width: 0; border-style: $checkbox-ios-icon-checkmark-style; border-color: $checkbox-ios-icon-checkmark-color; transform: rotate(45deg); diff --git a/src/components/checkbox/checkbox.md.scss b/src/components/checkbox/checkbox.md.scss index ad76e0c5ce5..2c3135229ec 100644 --- a/src/components/checkbox/checkbox.md.scss +++ b/src/components/checkbox/checkbox.md.scss @@ -96,13 +96,13 @@ $checkbox-md-item-end-margin-start: 0 !default; .checkbox-md .checkbox-icon { @include border-radius($checkbox-md-icon-border-radius); + @include border-width($checkbox-md-icon-border-width); position: relative; width: $checkbox-md-icon-size; height: $checkbox-md-icon-size; - border-width: $checkbox-md-icon-border-width; border-style: $checkbox-md-icon-border-style; border-color: $checkbox-md-icon-border-color-off; background-color: $checkbox-md-icon-background-color-off; @@ -127,15 +127,13 @@ $checkbox-md-item-end-margin-start: 0 !default; .checkbox-md .checkbox-checked .checkbox-inner { @include position(0, null, null, 4px); + @include border-width(0, $checkbox-md-icon-checkmark-width, $checkbox-md-icon-checkmark-width, 0); position: absolute; width: 5px; height: 10px; - border-width: $checkbox-md-icon-checkmark-width; - border-top-width: 0; - border-left-width: 0; border-style: $checkbox-md-icon-checkmark-style; border-color: $checkbox-md-icon-checkmark-color; transform: rotate(45deg); diff --git a/src/components/checkbox/checkbox.wp.scss b/src/components/checkbox/checkbox.wp.scss index 517e82871c8..fa4cbfc1595 100644 --- a/src/components/checkbox/checkbox.wp.scss +++ b/src/components/checkbox/checkbox.wp.scss @@ -89,13 +89,13 @@ $checkbox-wp-item-end-margin-start: 0 !default; .checkbox-wp .checkbox-icon { @include border-radius($checkbox-wp-icon-border-radius); + @include border-width($checkbox-wp-icon-border-width); position: relative; width: $checkbox-wp-icon-size; height: $checkbox-wp-icon-size; - border-width: $checkbox-wp-icon-border-width; border-style: $checkbox-wp-icon-border-style; border-color: $checkbox-wp-icon-border-color-off; background-color: $checkbox-wp-icon-background-color-off; @@ -116,15 +116,13 @@ $checkbox-wp-item-end-margin-start: 0 !default; .checkbox-wp .checkbox-checked .checkbox-inner { @include position(-2px, null, null, 3px); + @include border-width(0, $checkbox-wp-icon-checkmark-width, $checkbox-wp-icon-checkmark-width, 0); position: absolute; width: 6px; height: 12px; - border-width: $checkbox-wp-icon-checkmark-width; - border-top-width: 0; - border-left-width: 0; border-style: $checkbox-wp-icon-checkmark-style; border-color: $checkbox-wp-icon-checkmark-color; transform: rotate(45deg); diff --git a/src/components/chip/chip.wp.scss b/src/components/chip/chip.wp.scss index 58986960933..d12ed544645 100644 --- a/src/components/chip/chip.wp.scss +++ b/src/components/chip/chip.wp.scss @@ -81,7 +81,7 @@ $chip-wp-icon-text-color: color-contrast($colors-wp, $chip-wp-icon-ba } .chip-wp .button { - border: 0; + @include border(0); } diff --git a/src/components/datetime/datetime.wp.scss b/src/components/datetime/datetime.wp.scss index 3d30441852d..1b7afe07119 100644 --- a/src/components/datetime/datetime.wp.scss +++ b/src/components/datetime/datetime.wp.scss @@ -40,10 +40,10 @@ $datetime-wp-placeholder-color: $input-wp-border-color !default; .datetime-wp .datetime-text { @include padding(0, 8px); + @include border($datetime-wp-border-width solid $datetime-wp-border-color); min-height: 3.4rem; - border: $datetime-wp-border-width solid $datetime-wp-border-color; line-height: 3rem; } diff --git a/src/components/input/input.scss b/src/components/input/input.scss index 119a87e8333..41b8b080b3d 100644 --- a/src/components/input/input.scss +++ b/src/components/input/input.scss @@ -47,6 +47,7 @@ ion-textarea { @include placeholder($text-input-placeholder-color); @include appearance(none); @include border-radius(0); + @include border(0); display: inline-block; @@ -55,8 +56,6 @@ ion-textarea { width: 92%; width: calc(100% - 10px); - border: 0; - background: transparent; } diff --git a/src/components/input/input.wp.scss b/src/components/input/input.wp.scss index 5d862920c45..96fc1139f8f 100644 --- a/src/components/input/input.wp.scss +++ b/src/components/input/input.wp.scss @@ -74,10 +74,10 @@ $text-input-wp-highlight-color-invalid: $text-input-highlight-color-invalid .text-input-wp { @include margin($text-input-wp-margin-top, $text-input-wp-margin-end, $text-input-wp-margin-bottom, $text-input-wp-margin-start); @include padding($text-input-wp-padding-vertical, $text-input-wp-padding-horizontal); + @include border($text-input-wp-border-width solid $text-input-wp-border-color); width: calc(100% - #{$text-input-wp-margin-end} - #{$text-input-wp-margin-start}); - border: $text-input-wp-border-width solid $text-input-wp-border-color; line-height: $text-input-wp-line-height; } diff --git a/src/components/item/item.ios.scss b/src/components/item/item.ios.scss index 4493c7eb143..282fdf648a7 100644 --- a/src/components/item/item.ios.scss +++ b/src/components/item/item.ios.scss @@ -124,8 +124,7 @@ $item-ios-sliding-content-background: $list-ios-background-color !default; .item-ios.item-block .item-inner { @include padding-horizontal(null, $item-ios-padding-end / 2); - - border-bottom: $hairlines-width solid $list-ios-border-color; + @include border(null, null, $hairlines-width solid $list-ios-border-color, null); } @@ -233,12 +232,12 @@ $item-ios-sliding-content-background: $list-ios-background-color !default; // -------------------------------------------------- ion-item-group .item-ios:first-child .item-inner { - border-top-width: 0; + @include border-width(0, null, null, null); } ion-item-group .item-ios:last-child .item-inner, ion-item-group .item-wrapper:last-child .item-ios .item-inner { - border: 0; + @include border(0); } diff --git a/src/components/item/item.md.scss b/src/components/item/item.md.scss index 4aeb055a26e..728e41f3afc 100644 --- a/src/components/item/item.md.scss +++ b/src/components/item/item.md.scss @@ -69,7 +69,7 @@ $item-md-sliding-content-background: $list-md-background-color !default; } .item-md[no-lines] { - border-width: 0; + @include border-width(0); } .item-md h1 { @@ -110,8 +110,7 @@ $item-md-sliding-content-background: $list-md-background-color !default; .item-md.item-block .item-inner { @include padding-horizontal(null, ($item-md-padding-end / 2)); - - border-bottom: 1px solid $list-md-border-color; + @include border(null, null, 1px solid $list-md-border-color, null); } @@ -223,12 +222,12 @@ $item-md-sliding-content-background: $list-md-background-color !default; // -------------------------------------------------- ion-item-group .item-md:first-child .item-inner { - border-top-width: 0; + @include border-width(0, null, null, null); } ion-item-group .item-md:last-child .item-inner, ion-item-group .item-md .item-wrapper:last-child .item-inner { - border: 0; + @include border(0); } @@ -237,8 +236,8 @@ ion-item-group .item-md .item-wrapper:last-child .item-inner { .item-divider-md { @include padding-horizontal($item-md-padding-start, null); + @include border(null, null, $item-md-divider-border-bottom, null); - border-bottom: $item-md-divider-border-bottom; font-size: $item-md-divider-font-size; color: $item-md-divider-color; background-color: $item-md-divider-background; diff --git a/src/components/item/item.scss b/src/components/item/item.scss index 940e1250abc..db50dd712d8 100644 --- a/src/components/item/item.scss +++ b/src/components/item/item.scss @@ -22,6 +22,7 @@ @include margin(0); @include padding(0); @include text-align(initial); + @include border(0); display: flex; overflow: hidden; @@ -32,8 +33,6 @@ width: 100%; min-height: 4.4rem; - border: 0; - font-weight: normal; line-height: normal; text-decoration: none; @@ -43,6 +42,7 @@ .item-inner { @include margin(0); @include padding(0); + @include border(0); display: flex; overflow: hidden; @@ -53,8 +53,6 @@ align-self: stretch; min-height: inherit; - - border: 0; } .input-wrapper { @@ -71,7 +69,7 @@ .item[no-lines], .item.item[no-lines] .item-inner { - border: 0; + @include border(0); } ion-item-group { diff --git a/src/components/item/item.wp.scss b/src/components/item/item.wp.scss index 5183caa56d2..654a06cf111 100644 --- a/src/components/item/item.wp.scss +++ b/src/components/item/item.wp.scss @@ -74,7 +74,7 @@ $item-wp-sliding-content-background: $list-wp-background-color !default; } .item-wp[no-lines] { - border-width: 0; + @include border-width(0); } .item-wp h1 { @@ -115,8 +115,7 @@ $item-wp-sliding-content-background: $list-wp-background-color !default; .item-wp.item-block .item-inner { @include padding-horizontal(null, ($item-wp-padding-end / 2)); - - border-bottom: 1px solid $list-wp-border-color; + @include border(null, null, 1px solid $list-wp-border-color, null); } @@ -234,8 +233,8 @@ $item-wp-sliding-content-background: $list-wp-background-color !default; .item-divider-wp { @include padding-horizontal($item-wp-padding-start, null); + @include border(null, null, $item-wp-divider-border-bottom, null); - border-bottom: $item-wp-divider-border-bottom; font-size: $item-wp-divider-font-size; color: $item-wp-divider-color; diff --git a/src/components/list/list.ios.scss b/src/components/list/list.ios.scss index a7f6bc1cd86..f69f180b1f3 100644 --- a/src/components/list/list.ios.scss +++ b/src/components/list/list.ios.scss @@ -73,21 +73,21 @@ $list-ios-header-background-color: transparent !default; } .list-ios > .item-block:first-child { - border-top: $hairlines-width solid $list-ios-border-color; + @include border($hairlines-width solid $list-ios-border-color, null, null, null); } .list-ios > .item-block:last-child, .list-ios > .item-wrapper:last-child .item-block { - border-bottom: $hairlines-width solid $list-ios-border-color; + @include border(null, null, $hairlines-width solid $list-ios-border-color, null); } .list-ios > .item-block:last-child .item-inner, .list-ios > .item-wrapper:last-child .item-block .item-inner { - border-bottom: 0; + @include border(null, null, 0, null); } .list-ios .item-block .item-inner { - border-bottom: $hairlines-width solid $list-ios-border-color; + @include border(null, null, $hairlines-width solid $list-ios-border-color, null); } // If the item has the no-lines attribute remove the bottom border from: @@ -95,16 +95,17 @@ $list-ios-header-background-color: transparent !default; // the item-inner class (if it is not last) .list-ios .item[no-lines], .list-ios .item[no-lines] .item-inner { - border-width: 0; + @include border-width(0); } .list-ios ion-item-options { - border-bottom: $hairlines-width solid $list-ios-border-color; + @include border(null, null, $hairlines-width solid $list-ios-border-color, null); } .list-ios ion-item-options .button { @include margin(0); @include border-radius(0); + @include border(0); display: inline-flex; @@ -113,8 +114,6 @@ $list-ios-header-background-color: transparent !default; height: 100%; min-height: 100%; - border: 0; - box-sizing: border-box; } @@ -141,21 +140,21 @@ $list-ios-header-background-color: transparent !default; } .list-ios[inset] .item { - border-bottom: 1px solid $list-ios-border-color; + @include border(null, null, 1px solid $list-ios-border-color, null); } .list-ios[inset] .item-inner { - border-bottom: 0; + @include border(null, null, 0, null); } .list-ios[inset] > .item:first-child, .list-ios[inset] > .item-wrapper:first-child .item { - border-top: 0; + @include border(1, null, null, null); } .list-ios[inset] > .item:last-child, .list-ios[inset] > .item-wrapper:last-child .item { - border-bottom: 0; + @include border(null, null, 0, null); } .list-ios[inset] + ion-list[inset] { @@ -170,7 +169,7 @@ $list-ios-header-background-color: transparent !default; .list-ios[no-lines] ion-item-options, .list-ios[no-lines] .item, .list-ios[no-lines] .item .item-inner { - border-width: 0; + @include border-width(0); } @@ -179,10 +178,10 @@ $list-ios-header-background-color: transparent !default; .list-header-ios { @include padding-horizontal($list-ios-header-padding-start, null); + @include border(null, null, $list-ios-header-border-bottom, null); position: relative; - border-bottom: $list-ios-header-border-bottom; font-size: $list-ios-header-font-size; font-weight: $list-ios-header-font-weight; letter-spacing: $list-ios-header-letter-spacing; diff --git a/src/components/list/list.md.scss b/src/components/list/list.md.scss index b4737e2e6c3..c442a6436bc 100644 --- a/src/components/list/list.md.scss +++ b/src/components/list/list.md.scss @@ -67,14 +67,14 @@ $list-md-header-color: #757575 !default; } .list-md .item-block .item-inner { - border-bottom: 1px solid $list-md-border-color; + @include border(null, null, 1px solid $list-md-border-color, null); } .list-md > .item-block:last-child, .list-md > .item-wrapper:last-child { ion-label, .item-inner { - border-bottom: 0; + @include border(null, null, 0, null); } } @@ -83,12 +83,13 @@ $list-md-header-color: #757575 !default; } .list-md ion-item-options { - border-bottom: 1px solid $list-md-border-color; + @include border(null, null, 1px solid $list-md-border-color, null); } .list-md ion-item-options .button { @include margin(1px, 0); @include border-radius(0); + @include border(0); display: inline-flex; @@ -96,8 +97,6 @@ $list-md-header-color: #757575 !default; height: calc(100% - 2px); - border: 0; - box-shadow: none; box-sizing: border-box; @@ -112,7 +111,7 @@ $list-md-header-color: #757575 !default; // the item-inner class (if it is not last) .list-md .item[no-lines], .list-md .item[no-lines] .item-inner { - border-width: 0; + @include border-width(0); } .list-md + ion-list ion-list-header { @@ -130,14 +129,12 @@ $list-md-header-color: #757575 !default; .list-md[inset] .item:first-child { @include border-radius($list-inset-md-border-radius, $list-inset-md-border-radius, null, null); - - border-top-width: 0; + @include border-width(0, null, null, null); } .list-md[inset] .item:last-child { @include border-radius(null, null, $list-inset-md-border-radius, $list-inset-md-border-radius); - - border-bottom-width: 0; + @include border-width(null, null, 0, null); } .list-md[inset] .item-input { @@ -159,7 +156,7 @@ $list-md-header-color: #757575 !default; .list-md[no-lines] .item-block, .list-md[no-lines] ion-item-options, .list-md[no-lines] .item .item-inner { - border-width: 0; + @include border-width(0); } @@ -169,10 +166,10 @@ $list-md-header-color: #757575 !default; .list-header-md { @include padding-horizontal($list-md-header-padding-start, null); @include margin(null, null, $list-md-header-margin-bottom, null); + @include border($list-md-header-border-top, null, null, null); min-height: $list-md-header-min-height; - border-top: $list-md-header-border-top; font-size: $list-md-header-font-size; color: $list-md-header-color; } @@ -192,5 +189,5 @@ $list-md-header-color: #757575 !default; // -------------------------------------------------- .list-md .item-input:last-child { - border-bottom: 1px solid $list-md-border-color; + @include border(null, null, 1px solid $list-md-border-color, null); } diff --git a/src/components/list/list.wp.scss b/src/components/list/list.wp.scss index bf3fc2e46aa..e7d0f00f876 100644 --- a/src/components/list/list.wp.scss +++ b/src/components/list/list.wp.scss @@ -61,24 +61,24 @@ $list-wp-header-color: $list-wp-text-color !default; } .list-wp .item-block .item-inner { - border-bottom: 1px solid $list-wp-border-color; + @include border(null, null, 1px solid $list-wp-border-color, null); } .list-wp > .item-block:first-child, .list-wp > .item-wrapper:first-child .item-block { - border-top: 1px solid $list-wp-border-color; + @include border(1px solid $list-wp-border-color, null, null, null); } .list-wp > .item-block:last-child, .list-wp > .item-wrapper:last-child .item-block { - border-bottom: 1px solid $list-wp-border-color; + @include border(null, null, 1px solid $list-wp-border-color, null); } .list-wp > .item-block:last-child, .list-wp > .item-wrapper:last-child { ion-label, .item-inner { - border-bottom: 0; + @include border(null, null, 0, null); } } @@ -89,6 +89,7 @@ $list-wp-header-color: $list-wp-text-color !default; .list-wp ion-item-options .button { @include margin(1px, 0); @include border-radius(0); + @include border(0); display: inline-flex; @@ -96,7 +97,6 @@ $list-wp-header-color: $list-wp-text-color !default; height: calc(100% - 2px); - border: 0; box-shadow: none; box-sizing: border-box; @@ -111,7 +111,7 @@ $list-wp-header-color: $list-wp-text-color !default; // the item-inner class (if it is not last) .list-wp .item[no-lines], .list-wp .item[no-lines] .item-inner { - border-width: 0; + @include border-width(0); } .list-wp + ion-list ion-list-header { @@ -130,14 +130,12 @@ $list-wp-header-color: $list-wp-text-color !default; .list-wp[inset] .item:first-child { @include border-radius($list-inset-wp-border-radius, $list-inset-wp-border-radius, null, null); - - border-top-width: 0; + @include border-width(0, null, null, null); } .list-wp[inset] .item:last-child { @include border-radius(null, null, $list-inset-wp-border-radius, $list-inset-wp-border-radius); - - border-bottom-width: 0; + @include border-width(null, null, 0, null); } .list-wp[inset] .item-input { @@ -158,7 +156,7 @@ $list-wp-header-color: $list-wp-text-color !default; .list-wp[no-lines] .item, .list-wp[no-lines] .item .item-inner { - border-width: 0; + @include border-width(0); } @@ -167,8 +165,8 @@ $list-wp-header-color: $list-wp-text-color !default; .list-header-wp { @include padding-horizontal($list-wp-header-padding-start, null); + @include border(null, null, $list-wp-header-border-bottom, null); - border-bottom: $list-wp-header-border-bottom; font-size: $list-wp-header-font-size; color: $list-wp-header-color; } diff --git a/src/components/picker/picker.ios.scss b/src/components/picker/picker.ios.scss index d75876a149a..f095da10eca 100644 --- a/src/components/picker/picker.ios.scss +++ b/src/components/picker/picker.ios.scss @@ -75,20 +75,20 @@ $picker-ios-option-offset-y: (($picker-ios-height - $picker-io $picker-button-ios-strong-font-weight: 600 !default; .picker-ios .picker-wrapper { - height: $picker-ios-height; + @include border(1px solid $picker-ios-border-color, null, null, null); - border-top: 1px solid $picker-ios-border-color; + height: $picker-ios-height; background: $picker-ios-background-color; } .picker-ios .picker-toolbar { + @include border(null, null, $hairlines-width solid $picker-ios-border-color, null); + display: flex; height: $picker-ios-toolbar-height; - border-bottom: $hairlines-width solid $picker-ios-border-color; - background: $picker-ios-toolbar-background-color; } @@ -172,6 +172,7 @@ $picker-button-ios-strong-font-weight: 600 !default; .picker-ios .picker-above-highlight { @include position(0, null, null, 0); @include transform(translate3d(0, 0, 90px)); + @include border(null, null, 1px solid $picker-ios-border-color, null); position: absolute; z-index: 10; @@ -180,8 +181,6 @@ $picker-button-ios-strong-font-weight: 600 !default; width: 100%; height: $picker-ios-option-offset-y + 4px; - border-bottom: 1px solid $picker-ios-border-color; - background: linear-gradient(to bottom, rgba($picker-ios-background-color, 1) 20%, rgba($picker-ios-background-color, .7) 100%); @@ -190,6 +189,7 @@ $picker-button-ios-strong-font-weight: 600 !default; .picker-ios .picker-below-highlight { @include position($picker-ios-option-offset-y + $picker-ios-option-height - 4, null, null, 0); @include transform(translate3d(0, 0, 90px)); + @include border(1px solid $picker-ios-border-color, null, null, null); position: absolute; @@ -199,8 +199,6 @@ $picker-button-ios-strong-font-weight: 600 !default; width: 100%; height: $picker-ios-option-offset-y + $picker-ios-option-height; - border-top: 1px solid $picker-ios-border-color; - background: linear-gradient(to top, rgba($picker-ios-background-color, 1) 30%, rgba($picker-ios-background-color, .7) 100%); diff --git a/src/components/picker/picker.md.scss b/src/components/picker/picker.md.scss index 5c2e126b652..433022ab447 100644 --- a/src/components/picker/picker.md.scss +++ b/src/components/picker/picker.md.scss @@ -74,9 +74,9 @@ $picker-md-option-selected-color: color($colors-md, primary) !defaul .picker-md .picker-wrapper { - height: $picker-md-height; + @include border($hairlines-width solid $picker-md-border-color, null, null, null); - border-top: $hairlines-width solid $picker-md-border-color; + height: $picker-md-height; background: $picker-md-background-color; } @@ -164,6 +164,7 @@ $picker-md-option-selected-color: color($colors-md, primary) !defaul .picker-md .picker-above-highlight { @include position(0, null, null, 0); @include transform(translate3d(0, 0, 90px)); + @include border(null, null, 1px solid $picker-md-border-color, null); position: absolute; z-index: 10; @@ -171,8 +172,6 @@ $picker-md-option-selected-color: color($colors-md, primary) !defaul width: 100%; height: $picker-md-option-offset-y + 4px; - border-bottom: 1px solid $picker-md-border-color; - background: linear-gradient(to bottom, rgba($picker-md-background-color, 1) 20%, rgba($picker-md-background-color, .7) 100%); @@ -181,6 +180,7 @@ $picker-md-option-selected-color: color($colors-md, primary) !defaul .picker-md .picker-below-highlight { @include position($picker-md-option-offset-y + $picker-md-option-height - 4, null, null, 0); @include transform(translate3d(0, 0, 90px)); + @include border(1px solid $picker-md-border-color, null, null, null); position: absolute; z-index: 11; @@ -188,8 +188,6 @@ $picker-md-option-selected-color: color($colors-md, primary) !defaul width: 100%; height: $picker-md-option-offset-y + $picker-md-option-height; - border-top: 1px solid $picker-md-border-color; - background: linear-gradient(to top, rgba($picker-md-background-color, 1) 30%, rgba($picker-md-background-color, .7) 100%); diff --git a/src/components/picker/picker.wp.scss b/src/components/picker/picker.wp.scss index e19f9332153..17141be5887 100644 --- a/src/components/picker/picker.wp.scss +++ b/src/components/picker/picker.wp.scss @@ -74,22 +74,22 @@ $picker-wp-option-selected-color: color($colors-wp, primary) !defaul .picker-wp .picker-wrapper { - height: $picker-wp-height; + @include border($hairlines-width solid $picker-wp-border-color, null, null, null); - border-top: $hairlines-width solid $picker-wp-border-color; + height: $picker-wp-height; background: $picker-wp-background-color; } .picker-wp .picker-toolbar { + @include border-width($hairlines-width); + display: flex; justify-content: flex-end; height: $picker-wp-toolbar-height; - border-width: $hairlines-width; - background: $picker-wp-toolbar-background-color; } @@ -176,6 +176,7 @@ $picker-wp-option-selected-color: color($colors-wp, primary) !defaul .picker-wp .picker-above-highlight { @include position(0, null, null, 0); @include transform(translate3d(0, 0, 90px)); + @include border(null, null, 1px solid $picker-wp-border-color, null); position: absolute; @@ -184,8 +185,6 @@ $picker-wp-option-selected-color: color($colors-wp, primary) !defaul width: 100%; height: $picker-wp-option-offset-y + 4px; - border-bottom: 1px solid $picker-wp-border-color; - background: linear-gradient(to bottom, rgba($picker-wp-background-color, 1) 20%, rgba($picker-wp-background-color, .7) 100%); @@ -194,6 +193,7 @@ $picker-wp-option-selected-color: color($colors-wp, primary) !defaul .picker-wp .picker-below-highlight { @include position($picker-wp-option-offset-y + $picker-wp-option-height - 4, null, null, 0); @include transform(translate3d(0, 0, 90px)); + @include border(1px solid $picker-wp-border-color, null, null, null); position: absolute; @@ -202,8 +202,6 @@ $picker-wp-option-selected-color: color($colors-wp, primary) !defaul width: 100%; height: $picker-wp-option-offset-y + $picker-wp-option-height; - border-top: 1px solid $picker-wp-border-color; - background: linear-gradient(to top, rgba($picker-wp-background-color, 1) 30%, rgba($picker-wp-background-color, .7) 100%); diff --git a/src/components/popover/popover.wp.scss b/src/components/popover/popover.wp.scss index b7d2b11f1d8..b7824645bde 100644 --- a/src/components/popover/popover.wp.scss +++ b/src/components/popover/popover.wp.scss @@ -31,13 +31,13 @@ $popover-wp-background: $background-wp-color !default; .popover-wp .popover-content { @include border-radius($popover-wp-border-radius); @include transform-origin(start, top); + @include border($popover-wp-border); width: $popover-wp-width; min-width: $popover-wp-min-width; min-height: $popover-wp-min-height; max-height: $popover-wp-max-height; - border: $popover-wp-border; color: $popover-wp-text-color; background: $popover-wp-background; } diff --git a/src/components/radio/radio.ios.scss b/src/components/radio/radio.ios.scss index a20fd09752f..aa919cd3661 100644 --- a/src/components/radio/radio.ios.scss +++ b/src/components/radio/radio.ios.scss @@ -75,15 +75,13 @@ $radio-ios-item-end-margin-start: ($item-ios-padding-start / 2) !default; .radio-ios .radio-checked .radio-inner { @include position(4px, null, null, 7px); + @include border-width(0, $radio-ios-icon-border-width, $radio-ios-icon-border-width, 0); position: absolute; width: 5px; height: 12px; - border-width: $radio-ios-icon-border-width; - border-top-width: 0; - border-left-width: 0; border-style: $radio-ios-icon-border-style; border-color: $radio-ios-color-on; transform: rotate(45deg); diff --git a/src/components/radio/radio.md.scss b/src/components/radio/radio.md.scss index 3c8a5351683..138873896b5 100644 --- a/src/components/radio/radio.md.scss +++ b/src/components/radio/radio.md.scss @@ -77,6 +77,7 @@ $radio-md-item-end-margin-start: 0 !default; @include position(0, null, null, 0); @include margin(0); @include border-radius($radio-md-icon-border-radius); + @include border-width($radio-md-icon-border-width); position: relative; display: block; @@ -84,7 +85,6 @@ $radio-md-item-end-margin-start: 0 !default; width: $radio-md-icon-width; height: $radio-md-icon-height; - border-width: $radio-md-icon-border-width; border-style: $radio-md-icon-border-style; border-color: $radio-md-color-off; } diff --git a/src/components/radio/radio.wp.scss b/src/components/radio/radio.wp.scss index c08c77355a1..c871b1764a1 100644 --- a/src/components/radio/radio.wp.scss +++ b/src/components/radio/radio.wp.scss @@ -74,6 +74,7 @@ $radio-wp-item-end-margin-start: 0 !default; @include position(0, null, null, 0); @include margin(0); @include border-radius($radio-wp-icon-border-radius); + @include border-width($radio-wp-icon-border-width); position: relative; @@ -82,7 +83,6 @@ $radio-wp-item-end-margin-start: 0 !default; width: $radio-wp-icon-width; height: $radio-wp-icon-height; - border-width: $radio-wp-icon-border-width; border-style: $radio-wp-icon-border-style; border-color: $radio-wp-color-off; } diff --git a/src/components/range/range.md.scss b/src/components/range/range.md.scss index 90f792e86b9..85a6561b571 100644 --- a/src/components/range/range.md.scss +++ b/src/components/range/range.md.scss @@ -232,7 +232,7 @@ $range-md-pin-min-background-color: $range-md-bar-background-color !def @mixin md-range-min() { .range-md .range-knob-min.range-knob-min { .range-knob { - border: $range-md-knob-min-border; + @include border($range-md-knob-min-border); background: $range-md-knob-min-background-color; } diff --git a/src/components/refresher/refresher.scss b/src/components/refresher/refresher.scss index 97a9f293b19..5fc9c34f102 100644 --- a/src/components/refresher/refresher.scss +++ b/src/components/refresher/refresher.scss @@ -44,8 +44,8 @@ ion-refresher { // this transition is what is used to put the // scroll content back into it's original location @include margin(-1px, null, null, null); + @include border(1px solid $refresher-border-color, null, null, null); - border-top: 1px solid $refresher-border-color; transition: transform 320ms cubic-bezier(.36, .66, .04, 1); } diff --git a/src/components/searchbar/searchbar.ios.scss b/src/components/searchbar/searchbar.ios.scss index 521b35f945e..df79663bdc8 100644 --- a/src/components/searchbar/searchbar.ios.scss +++ b/src/components/searchbar/searchbar.ios.scss @@ -63,11 +63,10 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; .searchbar-ios { @include padding($searchbar-ios-padding-vertical, $searchbar-ios-padding-horizontal); + @include border($hairlines-width solid transparent, null, $hairlines-width solid $searchbar-ios-border-color, null); min-height: $searchbar-ios-min-height; - border-top: $hairlines-width solid transparent; - border-bottom: $hairlines-width solid $searchbar-ios-border-color; background: $searchbar-ios-background-color; } @@ -175,7 +174,8 @@ $searchbar-ios-toolbar-input-background: rgba(0, 0, 0, .08) !default; // ----------------------------------------- .toolbar .searchbar-ios { - border-bottom-width: 0; + @include border-width(null, null, 0, null); + background: transparent; } diff --git a/src/components/searchbar/searchbar.scss b/src/components/searchbar/searchbar.scss index 9e37d8eb21a..2f0c0932987 100644 --- a/src/components/searchbar/searchbar.scss +++ b/src/components/searchbar/searchbar.scss @@ -28,11 +28,12 @@ ion-searchbar { .searchbar-input { @include appearance(none); + @include border(0); + display: block; width: 100%; - border: 0; font-family: inherit; } diff --git a/src/components/searchbar/searchbar.wp.scss b/src/components/searchbar/searchbar.wp.scss index ce25b934909..d8f25205c32 100644 --- a/src/components/searchbar/searchbar.wp.scss +++ b/src/components/searchbar/searchbar.wp.scss @@ -91,7 +91,7 @@ $searchbar-wp-input-clear-icon-size: 22px !default; } .searchbar-wp .searchbar-input-container { - border: $searchbar-wp-border-width solid $searchbar-wp-border-color; + @include border($searchbar-wp-border-width solid $searchbar-wp-border-color); } // Searchbar Search Icon diff --git a/src/components/segment/segment.ios.scss b/src/components/segment/segment.ios.scss index 5cd7359ad75..29b6fb95e1a 100644 --- a/src/components/segment/segment.ios.scss +++ b/src/components/segment/segment.ios.scss @@ -74,13 +74,14 @@ $segment-button-ios-toolbar-icon-line-height: 2.4rem !default; .segment-ios .segment-button { + @include border-width($segment-button-ios-border-width); + flex: 1; width: 0; height: $segment-button-ios-height; - border-width: $segment-button-ios-border-width; border-style: solid; border-color: $segment-button-ios-background-color-activated; @@ -118,28 +119,16 @@ $segment-button-ios-toolbar-icon-line-height: 2.4rem !default; } &:not(:first-of-type) { - border-left-width: 0; + @include border-width(null, null, null, 0); } &:last-of-type { @include border-radius(0, $segment-button-ios-border-radius, $segment-button-ios-border-radius, 0); @include margin-horizontal(0, null); - - border-left-width: 0; - } -} - -[dir="rtl"] .segment-ios .segment-button { - &:first-of-type { - border-left-width: 0; - } - - &:last-of-type { - border-left-width: $segment-button-ios-border-width; + @include border-width(null, null, null, 0); } } - .segment-ios.segment-disabled { opacity: .4; diff --git a/src/components/segment/segment.md.scss b/src/components/segment/segment.md.scss index 4db2e65d6c3..a9c7fffced0 100644 --- a/src/components/segment/segment.md.scss +++ b/src/components/segment/segment.md.scss @@ -56,12 +56,13 @@ $segment-button-md-icon-line-height: $segment-button-md-line-height !d .segment-md .segment-button { + @include border-width(null, null, $segment-button-md-border-bottom-width, null); + flex: 1; width: 0; height: $segment-button-md-height; - border-bottom-width: $segment-button-md-border-bottom-width; border-bottom-style: solid; border-bottom-color: $segment-button-md-border-bottom-color; diff --git a/src/components/select/select.ios.scss b/src/components/select/select.ios.scss index 7c4505f6269..e46e7668254 100644 --- a/src/components/select/select.ios.scss +++ b/src/components/select/select.ios.scss @@ -44,15 +44,13 @@ $select-ios-placeholder-color: $select-ios-icon-color !default; .select-ios .select-icon .select-icon-inner { @include position(50%, null, null, 5px); @include margin(-2px, null, null, null); + @include border(5px solid, 5px solid transparent, null, 5px solid transparent); position: absolute; width: 0; height: 0; - border-top: 5px solid; - border-right: 5px solid transparent; - border-left: 5px solid transparent; color: $select-ios-icon-color; pointer-events: none; diff --git a/src/components/select/select.md.scss b/src/components/select/select.md.scss index d54ba00ab24..900ef779b93 100644 --- a/src/components/select/select.md.scss +++ b/src/components/select/select.md.scss @@ -48,15 +48,13 @@ $select-md-placeholder-color: $select-md-icon-color !default; .select-md .select-icon .select-icon-inner { @include position(50%, null, null, 5px); @include margin(-3px, null, null, null); + @include border(5px solid, 5px solid transparent, null, 5px solid transparent); position: absolute; width: 0; height: 0; - border-top: 5px solid; - border-right: 5px solid transparent; - border-left: 5px solid transparent; color: $select-md-icon-color; pointer-events: none; diff --git a/src/components/select/select.wp.scss b/src/components/select/select.wp.scss index 7224561ceb5..07cff6f0124 100644 --- a/src/components/select/select.wp.scss +++ b/src/components/select/select.wp.scss @@ -47,12 +47,12 @@ $select-wp-placeholder-color: $select-wp-icon-color !default; .select-wp { @include margin($select-wp-margin-top, $select-wp-margin-end, $select-wp-margin-bottom, $select-wp-margin-start); @include padding($select-wp-padding-vertical, $select-wp-padding-horizontal); + @include border($select-wp-border-width solid $select-wp-border-color); flex: 1; max-width: 100%; - border: $select-wp-border-width solid $select-wp-border-color; line-height: 3rem; } @@ -75,6 +75,8 @@ $select-wp-placeholder-color: $select-wp-icon-color !default; .select-wp .select-icon .select-icon-inner { @include position(3px, null, null, 5px); + @include border($select-wp-icon-arrow-width solid $select-wp-icon-color, + $select-wp-icon-arrow-width solid $select-wp-icon-color, null, null); position: absolute; @@ -83,9 +85,6 @@ $select-wp-placeholder-color: $select-wp-icon-color !default; width: ($select-wp-icon-width / 2); height: ($select-wp-icon-width / 2); - border-top: $select-wp-icon-arrow-width solid $select-wp-icon-color; - border-right: $select-wp-icon-arrow-width solid $select-wp-icon-color; - transform: rotate(135deg); pointer-events: none; diff --git a/src/components/slides/slides.scss b/src/components/slides/slides.scss index ca1aecab364..99df0e66edb 100644 --- a/src/components/slides/slides.scss +++ b/src/components/slides/slides.scss @@ -225,8 +225,8 @@ button.swiper-pagination-bullet { @include margin(0); @include padding(0); @include appearance(none); + @include border(0); - border: 0; box-shadow: none; } diff --git a/src/components/split-pane/split-pane.ios.scss b/src/components/split-pane/split-pane.ios.scss index eeb105a8206..cc250fa9aa6 100644 --- a/src/components/split-pane/split-pane.ios.scss +++ b/src/components/split-pane/split-pane.ios.scss @@ -14,14 +14,12 @@ $split-pane-ios-side-max-width: $split-pane-side-max-width !default; $split-pane-ios-border: $hairlines-width solid $list-ios-border-color !default; .split-pane-ios.split-pane-visible >.split-pane-side { + @include border(null, $split-pane-ios-border, null, 0); + min-width: $split-pane-ios-side-min-width; max-width: $split-pane-ios-side-max-width; - - border-right: $split-pane-ios-border; - border-left: 0; } .split-pane-ios.split-pane-visible > .split-pane-side[side=right] { - border-right: 0; - border-left: $split-pane-ios-border; + @include border(null, 0, null, $split-pane-ios-border); } diff --git a/src/components/split-pane/split-pane.md.scss b/src/components/split-pane/split-pane.md.scss index c5ba59166a0..7e0e92ed80e 100644 --- a/src/components/split-pane/split-pane.md.scss +++ b/src/components/split-pane/split-pane.md.scss @@ -14,14 +14,12 @@ $split-pane-md-side-max-width: $split-pane-side-max-width !default; $split-pane-md-border: 1px solid $list-md-border-color !default; .split-pane-md.split-pane-visible >.split-pane-side { + @include border(null, $split-pane-md-border, null, 0); + min-width: $split-pane-md-side-min-width; max-width: $split-pane-md-side-max-width; - - border-right: $split-pane-md-border; - border-left: 0; } .split-pane-md.split-pane-visible > .split-pane-side[side=right] { - border-right: 0; - border-left: $split-pane-md-border; + @include border(null, 0, null, $split-pane-md-border); } diff --git a/src/components/split-pane/split-pane.wp.scss b/src/components/split-pane/split-pane.wp.scss index 1a6c5bdd756..f05cd21cf67 100644 --- a/src/components/split-pane/split-pane.wp.scss +++ b/src/components/split-pane/split-pane.wp.scss @@ -14,14 +14,12 @@ $split-pane-wp-side-max-width: $split-pane-side-max-width !default; $split-pane-wp-border: 1px solid $list-wp-border-color !default; .split-pane-wp.split-pane-visible >.split-pane-side { + @include border(null, $split-pane-wp-border, null, 0); + min-width: $split-pane-wp-side-min-width; max-width: $split-pane-wp-side-max-width; - - border-right: $split-pane-wp-border; - border-left: 0; } .split-pane-wp.split-pane-visible > .split-pane-side[side=right] { - border-right: 0; - border-left: $split-pane-wp-border; + @include border(null, 0, null, $split-pane-wp-border); } diff --git a/src/components/tabs/tabs.ios.scss b/src/components/tabs/tabs.ios.scss index 4307620a982..47e9e278024 100644 --- a/src/components/tabs/tabs.ios.scss +++ b/src/components/tabs/tabs.ios.scss @@ -47,15 +47,15 @@ $tabs-ios-tab-icon-size: 30px !default; .tabs-ios .tabbar { + @include border($tabs-ios-border, null, null, null); + justify-content: center; - border-top: $tabs-ios-border; background: $tabs-ios-background; } .tabs-ios[tabsPlacement=top] .tabbar { - border-top: 0; - border-bottom: $tabs-ios-border; + @include border(0, null, $tabs-ios-border, null); } .tabs-ios .tab-button { diff --git a/src/components/tabs/tabs.scss b/src/components/tabs/tabs.scss index c07cb681daf..b7078d9a3db 100644 --- a/src/components/tabs/tabs.scss +++ b/src/components/tabs/tabs.scss @@ -34,6 +34,7 @@ @include margin(0); @include text-align(center); @include border-radius(0); + @include border(0); position: relative; z-index: 0; @@ -46,8 +47,6 @@ align-self: center; justify-content: center; - border: 0; - text-decoration: none; background: none; cursor: pointer; diff --git a/src/components/tabs/tabs.wp.scss b/src/components/tabs/tabs.wp.scss index e12f6c3923a..49942a52e72 100644 --- a/src/components/tabs/tabs.wp.scss +++ b/src/components/tabs/tabs.wp.scss @@ -61,10 +61,10 @@ $tabs-wp-tab-icon-size: 2.4rem !default; .tabs-wp .tab-button { @include border-radius(0); + @include border(null, null, $tabs-wp-tab-border, null); min-height: $tabs-wp-tab-min-height; - border-bottom: $tabs-wp-tab-border; font-size: $tabs-wp-tab-font-size; font-weight: $tabs-wp-tab-font-weight; color: $tabs-wp-tab-color; @@ -85,8 +85,7 @@ $tabs-wp-tab-icon-size: 2.4rem !default; } .tabs-wp[tabsPlacement=bottom] .tab-button { - border-top: $tabs-wp-tab-border; - border-bottom-width: 0; + @include border($tabs-wp-tab-border, null, 0, null); } .tabs-wp[tabsPlacement=bottom] .tab-button[aria-selected=true] { diff --git a/src/components/toggle/toggle.wp.scss b/src/components/toggle/toggle.wp.scss index c44565cbe35..a47f98500a2 100644 --- a/src/components/toggle/toggle.wp.scss +++ b/src/components/toggle/toggle.wp.scss @@ -110,6 +110,7 @@ $toggle-wp-item-end-padding-start: $item-wp-padding-start !default; .toggle-wp .toggle-icon { @include border-radius($toggle-wp-track-height); + @include border($toggle-wp-track-border-width solid $toggle-wp-track-border-color-off); position: relative; display: block; @@ -117,7 +118,6 @@ $toggle-wp-item-end-padding-start: $item-wp-padding-start !default; width: 100%; height: 100%; - border: $toggle-wp-track-border-width solid $toggle-wp-track-border-color-off; background-color: $toggle-wp-track-background-color-off; pointer-events: none; diff --git a/src/components/toolbar/toolbar.ios.scss b/src/components/toolbar/toolbar.ios.scss index 488e4ff220f..2f94dc59fc4 100644 --- a/src/components/toolbar/toolbar.ios.scss +++ b/src/components/toolbar/toolbar.ios.scss @@ -70,19 +70,20 @@ $toolbar-button-ios-strong-font-weight: 600 !default; } .header-ios .toolbar-ios:last-child .toolbar-background-ios { - border-width: 0 0 $hairlines-width; + @include border-width(0, 0, $hairlines-width); + } .footer-ios .toolbar-ios:first-child .toolbar-background-ios { - border-width: $hairlines-width 0 0; + @include border-width($hairlines-width, 0, 0); } .header-ios[no-border] .toolbar-ios:last-child .toolbar-background-ios { - border-bottom-width: 0; + @include border-width(null, null, 0, null); } .footer-ios[no-border] .toolbar-ios:first-child .toolbar-background-ios { - border-top-width: 0; + @include border-width(0, null, null, null); } @@ -174,10 +175,10 @@ $toolbar-button-ios-strong-font-weight: 600 !default; .bar-button-ios { @include padding(0, 4px); @include border-radius($toolbar-ios-button-border-radius); + @include border(0); height: 32px; - border: 0; font-size: $toolbar-ios-button-font-size; } @@ -205,7 +206,8 @@ $toolbar-button-ios-strong-font-weight: 600 !default; // -------------------------------------------------- .bar-button-outline-ios { - border-width: 1px; + @include border-width(1px); + border-style: solid; border-color: $toolbar-ios-button-color; color: $toolbar-ios-button-color; diff --git a/src/components/toolbar/toolbar.md.scss b/src/components/toolbar/toolbar.md.scss index dc2c9c447f4..83d499f284f 100644 --- a/src/components/toolbar/toolbar.md.scss +++ b/src/components/toolbar/toolbar.md.scss @@ -185,10 +185,10 @@ $toolbar-button-md-strong-font-weight: bold !default; @include margin(0, .2rem, 0, .2rem); @include padding(0, 5px); @include border-radius($toolbar-md-button-border-radius); + @include border(0); height: 32px; - border: 0; font-size: $toolbar-md-button-font-size; font-weight: 500; text-transform: uppercase; @@ -220,7 +220,8 @@ $toolbar-button-md-strong-font-weight: bold !default; // -------------------------------------------------- .bar-button-outline-md { - border-width: 1px; + @include border-width(1px); + border-style: solid; border-color: $toolbar-md-button-color; color: $toolbar-md-button-color; diff --git a/src/components/toolbar/toolbar.scss b/src/components/toolbar/toolbar.scss index 896e0fb51ca..dde45596a9a 100644 --- a/src/components/toolbar/toolbar.scss +++ b/src/components/toolbar/toolbar.scss @@ -23,6 +23,7 @@ ion-toolbar { .toolbar-background { @include position(0, null, null, 0); + @include border(0); position: absolute; z-index: $z-index-toolbar-background; @@ -30,7 +31,6 @@ ion-toolbar { width: 100%; height: 100%; - border: 0; transform: translateZ(0); pointer-events: none; diff --git a/src/components/toolbar/toolbar.wp.scss b/src/components/toolbar/toolbar.wp.scss index 07288b77eed..eec9529b99f 100644 --- a/src/components/toolbar/toolbar.wp.scss +++ b/src/components/toolbar/toolbar.wp.scss @@ -162,10 +162,10 @@ $toolbar-button-wp-strong-font-weight: bold !default; @include margin(0, .2rem); @include padding(0, 5px); @include border-radius($toolbar-wp-button-border-radius); + @include border(0); height: 32px; - border: 0; font-size: $toolbar-wp-button-font-size; font-weight: 500; text-transform: uppercase; @@ -197,7 +197,8 @@ $toolbar-button-wp-strong-font-weight: bold !default; // -------------------------------------------------- .bar-button-outline-wp { - border-width: 1px; + @include border-width(1px); + border-style: solid; border-color: $toolbar-wp-button-color; color: $toolbar-wp-button-color; diff --git a/src/themes/ionic.mixins.scss b/src/themes/ionic.mixins.scss index 9dedab2280e..94ccabec2da 100644 --- a/src/themes/ionic.mixins.scss +++ b/src/themes/ionic.mixins.scss @@ -464,6 +464,42 @@ } } +@mixin border($top, $end: $top, $bottom: $top, $start: $end) { + @include property(border, $top, $end, $bottom, $start); +} + +@mixin border-width($top, $end: $top, $bottom: $top, $start: $end) { + @include property(border, $top, $end, $bottom, $start); + + @if $top == $end and $top == $bottom and $top == $start { + @include multi-dir() { + border-width: $top; + } + } @else if $end == $start { + @include multi-dir() { + border-top-width: $top; + border-right-width: $end; + border-bottom-width: $bottom; + border-left-width: $start; + } + } @else { + @include ltr() { + border-top-width: $top; + border-right-width: $end; + border-bottom-width: $bottom; + border-left-width: $start; + } + + @include rtl() { + border-top-width: $top; + border-right-width: $start; + border-bottom-width: $bottom; + border-left-width: $end; + } + } +} + + @mixin transform-origin($x-axis, $y-axis: null) { @if $x-axis == start { @include ltr() { From 34ddbedf4881edb352afa23879cf8a88f122f653 Mon Sep 17 00:00:00 2001 From: Amit Moryossef Date: Tue, 13 Jun 2017 14:38:59 +0300 Subject: [PATCH 07/13] fix(border): border-width mixin remove rogue call --- src/themes/ionic.mixins.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/themes/ionic.mixins.scss b/src/themes/ionic.mixins.scss index 94ccabec2da..cb922fb9d3f 100644 --- a/src/themes/ionic.mixins.scss +++ b/src/themes/ionic.mixins.scss @@ -469,8 +469,6 @@ } @mixin border-width($top, $end: $top, $bottom: $top, $start: $end) { - @include property(border, $top, $end, $bottom, $start); - @if $top == $end and $top == $bottom and $top == $start { @include multi-dir() { border-width: $top; From 9f86e10f46d87441de170e114bc876cf8157d57b Mon Sep 17 00:00:00 2001 From: Manuel Mtz-Almeida Date: Tue, 13 Jun 2017 14:18:03 +0200 Subject: [PATCH 08/13] fix(input): better handling of attributes --- src/components/input/input.ts | 44 ++++---------- .../test/attributes/app/app.component.ts | 10 ++++ .../input/test/attributes/app/app.module.ts | 19 ++++++ .../input/test/attributes/app/main.ts | 5 ++ src/components/input/test/attributes/e2e.ts | 0 .../attributes/pages/root-page/root-page.html | 39 ++++++++++++ .../pages/root-page/root-page.module.ts | 14 +++++ .../attributes/pages/root-page/root-page.ts | 59 +++++++++++++++++++ src/components/item/item.ts | 3 +- src/util/base-input.ts | 3 +- src/util/dom.ts | 6 +- 11 files changed, 164 insertions(+), 38 deletions(-) create mode 100644 src/components/input/test/attributes/app/app.component.ts create mode 100644 src/components/input/test/attributes/app/app.module.ts create mode 100644 src/components/input/test/attributes/app/main.ts create mode 100644 src/components/input/test/attributes/e2e.ts create mode 100644 src/components/input/test/attributes/pages/root-page/root-page.html create mode 100644 src/components/input/test/attributes/pages/root-page/root-page.module.ts create mode 100644 src/components/input/test/attributes/pages/root-page/root-page.ts diff --git a/src/components/input/input.ts b/src/components/input/input.ts index ca6f3e92143..36bad87447c 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -10,7 +10,7 @@ import 'rxjs/add/operator/takeUntil'; import { App } from '../app/app'; import { Config } from '../../config/config'; import { Content, ContentDimensions } from '../content/content'; -import { hasPointerMoved, pointerCoord } from '../../util/dom'; +import { copyInputAttributes, hasPointerMoved, pointerCoord } from '../../util/dom'; import { DomController } from '../../platform/dom-controller'; import { Form, IonicFormInput } from '../../util/form'; import { BaseInput } from '../../util/base-input'; @@ -93,15 +93,12 @@ import { Platform } from '../../platform/platform'; '(focus)="onFocus($event)" ' + '(keydown)="onKeydown($event)" ' + '[type]="_type" ' + - '[attr.name]="name" ' + + '[attr.aria-labelledby]="_labelId" ' + '[attr.min]="min" ' + '[attr.max]="max" ' + '[attr.step]="step" ' + - '[attr.maxlength]="maxlength" ' + '[attr.autocomplete]="autocomplete" ' + '[attr.autocorrect]="autocorrect" ' + - '[attr.spellcheck]="spellcheck" ' + - '[attr.autocapitalize]="autocapitalize" ' + '[placeholder]="placeholder" ' + '[disabled]="_disabled" ' + '[readonly]="_readonly">' + @@ -111,12 +108,9 @@ import { Platform } from '../../platform/platform'; '(blur)="onBlur($event)" ' + '(focus)="onFocus($event)" ' + '(keydown)="onKeydown($event)" ' + - '[attr.name]="name" ' + - '[attr.maxlength]="maxlength" ' + + '[attr.aria-labelledby]="_labelId" ' + '[attr.autocomplete]="autocomplete" ' + '[attr.autocorrect]="autocorrect" ' + - '[attr.spellcheck]="spellcheck" ' + - '[attr.autocapitalize]="autocapitalize" ' + '[placeholder]="placeholder" ' + '[disabled]="_disabled" ' + '[readonly]="_readonly">' + @@ -134,7 +128,8 @@ import { Platform } from '../../platform/platform'; '(mouseup)="_pointerEnd($event)">', encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + inputs: ['value'] }) export class TextInput extends BaseInput implements IonicFormInput { @@ -216,28 +211,11 @@ export class TextInput extends BaseInput implements IonicFormInput { */ @Input() autocorrect: string = ''; - /** - * @input {string} Specifies whether the element is to have its spelling - * and grammar checked or not. - */ - @Input() spellcheck: string = null; - - /** - * @input {string} controls whether and how the text value for textual form control descendants should be automatically capitalized as it is entered/edited by the user. - */ - @Input() autocapitalize: string = null; - /** * @input {string} Instructional text that shows before the input has a value. */ @Input() placeholder: string = ''; - /** - * @input {string} The name attribute is used to reference elements in a JavaScript, - * or to reference form data after a form is submitted. - */ - @Input() name: string = null; - /** * @input {any} The minimum value, which must not be greater than its maximum (max attribute) value. */ @@ -253,11 +231,6 @@ export class TextInput extends BaseInput implements IonicFormInput { */ @Input() step: number | string = null; - /** - * @input {any} Specifies the maximum number of characters allowed in the element. - */ - @Input() maxlength: number | string = null; - /** * @hidden */ @@ -331,10 +304,13 @@ export class TextInput extends BaseInput implements IonicFormInput { this.clearOnEdit = true; } const ionInputEle: HTMLElement = this._elementRef.nativeElement; + const nativeInputEle: HTMLElement = this._native.nativeElement; + + // Copy remaining attributes, not handled by ionic/angular + copyInputAttributes(ionInputEle, nativeInputEle); if (ionInputEle.hasAttribute('autofocus')) { // the ion-input element has the autofocus attributes - const nativeInputEle: HTMLElement = this._native.nativeElement; ionInputEle.removeAttribute('autofocus'); switch (this._autoFocusAssist) { case 'immediate': @@ -351,6 +327,8 @@ export class TextInput extends BaseInput implements IonicFormInput { // traditionally iOS has big issues with autofocus on actual devices // autoFocus is disabled by default with the iOS mode config } + + // Initialize the input (can start emitting events) this._initialize(); if (this.focus.observers.length > 0) { diff --git a/src/components/input/test/attributes/app/app.component.ts b/src/components/input/test/attributes/app/app.component.ts new file mode 100644 index 00000000000..dcf2f9e7e73 --- /dev/null +++ b/src/components/input/test/attributes/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +import { RootPage } from '../pages/root-page/root-page'; + +@Component({ + template: '' +}) +export class AppComponent { + root = RootPage; +} diff --git a/src/components/input/test/attributes/app/app.module.ts b/src/components/input/test/attributes/app/app.module.ts new file mode 100644 index 00000000000..7f76975a263 --- /dev/null +++ b/src/components/input/test/attributes/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { IonicApp, IonicModule } from '../../../../..'; + +import { AppComponent } from './app.component'; +import { RootPageModule } from '../pages/root-page/root-page.module'; + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + IonicModule.forRoot(AppComponent), + RootPageModule + ], + bootstrap: [IonicApp] +}) +export class AppModule {} diff --git a/src/components/input/test/attributes/app/main.ts b/src/components/input/test/attributes/app/main.ts new file mode 100644 index 00000000000..6af7a5b2ae8 --- /dev/null +++ b/src/components/input/test/attributes/app/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/src/components/input/test/attributes/e2e.ts b/src/components/input/test/attributes/e2e.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/components/input/test/attributes/pages/root-page/root-page.html b/src/components/input/test/attributes/pages/root-page/root-page.html new file mode 100644 index 00000000000..c7e03960992 --- /dev/null +++ b/src/components/input/test/attributes/pages/root-page/root-page.html @@ -0,0 +1,39 @@ + + + + Input attributes + + + + + + + + Stacked + + + + + Test passed + Test FAILED + + + + + diff --git a/src/components/input/test/attributes/pages/root-page/root-page.module.ts b/src/components/input/test/attributes/pages/root-page/root-page.module.ts new file mode 100644 index 00000000000..85fba79181b --- /dev/null +++ b/src/components/input/test/attributes/pages/root-page/root-page.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from '../../../../../..'; + +import { RootPage } from './root-page'; + +@NgModule({ + declarations: [ + RootPage, + ], + imports: [ + IonicPageModule.forChild(RootPage) + ] +}) +export class RootPageModule {} diff --git a/src/components/input/test/attributes/pages/root-page/root-page.ts b/src/components/input/test/attributes/pages/root-page/root-page.ts new file mode 100644 index 00000000000..d33987eb606 --- /dev/null +++ b/src/components/input/test/attributes/pages/root-page/root-page.ts @@ -0,0 +1,59 @@ +import { Component, ViewChild } from '@angular/core'; +import { TextInput } from '../../../../../../'; + +@Component({ + templateUrl: 'root-page.html' +}) +export class RootPage { + + input1Valid: boolean; + input2Valid: boolean; + + @ViewChild('input1') input1: TextInput; + + ionViewDidEnter() { + this.input1Valid = this.checkInput1(); + } + + checkInput1(): boolean { + const nativeEle = this.input1._native.nativeElement; + + return testAttributes(nativeEle, { + id: null, + type: 'number', + placeholder: 'Placeholder', + name: 'holaa', + min: '0', + max: '10000', + step: '2', + autocomplete: 'on', + autocorrect: 'on', + autocapitalize: 'on', + spellcheck: 'true', + maxLength: '4', + 'aria-labelledby': 'lbl-0', + readOnly: true, + disabled: true + }); + } +} + +function testAttributes(ele: HTMLElement, attributes: any): boolean { + for (let attr in attributes) { + const expected = attributes[attr]; + const value = (ele)[attr]; + + if (expected === null) { + if (ele.hasAttribute(attr) || value !== '') { + console.error(`Element should NOT have "${attr}"`); + return false; + } + } else { + if (expected !== value && expected !== ele.getAttribute(attr)) { + console.error(`Value "${attr}" does not match: ${expected} != ${value}`); + return false; + } + } + } + return true; +} diff --git a/src/components/item/item.ts b/src/components/item/item.ts index 8f5bb710635..87152f022e9 100644 --- a/src/components/item/item.ts +++ b/src/components/item/item.ts @@ -323,6 +323,7 @@ export class Item extends Ion { this._setName(elementRef); this._hasReorder = !!reorder; this.id = form.nextId().toString(); + this.labelId = 'lbl-' + this.id; // auto add "tappable" attribute to ion-item components that have a click listener if (!(renderer).orgListen) { @@ -391,7 +392,7 @@ export class Item extends Ion { set contentLabel(label: Label) { if (label) { this._label = label; - this.labelId = label.id = ('lbl-' + this.id); + label.id = this.labelId; if (label.type) { this.setElementClass('item-label-' + label.type, true); } diff --git a/src/util/base-input.ts b/src/util/base-input.ts index e648e52ee34..1094915a792 100644 --- a/src/util/base-input.ts +++ b/src/util/base-input.ts @@ -84,8 +84,9 @@ export class BaseInput extends Ion implements CommonInput { this._value = deepCopy(this._defaultValue); if (_item) { + assert('lbl-' + _item.id === _item.labelId, 'labelId was not calculated correctly'); this.id = name + '-' + _item.registerInput(name); - this._labelId = 'lbl-' + _item.id; + this._labelId = _item.labelId; this._item.setElementClass('item-' + name, true); } diff --git a/src/util/dom.ts b/src/util/dom.ts index 37412e37cae..62d742a905f 100644 --- a/src/util/dom.ts +++ b/src/util/dom.ts @@ -94,15 +94,15 @@ export function isTextInput(ele: any) { export const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i; -const skipInputAttrsReg = /^(value|checked|disabled|type|class|style|id|autofocus|autocomplete|autocorrect)$/i; +const SKIP_INPUT_ATTR = ['value', 'checked', 'disabled', 'readonly', 'placeholder', 'type', 'class', 'style', 'id', 'autofocus', 'autocomplete', 'autocorrect']; export function copyInputAttributes(srcElement: HTMLElement, destElement: HTMLElement) { // copy attributes from one element to another // however, skip over a few of them as they're already // handled in the angular world - var attrs = srcElement.attributes; + const attrs = srcElement.attributes; for (var i = 0; i < attrs.length; i++) { var attr = attrs[i]; - if (!skipInputAttrsReg.test(attr.name)) { + if (SKIP_INPUT_ATTR.indexOf(attr.name) === -1) { destElement.setAttribute(attr.name, attr.value); } } From dc958c3e2cc3726339591a91cbe64e90c82d0238 Mon Sep 17 00:00:00 2001 From: Manuel Mtz-Almeida Date: Tue, 13 Jun 2017 17:26:04 +0200 Subject: [PATCH 09/13] fix(textarea): apply classes properly --- src/components/input/input.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/input/input.ts b/src/components/input/input.ts index 36bad87447c..39c822bf5e8 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -104,6 +104,7 @@ import { Platform } from '../../platform/platform'; '[readonly]="_readonly">' + '