From ced9c90beebd00f60af01087d6078effef0ce359 Mon Sep 17 00:00:00 2001 From: mmalerba Date: Tue, 13 Mar 2018 09:00:11 -0700 Subject: [PATCH] feat(cdk-input): move input autofill and autosize utils into cdk (#9831) * move autofill to cdk * move textarea autosize into cdk * fix bazel build * rename input to text-field * set resize back to none for autosize in input css * fix tsconfig --- .github/CODEOWNERS | 1 + src/cdk/text-field/BUILD.bazel | 34 +++ src/cdk/text-field/_text-field.scss | 50 +++++ .../input => cdk/text-field}/autofill.spec.ts | 46 ++-- src/{lib/input => cdk/text-field}/autofill.ts | 22 +- .../input => cdk/text-field}/autosize.spec.ts | 113 ++-------- src/cdk/text-field/autosize.ts | 211 ++++++++++++++++++ src/cdk/text-field/index.ts | 9 + src/cdk/text-field/public-api.ts | 11 + src/cdk/text-field/text-field-module.ts | 21 ++ src/cdk/text-field/text-field-prebuilt.scss | 3 + src/cdk/text-field/tsconfig-build.json | 14 ++ src/demo-app/a11y/input/input-a11y.html | 2 +- src/demo-app/baseline/baseline-demo.html | 4 +- src/demo-app/demo-material-module.ts | 2 + src/demo-app/input/input-demo.html | 10 +- src/demo-app/input/input-demo.scss | 4 +- src/demo-app/stepper/stepper-demo.html | 2 +- src/demo-app/system-config.ts | 1 + src/demo-app/tabs/tabs-demo.html | 2 +- src/e2e-app/input/input-e2e.html | 2 +- src/e2e-app/system-config.ts | 1 + src/e2e-app/tabs/tabs-e2e.html | 2 +- src/lib/core/_core.scss | 4 +- src/lib/input/BUILD.bazel | 1 + src/lib/input/_autofill.scss | 43 ---- src/lib/input/autofill-prebuilt.scss | 3 - src/lib/input/autosize.ts | 201 ++--------------- src/lib/input/input-module.ts | 13 +- src/lib/input/input.md | 4 +- src/lib/input/input.scss | 8 +- src/lib/input/input.spec.ts | 111 ++++++++- src/lib/input/input.ts | 2 +- src/lib/input/public-api.ts | 2 - .../input-autosize-textarea-example.html | 4 +- .../kitchen-sink/kitchen-sink.html | 2 +- test/karma-test-shim.js | 1 + 37 files changed, 563 insertions(+), 403 deletions(-) create mode 100644 src/cdk/text-field/BUILD.bazel create mode 100644 src/cdk/text-field/_text-field.scss rename src/{lib/input => cdk/text-field}/autofill.spec.ts (81%) rename src/{lib/input => cdk/text-field}/autofill.ts (83%) rename src/{lib/input => cdk/text-field}/autosize.spec.ts (70%) create mode 100644 src/cdk/text-field/autosize.ts create mode 100644 src/cdk/text-field/index.ts create mode 100644 src/cdk/text-field/public-api.ts create mode 100644 src/cdk/text-field/text-field-module.ts create mode 100644 src/cdk/text-field/text-field-prebuilt.scss create mode 100644 src/cdk/text-field/tsconfig-build.json delete mode 100644 src/lib/input/_autofill.scss delete mode 100644 src/lib/input/autofill-prebuilt.scss diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2c31af3d6c08..67c5b5a2557d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -72,6 +72,7 @@ /src/cdk/stepper/** @mmalerba /src/cdk/table/** @andrewseguin /src/cdk/testing/** @devversion +/src/cdk/text-field/** @mmalerba /src/cdk/tree/** @tinayuangao # Moment adapter package diff --git a/src/cdk/text-field/BUILD.bazel b/src/cdk/text-field/BUILD.bazel new file mode 100644 index 000000000000..c9f9d13f79c7 --- /dev/null +++ b/src/cdk/text-field/BUILD.bazel @@ -0,0 +1,34 @@ +package(default_visibility=["//visibility:public"]) +load("@angular//:index.bzl", "ng_module") +load("@io_bazel_rules_sass//sass:sass.bzl", "sass_library", "sass_binary") + +ng_module( + name = "text-field", + srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]), + module_name = "@angular/cdk/text-field", + deps = [ + "@rxjs", + "//src/cdk/platform", + ], + tsconfig = ":tsconfig-build.json", +) + +sass_library( + name = "text_field_scss_lib", + srcs = glob(["**/_*.scss"]), +) + +sass_binary( + name = "text_field_prebuilt_scss", + src = "text-field-prebuilt.scss", + deps = [":text_field_scss_lib"], +) + +# TODO(jelbourn): remove this when sass_binary supports specifying an output filename and dir. +# Copy the output of the sass_binary such that the filename and path match what we expect. +genrule( + name = "text_field_prebuilt_css", + srcs = [":text_field_prebuilt_scss"], + outs = ["text-field-prebuilt.css"], + cmd = "cat $(locations :text_field_prebuilt_scss) > $@", +) diff --git a/src/cdk/text-field/_text-field.scss b/src/cdk/text-field/_text-field.scss new file mode 100644 index 000000000000..f7d1bd8a04e0 --- /dev/null +++ b/src/cdk/text-field/_text-field.scss @@ -0,0 +1,50 @@ +// Core styles that enable monitoring autofill state of text fields. +@mixin cdk-text-field { + // Keyframes that apply no styles, but allow us to monitor when an text field becomes autofilled + // by watching for the animation events that are fired when they start. + // Based on: https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7 + @keyframes cdk-text-field-autofill-start {} + @keyframes cdk-text-field-autofill-end {} + + .cdk-text-field-autofill-monitored:-webkit-autofill { + animation-name: cdk-text-field-autofill-start; + } + + .cdk-text-field-autofill-monitored:not(:-webkit-autofill) { + animation-name: cdk-text-field-autofill-end; + } + + // Remove the resize handle on autosizing textareas, because whatever height + // the user resized to will be overwritten once they start typing again. + textarea.cdk-textarea-autosize { + resize: none; + } +} + +// Used to generate UIDs for keyframes used to change the text field autofill styles. +$cdk-text-field-autofill-color-frame-count: 0; + +// Mixin used to apply custom background and foreground colors to an autofilled text field. +// Based on: https://stackoverflow.com/questions/2781549/ +// removing-input-background-colour-for-chrome-autocomplete#answer-37432260 +@mixin cdk-text-field-autofill-color($background, $foreground:'') { + @keyframes cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count} { + to { + background: $background; + @if $foreground != '' { color: $foreground; } + } + } + + &:-webkit-autofill { + animation-name: cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count}; + animation-fill-mode: both; + } + + &.cdk-text-field-autofill-monitored:-webkit-autofill { + animation-name: cdk-text-field-autofill-start, + cdk-text-field-autofill-color-#{$cdk-text-field-autofill-color-frame-count}; + } + + $cdk-text-field-autofill-color-frame-count: + $cdk-text-field-autofill-color-frame-count + 1 !global; +} diff --git a/src/lib/input/autofill.spec.ts b/src/cdk/text-field/autofill.spec.ts similarity index 81% rename from src/lib/input/autofill.spec.ts rename to src/cdk/text-field/autofill.spec.ts index 8a5dbea6069a..d95dfd461702 100644 --- a/src/lib/input/autofill.spec.ts +++ b/src/cdk/text-field/autofill.spec.ts @@ -11,7 +11,7 @@ import {Component, ElementRef, ViewChild} from '@angular/core'; import {ComponentFixture, inject, TestBed} from '@angular/core/testing'; import {empty as observableEmpty} from 'rxjs/observable/empty'; import {AutofillEvent, AutofillMonitor} from './autofill'; -import {MatInputModule} from './input-module'; +import {TextFieldModule} from './text-field-module'; const listenerOptions: any = supportsPassiveEventListeners() ? {passive: true} : false; @@ -24,7 +24,7 @@ describe('AutofillMonitor', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatInputModule], + imports: [TextFieldModule], declarations: [Inputs], }).compileComponents(); }); @@ -52,7 +52,7 @@ describe('AutofillMonitor', () => { expect(inputEl.addEventListener).not.toHaveBeenCalled(); autofillMonitor.monitor(inputEl); - expect(inputEl.classList).toContain('mat-input-autofill-monitored'); + expect(inputEl.classList).toContain('cdk-text-field-autofill-monitored'); expect(inputEl.addEventListener) .toHaveBeenCalledWith('animationstart', jasmine.any(Function), listenerOptions); }); @@ -69,11 +69,11 @@ describe('AutofillMonitor', () => { it('should remove monitored class and listener upon stop monitoring', () => { const inputEl = testComponent.input1.nativeElement; autofillMonitor.monitor(inputEl); - expect(inputEl.classList).toContain('mat-input-autofill-monitored'); + expect(inputEl.classList).toContain('cdk-text-field-autofill-monitored'); expect(inputEl.removeEventListener).not.toHaveBeenCalled(); autofillMonitor.stopMonitoring(inputEl); - expect(inputEl.classList).not.toContain('mat-input-autofill-monitored'); + expect(inputEl.classList).not.toContain('cdk-text-field-autofill-monitored'); expect(inputEl.removeEventListener) .toHaveBeenCalledWith('animationstart', jasmine.any(Function), listenerOptions); }); @@ -103,10 +103,10 @@ describe('AutofillMonitor', () => { const autofillStream = autofillMonitor.monitor(inputEl); autofillStream.subscribe(event => autofillStreamEvent = event); expect(autofillStreamEvent).toBeNull(); - expect(inputEl.classList).not.toContain('mat-input-autofilled'); + expect(inputEl.classList).not.toContain('cdk-text-field-autofilled'); - animationStartCallback({animationName: 'mat-input-autofill-start', target: inputEl}); - expect(inputEl.classList).toContain('mat-input-autofilled'); + animationStartCallback({animationName: 'cdk-text-field-autofill-start', target: inputEl}); + expect(inputEl.classList).toContain('cdk-text-field-autofilled'); expect(autofillStreamEvent).toEqual({target: inputEl, isAutofilled: true} as any); }); @@ -117,12 +117,12 @@ describe('AutofillMonitor', () => { inputEl.addEventListener.and.callFake((_, cb) => animationStartCallback = cb); const autofillStream = autofillMonitor.monitor(inputEl); autofillStream.subscribe(event => autofillStreamEvent = event); - animationStartCallback({animationName: 'mat-input-autofill-start', target: inputEl}); - expect(inputEl.classList).toContain('mat-input-autofilled'); + animationStartCallback({animationName: 'cdk-text-field-autofill-start', target: inputEl}); + expect(inputEl.classList).toContain('cdk-text-field-autofilled'); expect(autofillStreamEvent).toEqual({target: inputEl, isAutofilled: true} as any); - animationStartCallback({animationName: 'mat-input-autofill-end', target: inputEl}); - expect(inputEl.classList).not.toContain('mat-input-autofilled'); + animationStartCallback({animationName: 'cdk-text-field-autofill-end', target: inputEl}); + expect(inputEl.classList).not.toContain('cdk-text-field-autofilled'); expect(autofillStreamEvent).toEqual({target: inputEl, isAutofilled: false} as any); }); @@ -131,11 +131,11 @@ describe('AutofillMonitor', () => { let animationStartCallback: Function = () => {}; inputEl.addEventListener.and.callFake((_, cb) => animationStartCallback = cb); autofillMonitor.monitor(inputEl); - animationStartCallback({animationName: 'mat-input-autofill-start', target: inputEl}); - expect(inputEl.classList).toContain('mat-input-autofilled'); + animationStartCallback({animationName: 'cdk-text-field-autofill-start', target: inputEl}); + expect(inputEl.classList).toContain('cdk-text-field-autofilled'); autofillMonitor.stopMonitoring(inputEl); - expect(inputEl.classlist).not.toContain('mat-input-autofilled'); + expect(inputEl.classlist).not.toContain('cdk-text-field-autofilled'); }); it('should complete the stream when monitoring is stopped', () => { @@ -152,15 +152,15 @@ describe('AutofillMonitor', () => { }); -describe('matAutofill', () => { +describe('cdkAutofill', () => { let autofillMonitor: AutofillMonitor; - let fixture: ComponentFixture; - let testComponent: InputWithMatAutofilled; + let fixture: ComponentFixture; + let testComponent: InputWithCdkAutofilled; beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatInputModule], - declarations: [InputWithMatAutofilled], + imports: [TextFieldModule], + declarations: [InputWithCdkAutofilled], }).compileComponents(); }); @@ -168,7 +168,7 @@ describe('matAutofill', () => { autofillMonitor = afm; spyOn(autofillMonitor, 'monitor').and.returnValue(observableEmpty()); spyOn(autofillMonitor, 'stopMonitoring'); - fixture = TestBed.createComponent(InputWithMatAutofilled); + fixture = TestBed.createComponent(InputWithCdkAutofilled); testComponent = fixture.componentInstance; fixture.detectChanges(); })); @@ -198,8 +198,8 @@ class Inputs { } @Component({ - template: `` + template: `` }) -class InputWithMatAutofilled { +class InputWithCdkAutofilled { @ViewChild('input') input: ElementRef; } diff --git a/src/lib/input/autofill.ts b/src/cdk/text-field/autofill.ts similarity index 83% rename from src/lib/input/autofill.ts rename to src/cdk/text-field/autofill.ts index 163c9ff4c187..7bd13996c72f 100644 --- a/src/lib/input/autofill.ts +++ b/src/cdk/text-field/autofill.ts @@ -69,17 +69,17 @@ export class AutofillMonitor implements OnDestroy { const result = new Subject(); const listener = (event: AnimationEvent) => { - if (event.animationName === 'mat-input-autofill-start') { - element.classList.add('mat-input-autofilled'); + if (event.animationName === 'cdk-text-field-autofill-start') { + element.classList.add('cdk-text-field-autofilled'); result.next({target: event.target as Element, isAutofilled: true}); - } else if (event.animationName === 'mat-input-autofill-end') { - element.classList.remove('mat-input-autofilled'); + } else if (event.animationName === 'cdk-text-field-autofill-end') { + element.classList.remove('cdk-text-field-autofilled'); result.next({target: event.target as Element, isAutofilled: false}); } }; element.addEventListener('animationstart', listener, listenerOptions); - element.classList.add('mat-input-autofill-monitored'); + element.classList.add('cdk-text-field-autofill-monitored'); this._monitoredElements.set(element, { subject: result, @@ -101,8 +101,8 @@ export class AutofillMonitor implements OnDestroy { if (info) { info.unlisten(); info.subject.complete(); - element.classList.remove('mat-input-autofill-monitored'); - element.classList.remove('mat-input-autofilled'); + element.classList.remove('cdk-text-field-autofill-monitored'); + element.classList.remove('cdk-text-field-autofilled'); this._monitoredElements.delete(element); } } @@ -115,17 +115,17 @@ export class AutofillMonitor implements OnDestroy { /** A directive that can be used to monitor the autofill state of an input. */ @Directive({ - selector: '[matAutofill]', + selector: '[cdkAutofill]', }) -export class MatAutofill implements OnDestroy, OnInit { - @Output() matAutofill: EventEmitter = new EventEmitter(); +export class CdkAutofill implements OnDestroy, OnInit { + @Output() cdkAutofill: EventEmitter = new EventEmitter(); constructor(private _elementRef: ElementRef, private _autofillMonitor: AutofillMonitor) {} ngOnInit() { this._autofillMonitor .monitor(this._elementRef.nativeElement) - .subscribe(event => this.matAutofill.emit(event)); + .subscribe(event => this.cdkAutofill.emit(event)); } ngOnDestroy() { diff --git a/src/lib/input/autosize.spec.ts b/src/cdk/text-field/autosize.spec.ts similarity index 70% rename from src/lib/input/autosize.spec.ts rename to src/cdk/text-field/autosize.spec.ts index 2226951dd068..746726a61885 100644 --- a/src/lib/input/autosize.spec.ts +++ b/src/cdk/text-field/autosize.spec.ts @@ -1,36 +1,29 @@ +import {dispatchFakeEvent} from '@angular/cdk/testing'; import {Component, ViewChild} from '@angular/core'; +import {async, ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; import {FormsModule} from '@angular/forms'; -import {ComponentFixture, TestBed, async, fakeAsync, flush, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {MatInputModule} from './index'; -import {MatTextareaAutosize} from './autosize'; -import {MatStepperModule} from '@angular/material/stepper'; -import {MatTabsModule} from '@angular/material/tabs'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {dispatchFakeEvent} from '@angular/cdk/testing'; +import {CdkTextareaAutosize} from './autosize'; +import {TextFieldModule} from './text-field-module'; -describe('MatTextareaAutosize', () => { +describe('CdkTextareaAutosize', () => { let fixture: ComponentFixture; let textarea: HTMLTextAreaElement; - let autosize: MatTextareaAutosize; + let autosize: CdkTextareaAutosize; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ FormsModule, - MatInputModule, - MatStepperModule, - MatTabsModule, + TextFieldModule, NoopAnimationsModule, ], declarations: [ - AutosizeTextareaInAStep, - AutosizeTextareaInATab, AutosizeTextAreaWithContent, AutosizeTextAreaWithValue, AutosizeTextareaWithNgModel, - AutosizeTextareaWithLongPlaceholder ], }); @@ -43,7 +36,7 @@ describe('MatTextareaAutosize', () => { textarea = fixture.nativeElement.querySelector('textarea'); autosize = fixture.debugElement.query( - By.directive(MatTextareaAutosize)).injector.get(MatTextareaAutosize); + By.directive(CdkTextareaAutosize)).injector.get(CdkTextareaAutosize); }); it('should resize the textarea based on its content', () => { @@ -116,7 +109,7 @@ describe('MatTextareaAutosize', () => { .toBeGreaterThan(previousMaxHeight, 'Expected increased max-height with maxRows increase.'); }); - it('should export the matAutosize reference', () => { + it('should export the cdkAutosize reference', () => { expect(fixture.componentInstance.autosize).toBeTruthy(); expect(fixture.componentInstance.autosize.resizeToFitContent).toBeTruthy(); }); @@ -163,7 +156,7 @@ describe('MatTextareaAutosize', () => { // detection should be triggered after a multiline content is set. fixture = TestBed.createComponent(AutosizeTextAreaWithContent); textarea = fixture.nativeElement.querySelector('textarea'); - autosize = fixture.debugElement.query(By.css('textarea')).injector.get(MatTextareaAutosize); + autosize = fixture.debugElement.query(By.css('textarea')).injector.get(CdkTextareaAutosize); fixture.componentInstance.content = ` Line @@ -178,27 +171,6 @@ describe('MatTextareaAutosize', () => { .toBe(textarea.scrollHeight, 'Expected textarea height to match its scrollHeight'); }); - it('should not calculate wrong content height due to long placeholders', () => { - const fixtureWithPlaceholder = TestBed.createComponent(AutosizeTextareaWithLongPlaceholder); - fixtureWithPlaceholder.detectChanges(); - - textarea = fixtureWithPlaceholder.nativeElement.querySelector('textarea'); - autosize = fixtureWithPlaceholder.debugElement.query( - By.directive(MatTextareaAutosize)).injector.get(MatTextareaAutosize); - - autosize.resizeToFitContent(true); - - const heightWithLongPlaceholder = textarea.clientHeight; - - fixtureWithPlaceholder.componentInstance.placeholder = 'Short'; - fixtureWithPlaceholder.detectChanges(); - - autosize.resizeToFitContent(true); - - expect(textarea.clientHeight).toBe(heightWithLongPlaceholder, - 'Expected the textarea height to be the same with a long placeholder.'); - }); - it('should resize when an associated form control value changes', fakeAsync(() => { const fixtureWithForms = TestBed.createComponent(AutosizeTextareaWithNgModel); textarea = fixtureWithForms.nativeElement.querySelector('textarea'); @@ -237,20 +209,6 @@ describe('MatTextareaAutosize', () => { .toBeGreaterThan(previousHeight, 'Expected the textarea height to have increased.'); })); - it('should work in a tab', () => { - const fixtureWithForms = TestBed.createComponent(AutosizeTextareaInATab); - fixtureWithForms.detectChanges(); - textarea = fixtureWithForms.nativeElement.querySelector('textarea'); - expect(textarea.getBoundingClientRect().height).toBeGreaterThan(1); - }); - - it('should work in a step', () => { - const fixtureWithForms = TestBed.createComponent(AutosizeTextareaInAStep); - fixtureWithForms.detectChanges(); - textarea = fixtureWithForms.nativeElement.querySelector('textarea'); - expect(textarea.getBoundingClientRect().height).toBeGreaterThan(1); - }); - it('should trigger a resize when the window is resized', fakeAsync(() => { spyOn(autosize, 'resizeToFitContent'); @@ -271,21 +229,21 @@ const textareaStyleReset = ` @Component({ template: ` - `, styles: [textareaStyleReset], }) class AutosizeTextAreaWithContent { - @ViewChild('autosize') autosize: MatTextareaAutosize; + @ViewChild('autosize') autosize: CdkTextareaAutosize; minRows: number | null = null; maxRows: number | null = null; content: string = ''; } @Component({ - template: ``, + template: ``, styles: [textareaStyleReset], }) class AutosizeTextAreaWithValue { @@ -293,50 +251,9 @@ class AutosizeTextAreaWithValue { } @Component({ - template: ``, + template: ``, styles: [textareaStyleReset], }) class AutosizeTextareaWithNgModel { model = ''; } - -@Component({ - template: ` - - - `, - styles: [textareaStyleReset], -}) -class AutosizeTextareaWithLongPlaceholder { - placeholder = 'Long Long Long Long Long Long Long Long Placeholder'; -} - -@Component({ - template: ` - - - - - - - - ` -}) -class AutosizeTextareaInATab {} - -@Component({ - template: ` - - - - - - - - ` -}) -class AutosizeTextareaInAStep {} diff --git a/src/cdk/text-field/autosize.ts b/src/cdk/text-field/autosize.ts new file mode 100644 index 000000000000..52a2c2e50db2 --- /dev/null +++ b/src/cdk/text-field/autosize.ts @@ -0,0 +1,211 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { + Directive, + ElementRef, + Input, + AfterViewInit, + DoCheck, + OnDestroy, + NgZone, +} from '@angular/core'; +import {Platform} from '@angular/cdk/platform'; +import {fromEvent} from 'rxjs/observable/fromEvent'; +import {auditTime} from 'rxjs/operators/auditTime'; +import {takeUntil} from 'rxjs/operators/takeUntil'; +import {Subject} from 'rxjs/Subject'; + + +/** Directive to automatically resize a textarea to fit its content. */ +@Directive({ + selector: 'textarea[cdkTextareaAutosize]', + exportAs: 'cdkTextareaAutosize', + host: { + 'class': 'cdk-textarea-autosize', + // Textarea elements that have the directive applied should have a single row by default. + // Browsers normally show two rows by default and therefore this limits the minRows binding. + 'rows': '1', + '(input)': '_noopInputHandler()', + }, +}) +export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { + /** Keep track of the previous textarea value to avoid resizing when the value hasn't changed. */ + private _previousValue: string; + private readonly _destroyed = new Subject(); + + private _minRows: number; + private _maxRows: number; + + /** Minimum amount of rows in the textarea. */ + @Input('cdkAutosizeMinRows') + set minRows(value: number) { + this._minRows = value; + this._setMinHeight(); + } + get minRows(): number { return this._minRows; } + + /** Maximum amount of rows in the textarea. */ + @Input('cdkAutosizeMaxRows') + get maxRows(): number { return this._maxRows; } + set maxRows(value: number) { + this._maxRows = value; + this._setMaxHeight(); + } + + /** Cached height of a textarea with a single row. */ + private _cachedLineHeight: number; + + constructor( + private _elementRef: ElementRef, + private _platform: Platform, + private _ngZone?: NgZone) {} + + // TODO(crisbeto): make the `_ngZone` a required param in the next major version. + + /** Sets the minimum height of the textarea as determined by minRows. */ + _setMinHeight(): void { + const minHeight = this.minRows && this._cachedLineHeight ? + `${this.minRows * this._cachedLineHeight}px` : null; + + if (minHeight) { + this._setTextareaStyle('minHeight', minHeight); + } + } + + /** Sets the maximum height of the textarea as determined by maxRows. */ + _setMaxHeight(): void { + const maxHeight = this.maxRows && this._cachedLineHeight ? + `${this.maxRows * this._cachedLineHeight}px` : null; + + if (maxHeight) { + this._setTextareaStyle('maxHeight', maxHeight); + } + } + + ngAfterViewInit() { + if (this._platform.isBrowser) { + this.resizeToFitContent(); + + if (this._ngZone) { + this._ngZone.runOutsideAngular(() => { + fromEvent(window, 'resize') + .pipe(auditTime(16), takeUntil(this._destroyed)) + .subscribe(() => this.resizeToFitContent(true)); + }); + } + } + } + + ngOnDestroy() { + this._destroyed.next(); + this._destroyed.complete(); + } + + /** Sets a style property on the textarea element. */ + private _setTextareaStyle(property: string, value: string): void { + const textarea = this._elementRef.nativeElement as HTMLTextAreaElement; + textarea.style[property] = value; + } + + /** + * Cache the height of a single-row textarea if it has not already been cached. + * + * We need to know how large a single "row" of a textarea is in order to apply minRows and + * maxRows. For the initial version, we will assume that the height of a single line in the + * textarea does not ever change. + */ + private _cacheTextareaLineHeight(): void { + if (this._cachedLineHeight) { + return; + } + + let textarea = this._elementRef.nativeElement as HTMLTextAreaElement; + + // Use a clone element because we have to override some styles. + let textareaClone = textarea.cloneNode(false) as HTMLTextAreaElement; + textareaClone.rows = 1; + + // Use `position: absolute` so that this doesn't cause a browser layout and use + // `visibility: hidden` so that nothing is rendered. Clear any other styles that + // would affect the height. + textareaClone.style.position = 'absolute'; + textareaClone.style.visibility = 'hidden'; + textareaClone.style.border = 'none'; + textareaClone.style.padding = '0'; + textareaClone.style.height = ''; + textareaClone.style.minHeight = ''; + textareaClone.style.maxHeight = ''; + + // In Firefox it happens that textarea elements are always bigger than the specified amount + // of rows. This is because Firefox tries to add extra space for the horizontal scrollbar. + // As a workaround that removes the extra space for the scrollbar, we can just set overflow + // to hidden. This ensures that there is no invalid calculation of the line height. + // See Firefox bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=33654 + textareaClone.style.overflow = 'hidden'; + + textarea.parentNode!.appendChild(textareaClone); + this._cachedLineHeight = textareaClone.clientHeight; + textarea.parentNode!.removeChild(textareaClone); + + // Min and max heights have to be re-calculated if the cached line height changes + this._setMinHeight(); + this._setMaxHeight(); + } + + ngDoCheck() { + if (this._platform.isBrowser) { + this.resizeToFitContent(); + } + } + + /** + * Resize the textarea to fit its content. + * @param force Whether to force a height recalculation. By default the height will be + * recalculated only if the value changed since the last call. + */ + resizeToFitContent(force: boolean = false) { + this._cacheTextareaLineHeight(); + + // If we haven't determined the line-height yet, we know we're still hidden and there's no point + // in checking the height of the textarea. + if (!this._cachedLineHeight) { + return; + } + + const textarea = this._elementRef.nativeElement as HTMLTextAreaElement; + const value = textarea.value; + + // Only resize of the value changed since these calculations can be expensive. + if (value === this._previousValue && !force) { + return; + } + + const placeholderText = textarea.placeholder; + + // Reset the textarea height to auto in order to shrink back to its default size. + // Also temporarily force overflow:hidden, so scroll bars do not interfere with calculations. + // Long placeholders that are wider than the textarea width may lead to a bigger scrollHeight + // value. To ensure that the scrollHeight is not bigger than the content, the placeholders + // need to be removed temporarily. + textarea.style.height = 'auto'; + textarea.style.overflow = 'hidden'; + textarea.placeholder = ''; + + // Use the scrollHeight to know how large the textarea *would* be if fit its entire value. + textarea.style.height = `${textarea.scrollHeight}px`; + textarea.style.overflow = ''; + textarea.placeholder = placeholderText; + + this._previousValue = value; + } + + _noopInputHandler() { + // no-op handler that ensures we're running change detection on input events. + } +} diff --git a/src/cdk/text-field/index.ts b/src/cdk/text-field/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/cdk/text-field/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/cdk/text-field/public-api.ts b/src/cdk/text-field/public-api.ts new file mode 100644 index 000000000000..dc36fd0b3f51 --- /dev/null +++ b/src/cdk/text-field/public-api.ts @@ -0,0 +1,11 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './autofill'; +export * from './autosize'; +export * from './text-field-module'; diff --git a/src/cdk/text-field/text-field-module.ts b/src/cdk/text-field/text-field-module.ts new file mode 100644 index 000000000000..518f652e0a5c --- /dev/null +++ b/src/cdk/text-field/text-field-module.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {PlatformModule} from '@angular/cdk/platform'; +import {NgModule} from '@angular/core'; +import {AutofillMonitor, CdkAutofill} from './autofill'; +import {CdkTextareaAutosize} from './autosize'; + + +@NgModule({ + declarations: [CdkAutofill, CdkTextareaAutosize], + imports: [PlatformModule], + exports: [CdkAutofill, CdkTextareaAutosize], + providers: [AutofillMonitor], +}) +export class TextFieldModule {} diff --git a/src/cdk/text-field/text-field-prebuilt.scss b/src/cdk/text-field/text-field-prebuilt.scss new file mode 100644 index 000000000000..b18d18728100 --- /dev/null +++ b/src/cdk/text-field/text-field-prebuilt.scss @@ -0,0 +1,3 @@ +@import 'text-field'; + +@include cdk-text-field(); diff --git a/src/cdk/text-field/tsconfig-build.json b/src/cdk/text-field/tsconfig-build.json new file mode 100644 index 000000000000..34258e730aeb --- /dev/null +++ b/src/cdk/text-field/tsconfig-build.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig-build", + "files": [ + "public-api.ts" + ], + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": false, // Workaround for Angular #22210 + "flatModuleOutFile": "index.js", + "flatModuleId": "@angular/cdk/text-field", + "skipTemplateCodegen": true, + "fullTemplateTypeCheck": true + } +} diff --git a/src/demo-app/a11y/input/input-a11y.html b/src/demo-app/a11y/input/input-a11y.html index 006b85707ac3..4a406ce078c9 100644 --- a/src/demo-app/a11y/input/input-a11y.html +++ b/src/demo-app/a11y/input/input-a11y.html @@ -53,7 +53,7 @@

Input with prefix & suffix (e.g. currency converter)

Textarea input (e.g. comment box)

- Leave us a comment! {{commentCount}}/{{commentMax}} diff --git a/src/demo-app/baseline/baseline-demo.html b/src/demo-app/baseline/baseline-demo.html index e25d9f566ee0..c021f9d37fbe 100644 --- a/src/demo-app/baseline/baseline-demo.html +++ b/src/demo-app/baseline/baseline-demo.html @@ -25,7 +25,7 @@ | Text 6 | Input - + | Text After @@ -59,7 +59,7 @@

| Text 6 | Input - + | Text After

diff --git a/src/demo-app/demo-material-module.ts b/src/demo-app/demo-material-module.ts index 0700ca429af4..59f87509b13f 100644 --- a/src/demo-app/demo-material-module.ts +++ b/src/demo-app/demo-material-module.ts @@ -9,6 +9,7 @@ import {A11yModule} from '@angular/cdk/a11y'; import {CdkAccordionModule} from '@angular/cdk/accordion'; import {BidiModule} from '@angular/cdk/bidi'; +import {TextFieldModule} from '@angular/cdk/text-field'; import {ObserversModule} from '@angular/cdk/observers'; import {OverlayModule} from '@angular/cdk/overlay'; import {PlatformModule} from '@angular/cdk/platform'; @@ -101,6 +102,7 @@ import { A11yModule, BidiModule, CdkAccordionModule, + TextFieldModule, ObserversModule, OverlayModule, PlatformModule, diff --git a/src/demo-app/input/input-demo.html b/src/demo-app/input/input-demo.html index d538ad3c9194..1a3708796669 100644 --- a/src/demo-app/input/input-demo.html +++ b/src/demo-app/input/input-demo.html @@ -541,15 +541,13 @@

Textarea

Textarea Autosize

Regular <textarea>

- +

<textarea> with mat-form-field

Autosized textarea - +
@@ -559,7 +557,7 @@

<textarea> with ngModel

@@ -641,7 +639,7 @@

<textarea> with ngModel

Autofill monitored - diff --git a/src/demo-app/input/input-demo.scss b/src/demo-app/input/input-demo.scss index eb672306f444..0fde5d5a9e15 100644 --- a/src/demo-app/input/input-demo.scss +++ b/src/demo-app/input/input-demo.scss @@ -1,4 +1,4 @@ -@import '../../lib/input/autofill'; +@import '../../cdk/text-field/text-field'; .demo-basic { padding: 0; @@ -31,5 +31,5 @@ } .demo-custom-autofill-style { - @include mat-input-autofill-color(transparent, red); + @include cdk-text-field-autofill-color(transparent, red); } diff --git a/src/demo-app/stepper/stepper-demo.html b/src/demo-app/stepper/stepper-demo.html index b1a0c020fdcc..67b2a97237bb 100644 --- a/src/demo-app/stepper/stepper-demo.html +++ b/src/demo-app/stepper/stepper-demo.html @@ -218,7 +218,7 @@

Stepper with autosize textarea

Autosize textarea - + diff --git a/src/demo-app/system-config.ts b/src/demo-app/system-config.ts index aed53636aace..d5668b7a0d28 100644 --- a/src/demo-app/system-config.ts +++ b/src/demo-app/system-config.ts @@ -57,6 +57,7 @@ System.config({ '@angular/cdk/scrolling': 'dist/packages/cdk/scrolling/index.js', '@angular/cdk/stepper': 'dist/packages/cdk/stepper/index.js', '@angular/cdk/table': 'dist/packages/cdk/table/index.js', + '@angular/cdk/text-field': 'dist/packages/cdk/text-field/index.js', '@angular/cdk/tree': 'dist/packages/cdk/tree/index.js', '@angular/material/autocomplete': 'dist/packages/material/autocomplete/index.js', diff --git a/src/demo-app/tabs/tabs-demo.html b/src/demo-app/tabs/tabs-demo.html index eefc9e8e3d6f..153bcc0ac97b 100644 --- a/src/demo-app/tabs/tabs-demo.html +++ b/src/demo-app/tabs/tabs-demo.html @@ -288,7 +288,7 @@

Tabs with autosize textarea

Autosize textarea - +
diff --git a/src/e2e-app/input/input-e2e.html b/src/e2e-app/input/input-e2e.html index ee908d25449e..424576ba81d5 100644 --- a/src/e2e-app/input/input-e2e.html +++ b/src/e2e-app/input/input-e2e.html @@ -16,7 +16,7 @@

-

diff --git a/src/e2e-app/system-config.ts b/src/e2e-app/system-config.ts index 92e08104e896..2c794dad0317 100644 --- a/src/e2e-app/system-config.ts +++ b/src/e2e-app/system-config.ts @@ -50,6 +50,7 @@ System.config({ '@angular/cdk/tree': 'dist/bundles/cdk-tree.umd.js', '@angular/cdk/testing': 'dist/bundles/cdk-testing.umd.js', '@angular/material-examples': 'dist/bundles/material-examples.umd.js', + '@angular/cdk/text-field': 'dist/bundles/cdk-text-field.umd.js', '@angular/material/autocomplete': 'dist/bundles/material-autocomplete.umd.js', '@angular/material/bottom-sheet': 'dist/bundles/material-bottom-sheet.umd.js', diff --git a/src/e2e-app/tabs/tabs-e2e.html b/src/e2e-app/tabs/tabs-e2e.html index ffd4723907a1..c0262f133d38 100644 --- a/src/e2e-app/tabs/tabs-e2e.html +++ b/src/e2e-app/tabs/tabs-e2e.html @@ -3,7 +3,7 @@ One - + diff --git a/src/lib/core/_core.scss b/src/lib/core/_core.scss index 748e3a5735b7..83e8b0da3111 100644 --- a/src/lib/core/_core.scss +++ b/src/lib/core/_core.scss @@ -2,7 +2,7 @@ // up into a single flat scss file for material. @import '../../cdk/overlay/overlay'; @import '../../cdk/a11y/a11y'; -@import '../input/autofill'; +@import '../../cdk/text-field/text-field'; // Core styles that can be used to apply material design treatments to any element. @import 'style/elevation'; @@ -27,7 +27,7 @@ @include mat-ripple(); @include cdk-a11y(); @include cdk-overlay(); - @include mat-input-autofill(); + @include cdk-text-field(); } // Mixin that renders all of the core styles that depend on the theme. diff --git a/src/lib/input/BUILD.bazel b/src/lib/input/BUILD.bazel index 4412e5905587..78e20c98e569 100644 --- a/src/lib/input/BUILD.bazel +++ b/src/lib/input/BUILD.bazel @@ -13,6 +13,7 @@ ng_module( "//src/lib/form-field", "//src/cdk/coercion", "//src/cdk/platform", + "//src/cdk/text-field", ], tsconfig = ":tsconfig-build.json", ) diff --git a/src/lib/input/_autofill.scss b/src/lib/input/_autofill.scss deleted file mode 100644 index fc778c84a9eb..000000000000 --- a/src/lib/input/_autofill.scss +++ /dev/null @@ -1,43 +0,0 @@ -// Core styles that enable monitoring autofill state of inputs. -@mixin mat-input-autofill { - // Keyframes that apply no styles, but allow us to monitor when an input becomes autofilled - // by watching for the animation events that are fired when they start. - // Based on: https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7 - @keyframes mat-input-autofill-start {} - @keyframes mat-input-autofill-end {} - - .mat-input-autofill-monitored:-webkit-autofill { - animation-name: mat-input-autofill-start; - } - - .mat-input-autofill-monitored:not(:-webkit-autofill) { - animation-name: mat-input-autofill-end; - } -} - -// Used to generate UIDs for keyframes used to change the input autofill styles. -$mat-input-autofill-color-frame-count: 0; - -// Mixin used to apply custom background and foreground colors to an autofilled input. Based on: -// https://stackoverflow.com/questions/2781549/ -// removing-input-background-colour-for-chrome-autocomplete#answer-37432260 -@mixin mat-input-autofill-color($background, $foreground:'') { - @keyframes mat-input-autofill-color-#{$mat-input-autofill-color-frame-count} { - to { - background: $background; - @if $foreground != '' { color: $foreground; } - } - } - - &:-webkit-autofill { - animation-name: mat-input-autofill-color-#{$mat-input-autofill-color-frame-count}; - animation-fill-mode: both; - } - - &.mat-input-autofill-monitored:-webkit-autofill { - animation-name: mat-input-autofill-start, - mat-input-autofill-color-#{$mat-input-autofill-color-frame-count}; - } - - $mat-input-autofill-color-frame-count: $mat-input-autofill-color-frame-count + 1 !global; -} diff --git a/src/lib/input/autofill-prebuilt.scss b/src/lib/input/autofill-prebuilt.scss deleted file mode 100644 index 39ae312c13f3..000000000000 --- a/src/lib/input/autofill-prebuilt.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import 'autofill'; - -@include mat-input-autofill(); diff --git a/src/lib/input/autosize.ts b/src/lib/input/autosize.ts index f906608eba7c..7d24c0391c0c 100644 --- a/src/lib/input/autosize.ts +++ b/src/lib/input/autosize.ts @@ -6,203 +6,32 @@ * found in the LICENSE file at https://angular.io/license */ -import { - Directive, - ElementRef, - Input, - AfterViewInit, - DoCheck, - OnDestroy, - NgZone, -} from '@angular/core'; -import {Platform} from '@angular/cdk/platform'; -import {fromEvent} from 'rxjs/observable/fromEvent'; -import {auditTime} from 'rxjs/operators/auditTime'; -import {takeUntil} from 'rxjs/operators/takeUntil'; -import {Subject} from 'rxjs/Subject'; +import {CdkTextareaAutosize} from '@angular/cdk/text-field'; +import {Directive, Input} from '@angular/core'; /** * Directive to automatically resize a textarea to fit its content. + * @deletion-target 7.0.0 deprecate in favor of `cdkTextareaAutosize`. */ @Directive({ - selector: `textarea[mat-autosize], textarea[matTextareaAutosize]`, + selector: 'textarea[mat-autosize], textarea[matTextareaAutosize]', exportAs: 'matTextareaAutosize', + inputs: ['cdkAutosizeMinRows', 'cdkAutosizeMaxRows'], host: { - 'class': 'mat-autosize', + 'class': 'cdk-textarea-autosize mat-autosize', // Textarea elements that have the directive applied should have a single row by default. // Browsers normally show two rows by default and therefore this limits the minRows binding. 'rows': '1', + '(input)': '_noopInputHandler()', }, }) -export class MatTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { - /** Keep track of the previous textarea value to avoid resizing when the value hasn't changed. */ - private _previousValue: string; - private readonly _destroyed = new Subject(); - - private _minRows: number; - private _maxRows: number; - - /** Minimum amount of rows in the textarea. */ - @Input('matAutosizeMinRows') - set minRows(value: number) { - this._minRows = value; - this._setMinHeight(); - } - get minRows(): number { return this._minRows; } - - /** Maximum amount of rows in the textarea. */ - @Input('matAutosizeMaxRows') - get maxRows(): number { return this._maxRows; } - set maxRows(value: number) { - this._maxRows = value; - this._setMaxHeight(); - } - - /** Cached height of a textarea with a single row. */ - private _cachedLineHeight: number; - - constructor( - private _elementRef: ElementRef, - private _platform: Platform, - private _ngZone?: NgZone) {} - - // TODO(crisbeto): make the `_ngZone` a required param in the next major version. - - /** Sets the minimum height of the textarea as determined by minRows. */ - _setMinHeight(): void { - const minHeight = this.minRows && this._cachedLineHeight ? - `${this.minRows * this._cachedLineHeight}px` : null; - - if (minHeight) { - this._setTextareaStyle('minHeight', minHeight); - } - } - - /** Sets the maximum height of the textarea as determined by maxRows. */ - _setMaxHeight(): void { - const maxHeight = this.maxRows && this._cachedLineHeight ? - `${this.maxRows * this._cachedLineHeight}px` : null; - - if (maxHeight) { - this._setTextareaStyle('maxHeight', maxHeight); - } - } - - ngAfterViewInit() { - if (this._platform.isBrowser) { - this.resizeToFitContent(); - - if (this._ngZone) { - this._ngZone.runOutsideAngular(() => { - fromEvent(window, 'resize') - .pipe(auditTime(16), takeUntil(this._destroyed)) - .subscribe(() => this.resizeToFitContent(true)); - }); - } - } - } - - ngOnDestroy() { - this._destroyed.next(); - this._destroyed.complete(); - } - - /** Sets a style property on the textarea element. */ - private _setTextareaStyle(property: string, value: string): void { - const textarea = this._elementRef.nativeElement as HTMLTextAreaElement; - textarea.style[property] = value; - } - - /** - * Cache the height of a single-row textarea if it has not already been cached. - * - * We need to know how large a single "row" of a textarea is in order to apply minRows and - * maxRows. For the initial version, we will assume that the height of a single line in the - * textarea does not ever change. - */ - private _cacheTextareaLineHeight(): void { - if (this._cachedLineHeight) { - return; - } - - let textarea = this._elementRef.nativeElement as HTMLTextAreaElement; - - // Use a clone element because we have to override some styles. - let textareaClone = textarea.cloneNode(false) as HTMLTextAreaElement; - textareaClone.rows = 1; - - // Use `position: absolute` so that this doesn't cause a browser layout and use - // `visibility: hidden` so that nothing is rendered. Clear any other styles that - // would affect the height. - textareaClone.style.position = 'absolute'; - textareaClone.style.visibility = 'hidden'; - textareaClone.style.border = 'none'; - textareaClone.style.padding = '0'; - textareaClone.style.height = ''; - textareaClone.style.minHeight = ''; - textareaClone.style.maxHeight = ''; - - // In Firefox it happens that textarea elements are always bigger than the specified amount - // of rows. This is because Firefox tries to add extra space for the horizontal scrollbar. - // As a workaround that removes the extra space for the scrollbar, we can just set overflow - // to hidden. This ensures that there is no invalid calculation of the line height. - // See Firefox bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=33654 - textareaClone.style.overflow = 'hidden'; - - textarea.parentNode!.appendChild(textareaClone); - this._cachedLineHeight = textareaClone.clientHeight; - textarea.parentNode!.removeChild(textareaClone); - - // Min and max heights have to be re-calculated if the cached line height changes - this._setMinHeight(); - this._setMaxHeight(); - } - - ngDoCheck() { - if (this._platform.isBrowser) { - this.resizeToFitContent(); - } - } - - /** - * Resize the textarea to fit its content. - * @param force Whether to force a height recalculation. By default the height will be - * recalculated only if the value changed since the last call. - */ - resizeToFitContent(force: boolean = false) { - this._cacheTextareaLineHeight(); - - // If we haven't determined the line-height yet, we know we're still hidden and there's no point - // in checking the height of the textarea. - if (!this._cachedLineHeight) { - return; - } - - const textarea = this._elementRef.nativeElement as HTMLTextAreaElement; - const value = textarea.value; - - // Only resize of the value changed since these calculations can be expensive. - if (value === this._previousValue && !force) { - return; - } - - const placeholderText = textarea.placeholder; - - // Reset the textarea height to auto in order to shrink back to its default size. - // Also temporarily force overflow:hidden, so scroll bars do not interfere with calculations. - // Long placeholders that are wider than the textarea width may lead to a bigger scrollHeight - // value. To ensure that the scrollHeight is not bigger than the content, the placeholders - // need to be removed temporarily. - textarea.style.height = 'auto'; - textarea.style.overflow = 'hidden'; - textarea.placeholder = ''; - - // Use the scrollHeight to know how large the textarea *would* be if fit its entire value. - textarea.style.height = `${textarea.scrollHeight}px`; - textarea.style.overflow = ''; - textarea.placeholder = placeholderText; - - this._previousValue = value; - } +export class MatTextareaAutosize extends CdkTextareaAutosize { + @Input() + get matAutosizeMinRows(): number { return this.minRows; } + set matAutosizeMinRows(value: number) { this.minRows = value; } + + @Input() + get matAutosizeMaxRows(): number { return this.maxRows; } + set matAutosizeMaxRows(value: number) { this.maxRows = value; } } diff --git a/src/lib/input/input-module.ts b/src/lib/input/input-module.ts index 00f9a838637d..403a866d3849 100644 --- a/src/lib/input/input-module.ts +++ b/src/lib/input/input-module.ts @@ -6,35 +6,32 @@ * found in the LICENSE file at https://angular.io/license */ +import {TextFieldModule} from '@angular/cdk/text-field'; import {PlatformModule} from '@angular/cdk/platform'; import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {ErrorStateMatcher} from '@angular/material/core'; import {MatFormFieldModule} from '@angular/material/form-field'; -import {AutofillMonitor, MatAutofill} from './autofill'; import {MatTextareaAutosize} from './autosize'; import {MatInput} from './input'; @NgModule({ - declarations: [ - MatAutofill, - MatInput, - MatTextareaAutosize, - ], + declarations: [MatInput, MatTextareaAutosize], imports: [ CommonModule, + TextFieldModule, MatFormFieldModule, PlatformModule, ], exports: [ - MatAutofill, + TextFieldModule, // We re-export the `MatFormFieldModule` since `MatInput` will almost always // be used together with `MatFormField`. MatFormFieldModule, MatInput, MatTextareaAutosize, ], - providers: [ErrorStateMatcher, AutofillMonitor], + providers: [ErrorStateMatcher], }) export class MatInputModule {} diff --git a/src/lib/input/input.md b/src/lib/input/input.md index 917a66e09946..87cb96b8054c 100644 --- a/src/lib/input/input.md +++ b/src/lib/input/input.md @@ -77,9 +77,9 @@ globally cause input errors to show when the input is dirty and invalid. ### Auto-resizing ` +
`, + styles: [textareaStyleReset], +}) +class AutosizeTextareaWithLongPlaceholder { + placeholder = 'Long Long Long Long Long Long Long Long Placeholder'; + @ViewChild(MatTextareaAutosize) autosize: MatTextareaAutosize; +} + +@Component({ + template: ` + + + + + + + + ` +}) +class AutosizeTextareaInATab {} + +@Component({ + template: ` + + + + + + + + ` +}) +class AutosizeTextareaInAStep {} diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts index eb0f757ba418..af144ed38a5a 100644 --- a/src/lib/input/input.ts +++ b/src/lib/input/input.ts @@ -24,7 +24,7 @@ import {FormGroupDirective, NgControl, NgForm} from '@angular/forms'; import {CanUpdateErrorState, ErrorStateMatcher, mixinErrorState} from '@angular/material/core'; import {MatFormFieldControl} from '@angular/material/form-field'; import {Subject} from 'rxjs/Subject'; -import {AutofillMonitor} from './autofill'; +import {AutofillMonitor} from '@angular/cdk/text-field'; import {getMatInputUnsupportedTypeError} from './input-errors'; import {MAT_INPUT_VALUE_ACCESSOR} from './input-value-accessor'; diff --git a/src/lib/input/public-api.ts b/src/lib/input/public-api.ts index f2f80acf31e8..a76b4fc47d05 100644 --- a/src/lib/input/public-api.ts +++ b/src/lib/input/public-api.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ - -export * from './autofill'; export * from './autosize'; export * from './input'; export * from './input-errors'; diff --git a/src/material-examples/input-autosize-textarea/input-autosize-textarea-example.html b/src/material-examples/input-autosize-textarea/input-autosize-textarea-example.html index e9487d20b7c4..ad7da7b97ec4 100644 --- a/src/material-examples/input-autosize-textarea/input-autosize-textarea-example.html +++ b/src/material-examples/input-autosize-textarea/input-autosize-textarea-example.html @@ -1,4 +1,4 @@ - + diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index e9c2be7d67a9..f230fd39587e 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -258,7 +258,7 @@

Tooltip

Autosize textarea

- +

Expansion Panel

diff --git a/test/karma-test-shim.js b/test/karma-test-shim.js index 7cb51136e509..8c88ad177aa3 100644 --- a/test/karma-test-shim.js +++ b/test/karma-test-shim.js @@ -69,6 +69,7 @@ System.config({ '@angular/cdk/stepper': 'dist/packages/cdk/stepper/index.js', '@angular/cdk/table': 'dist/packages/cdk/table/index.js', '@angular/cdk/testing': 'dist/packages/cdk/testing/index.js', + '@angular/cdk/text-field': 'dist/packages/cdk/text-field/index.js', '@angular/cdk/tree': 'dist/packages/cdk/tree/index.js', '@angular/material/autocomplete': 'dist/packages/material/autocomplete/index.js',