This is an autosize textarea, it should adjust to the size of its content.
+
This is an autosize textarea, it should adjust to the size of its content.
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 `
` elements
`
` elements can be made to automatically resize to fit their contents by applying the
-`matTextareaAutosize` directive. This works with `
` elements as well as plain
+`cdkTextareaAutosize` directive. This works with `
` elements as well as plain
native `
` elements. The min and max size of the textarea can be specified in rows, using
-the `matAutosizeMinRows` and `matAutosizeMaxRows` properties respectively.
+the `cdkAutosizeMinRows` and `cdkAutosizeMaxRows` properties respectively.
diff --git a/src/lib/input/input.scss b/src/lib/input/input.scss
index 6479e3d44677..9827e3b9c86a 100644
--- a/src/lib/input/input.scss
+++ b/src/lib/input/input.scss
@@ -87,10 +87,8 @@ textarea.mat-input-element {
// Only allow resizing along the Y axis.
resize: vertical;
overflow: auto;
-}
-// Remove the resize handle on autosizing textareas, because whatever height
-// the user resized to will be overwritten once they start typing again.
-textarea.mat-autosize {
- resize: none;
+ &.cdk-textarea-autosize {
+ resize: none;
+ }
}
diff --git a/src/lib/input/input.spec.ts b/src/lib/input/input.spec.ts
index 34109a29c779..476696f63b34 100644
--- a/src/lib/input/input.spec.ts
+++ b/src/lib/input/input.spec.ts
@@ -20,7 +20,8 @@ import {
import {
getMatFormFieldDuplicatedHintError,
getMatFormFieldMissingControlError,
- getMatFormFieldPlaceholderConflictError, MAT_FORM_FIELD_DEFAULT_OPTIONS,
+ getMatFormFieldPlaceholderConflictError,
+ MAT_FORM_FIELD_DEFAULT_OPTIONS,
MatFormField,
MatFormFieldAppearance,
MatFormFieldModule,
@@ -29,6 +30,9 @@ import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatInputModule} from './index';
import {MatInput} from './input';
+import {MatStepperModule} from '@angular/material/stepper';
+import {MatTabsModule} from '@angular/material/tabs';
+import {MatTextareaAutosize} from './autosize';
describe('MatInput without forms', () => {
beforeEach(fakeAsync(() => {
@@ -1298,6 +1302,61 @@ describe('MatFormField default options', () => {
}));
});
+describe('MatInput with textarea autosize', () => {
+ beforeEach(fakeAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ FormsModule,
+ MatInputModule,
+ MatStepperModule,
+ MatTabsModule,
+ NoopAnimationsModule,
+ ],
+ declarations: [
+ AutosizeTextareaInAStep,
+ AutosizeTextareaInATab,
+ AutosizeTextareaWithLongPlaceholder,
+ ],
+ });
+
+ TestBed.compileComponents();
+ }));
+
+ it('should not calculate wrong content height due to long placeholders', () => {
+ const fixture = TestBed.createComponent(AutosizeTextareaWithLongPlaceholder);
+ fixture.detectChanges();
+
+ const textarea = fixture.nativeElement.querySelector('textarea');
+ const autosize = fixture.componentInstance.autosize;
+
+ autosize.resizeToFitContent(true);
+
+ const heightWithLongPlaceholder = textarea.clientHeight;
+
+ fixture.componentInstance.placeholder = 'Short';
+ fixture.detectChanges();
+
+ autosize.resizeToFitContent(true);
+
+ expect(textarea.clientHeight).toBe(heightWithLongPlaceholder,
+ 'Expected the textarea height to be the same with a long placeholder.');
+ });
+
+ it('should work in a tab', () => {
+ const fixture = TestBed.createComponent(AutosizeTextareaInATab);
+ fixture.detectChanges();
+ const textarea = fixture.nativeElement.querySelector('textarea');
+ expect(textarea.getBoundingClientRect().height).toBeGreaterThan(1);
+ });
+
+ it('should work in a step', () => {
+ const fixture = TestBed.createComponent(AutosizeTextareaInAStep);
+ fixture.detectChanges();
+ const textarea = fixture.nativeElement.querySelector('textarea');
+ expect(textarea.getBoundingClientRect().height).toBeGreaterThan(1);
+ });
+});
+
@Component({
template: `
@@ -1658,3 +1717,53 @@ class MatInputWithAppearance {
})
class MatInputWithoutPlaceholder {
}
+
+// Styles to reset padding and border to make measurement comparisons easier.
+const textareaStyleReset = `
+ textarea {
+ padding: 0;
+ border: none;
+ overflow: auto;
+ }`;
+
+@Component({
+ template: `
+
+
+ `,
+ styles: [textareaStyleReset],
+})
+class AutosizeTextareaWithLongPlaceholder {
+ placeholder = 'Long Long Long Long Long Long Long Long Placeholder';
+ @ViewChild(MatTextareaAutosize) autosize: MatTextareaAutosize;
+}
+
+@Component({
+ template: `
+
+
+
+