diff --git a/scripts/check-mdc-tests-config.ts b/scripts/check-mdc-tests-config.ts index 4da723183dcb..bccb7185e851 100644 --- a/scripts/check-mdc-tests-config.ts +++ b/scripts/check-mdc-tests-config.ts @@ -77,7 +77,7 @@ export const config = { 'should calculate the outline gaps inside the shadow DOM', 'should be legacy appearance if no default options provided', 'should be legacy appearance if empty default options provided', - 'should adjust height due to long placeholders', + 'should not calculate wrong content height due to long placeholders', 'should work in a tab', 'should work in a step' ], diff --git a/src/cdk/text-field/autosize.spec.ts b/src/cdk/text-field/autosize.spec.ts index f80350cdcffb..a2a7463c427c 100644 --- a/src/cdk/text-field/autosize.spec.ts +++ b/src/cdk/text-field/autosize.spec.ts @@ -50,7 +50,7 @@ describe('CdkTextareaAutosize', () => { it('should resize the textarea based on its content', () => { let previousHeight = textarea.clientHeight; - textarea.value = ` + fixture.componentInstance.content = ` Once upon a midnight dreary, while I pondered, weak and weary, Over many a quaint and curious volume of forgotten lore— While I nodded, nearly napping, suddenly there came a tapping, @@ -68,7 +68,7 @@ describe('CdkTextareaAutosize', () => { .toBe(textarea.scrollHeight, 'Expected textarea height to match its scrollHeight'); previousHeight = textarea.clientHeight; - textarea.value += ` + fixture.componentInstance.content += ` Ah, distinctly I remember it was in the bleak December; And each separate dying ember wrought its ghost upon the floor. Eagerly I wished the morrow;—vainly I had sought to borrow @@ -85,38 +85,6 @@ describe('CdkTextareaAutosize', () => { .toBe(textarea.scrollHeight, 'Expected textarea height to match its scrollHeight'); }); - it('should keep the placeholder size if the value is shorter than the placeholder', () => { - fixture = TestBed.createComponent(AutosizeTextAreaWithContent); - - textarea = fixture.nativeElement.querySelector('textarea'); - autosize = fixture.debugElement.query(By.css('textarea'))! - .injector.get(CdkTextareaAutosize); - - fixture.componentInstance.placeholder = ` - Once upon a midnight dreary, while I pondered, weak and weary, - Over many a quaint and curious volume of forgotten lore— - While I nodded, nearly napping, suddenly there came a tapping, - As of some one gently rapping, rapping at my chamber door. - “’Tis some visitor,” I muttered, “tapping at my chamber door— - Only this and nothing more.”`; - - fixture.detectChanges(); - - expect(textarea.clientHeight) - .toBe(textarea.scrollHeight, 'Expected textarea height to match its scrollHeight'); - - let previousHeight = textarea.clientHeight; - - textarea.value = 'a'; - - // Manually call resizeToFitContent instead of faking an `input` event. - fixture.detectChanges(); - autosize.resizeToFitContent(); - - expect(textarea.clientHeight) - .toBe(previousHeight, 'Expected textarea height not to have changed'); - }); - it('should set a min-height based on minRows', () => { expect(textarea.style.minHeight).toBeFalsy(); @@ -193,7 +161,7 @@ describe('CdkTextareaAutosize', () => { }); it('should calculate the proper height based on the specified amount of max rows', () => { - textarea.value = [1, 2, 3, 4, 5, 6, 7, 8].join('\n'); + fixture.componentInstance.content = [1, 2, 3, 4, 5, 6, 7, 8].join('\n'); fixture.detectChanges(); autosize.resizeToFitContent(); @@ -228,27 +196,6 @@ describe('CdkTextareaAutosize', () => { .toBe(textarea.scrollHeight, 'Expected textarea height to match its scrollHeight'); }); - it('should properly resize to placeholder on init', () => { - // Manually create the test component in this test, because in this test the first change - // detection should be triggered after a multiline placeholder is set. - fixture = TestBed.createComponent(AutosizeTextAreaWithContent); - textarea = fixture.nativeElement.querySelector('textarea'); - autosize = fixture.debugElement.query(By.css('textarea'))! - .injector.get(CdkTextareaAutosize); - - fixture.componentInstance.placeholder = ` - Line - Line - Line - Line - Line`; - - fixture.detectChanges(); - - expect(textarea.clientHeight) - .toBe(textarea.scrollHeight, 'Expected textarea height to match its scrollHeight'); - }); - it('should resize when an associated form control value changes', fakeAsync(() => { const fixtureWithForms = TestBed.createComponent(AutosizeTextareaWithNgModel); textarea = fixtureWithForms.nativeElement.querySelector('textarea'); @@ -351,7 +298,7 @@ const textareaStyleReset = ` @Component({ template: ` `, + #autosize="cdkTextareaAutosize">{{content}}`, styles: [textareaStyleReset], }) class AutosizeTextAreaWithContent { @@ -359,7 +306,6 @@ class AutosizeTextAreaWithContent { minRows: number | null = null; maxRows: number | null = null; content: string = ''; - placeholder: string = ''; } @Component({ diff --git a/src/cdk/text-field/autosize.ts b/src/cdk/text-field/autosize.ts index 9e2263893ad6..74752924f0b6 100644 --- a/src/cdk/text-field/autosize.ts +++ b/src/cdk/text-field/autosize.ts @@ -88,19 +88,8 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { } } - @Input() - get placeholder(): string { return this._textareaElement.placeholder; } - set placeholder(value: string) { - this._cachedPlaceholderHeight = undefined; - this._textareaElement.placeholder = value; - this._cacheTextareaPlaceholderHeight(); - } - - /** Cached height of a textarea with a single row. */ private _cachedLineHeight: number; - /** Cached height of a textarea with only the placeholder. */ - private _cachedPlaceholderHeight?: number; /** Used to reference correct document/window */ protected _document?: Document; @@ -206,30 +195,6 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { this._setMaxHeight(); } - private _measureScrollHeight(): number { - // 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. - this._textareaElement.classList.add(this._measuringClass); - // The measuring class includes a 2px padding to workaround an issue with Chrome, - // so we account for that extra space here by subtracting 4 (2px top + 2px bottom). - const scrollHeight = this._textareaElement.scrollHeight - 4; - this._textareaElement.classList.remove(this._measuringClass); - - return scrollHeight; - } - - private _cacheTextareaPlaceholderHeight(): void { - if (this._cachedPlaceholderHeight) { - return; - } - - const value = this._textareaElement.value; - - this._textareaElement.value = this._textareaElement.placeholder; - this._cachedPlaceholderHeight = this._measureScrollHeight(); - this._textareaElement.value = value; - } - ngDoCheck() { if (this._platform.isBrowser) { this.resizeToFitContent(); @@ -248,7 +213,6 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { } this._cacheTextareaLineHeight(); - this._cacheTextareaPlaceholderHeight(); // 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. @@ -264,14 +228,24 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { return; } - const scrollHeight = this._measureScrollHeight(); + 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.classList.add(this._measuringClass); + textarea.placeholder = ''; // The measuring class includes a 2px padding to workaround an issue with Chrome, // so we account for that extra space here by subtracting 4 (2px top + 2px bottom). - const height = Math.max(scrollHeight, this._cachedPlaceholderHeight || 0); + const height = textarea.scrollHeight - 4; // Use the scrollHeight to know how large the textarea *would* be if fit its entire value. textarea.style.height = `${height}px`; + textarea.classList.remove(this._measuringClass); + textarea.placeholder = placeholderText; this._ngZone.runOutsideAngular(() => { if (typeof requestAnimationFrame !== 'undefined') { diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 032366acfd57..3e89d0efa63e 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -1719,7 +1719,7 @@ describe('MatFormField default options', () => { }); describe('MatInput with textarea autosize', () => { - it('should adjust height due to long placeholders', () => { + it('should not calculate wrong content height due to long placeholders', () => { const fixture = createComponent(AutosizeTextareaWithLongPlaceholder); fixture.detectChanges(); @@ -1735,8 +1735,8 @@ describe('MatInput with textarea autosize', () => { autosize.resizeToFitContent(true); - expect(textarea.clientHeight).toBeLessThan(heightWithLongPlaceholder, - 'Expected the textarea height to be shorter with a long placeholder.'); + expect(textarea.clientHeight).toBe(heightWithLongPlaceholder, + 'Expected the textarea height to be the same with a long placeholder.'); }); it('should work in a tab', () => { diff --git a/tools/public_api_guard/cdk/text-field.d.ts b/tools/public_api_guard/cdk/text-field.d.ts index 52ccc4947868..57d081ad1ffa 100644 --- a/tools/public_api_guard/cdk/text-field.d.ts +++ b/tools/public_api_guard/cdk/text-field.d.ts @@ -31,8 +31,6 @@ export declare class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDe set maxRows(value: number); get minRows(): number; set minRows(value: number); - get placeholder(): string; - set placeholder(value: string); constructor(_elementRef: ElementRef, _platform: Platform, _ngZone: NgZone, document?: any); _noopInputHandler(): void; @@ -46,7 +44,7 @@ export declare class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDe static ngAcceptInputType_enabled: BooleanInput; static ngAcceptInputType_maxRows: NumberInput; static ngAcceptInputType_minRows: NumberInput; - static ɵdir: i0.ɵɵDirectiveDefWithMeta; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; static ɵfac: i0.ɵɵFactoryDef; } diff --git a/yarn.lock b/yarn.lock index 699b10774e03..f2837217aa90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13,7 +13,7 @@ typescript "4.1.5" webpack-sources "2.2.0" -"@angular-devkit/core@12.0.0-next.4", "@angular-devkit/core@^12.0.0-next.4": +"@angular-devkit/core@12.0.0-next.4": version "12.0.0-next.4" resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-12.0.0-next.4.tgz#d12f5ea9195bf6d1c551a9a1808db2fdd93f0f48" integrity sha512-sMhcE1/wxLR6BcfpHnHUvqhZPYdzbI/O3uDO4+d9n/5OCpQ5ayw/rUzCtu5MFT957yfdaB9B6rSbJ40ftixevg== @@ -24,7 +24,18 @@ rxjs "6.6.6" source-map "0.7.3" -"@angular-devkit/schematics@12.0.0-next.4", "@angular-devkit/schematics@^12.0.0-next.4": +"@angular-devkit/core@12.0.0-next.5", "@angular-devkit/core@^12.0.0-next.4": + version "12.0.0-next.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-12.0.0-next.5.tgz#d553f35db6ccb1e1c75b67f6c478eb339f9a618b" + integrity sha512-Jo9aAhrnM4ZpUpW1y42O2FGZTPhLtW6KokGF1E6joBT4mj5JY9KXOIRh8nvvJ+gusiXsOFV4FW+gSJwAU26q4g== + dependencies: + ajv "6.12.6" + fast-json-stable-stringify "2.1.0" + magic-string "0.25.7" + rxjs "6.6.6" + source-map "0.7.3" + +"@angular-devkit/schematics@12.0.0-next.4": version "12.0.0-next.4" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-12.0.0-next.4.tgz#6d25334fada4cff06b1d28c05de244860d69f9d3" integrity sha512-xqfnuhBRjWbE3gqlL124YqwZiVGq2B6IbCk8lHEHqeQ38ArgaDsR8htEHSRWupKjBd6h2eT4Lx8URL7C1IpADA== @@ -33,6 +44,15 @@ ora "5.3.0" rxjs "6.6.6" +"@angular-devkit/schematics@^12.0.0-next.4": + version "12.0.0-next.5" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-12.0.0-next.5.tgz#28280be3ab4eb39e1112b81c1939b43f45cd87a3" + integrity sha512-rXFy/0Apvh1Tiw0iR/CvIUHwQSdEzj6jS6IxFJDrEeJffeBAUAPPnogcZ2fZetVpU9XsurmkkhkAh8QzhJnTgw== + dependencies: + "@angular-devkit/core" "12.0.0-next.5" + ora "5.4.0" + rxjs "6.6.6" + "@angular/animations@^12.0.0-next.5": version "12.0.0-next.5" resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-12.0.0-next.5.tgz#621d98140eca608144b7ffe8a8f93c875ca9da53" @@ -3033,6 +3053,15 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blakejs@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5" @@ -7601,6 +7630,11 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-upper-case@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" @@ -8524,6 +8558,14 @@ log-symbols@^4.0.0: dependencies: chalk "^4.0.0" +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + log4js@^4.0.0: version "4.5.1" resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5" @@ -9719,7 +9761,7 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" -ora@5.3.0, ora@^5.0.0, ora@^5.1.0: +ora@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== @@ -9733,6 +9775,21 @@ ora@5.3.0, ora@^5.0.0, ora@^5.1.0: strip-ansi "^6.0.0" wcwidth "^1.0.1" +ora@5.4.0, ora@^5.0.0, ora@^5.1.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.0.tgz#42eda4855835b9cd14d33864c97a3c95a3f56bf4" + integrity sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + ora@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"