From ad897c49bbe7cf57619e6edfb9317e14fb7ac7be Mon Sep 17 00:00:00 2001 From: Karan Mistry Date: Mon, 14 Apr 2025 19:13:53 +0530 Subject: [PATCH] fix(material/form-field): add hasFloatingLabel input and update classes if mat-label is added and removed dynamically Currently, when `mat-Label` is added dynamically initially its not visible in DOM, this fix will add/remove classes for the same. Fixes #29939 --- goldens/material/form-field/index.api.md | 2 + goldens/material/input/index.api.md | 1 + .../form-field/directives/notched-outline.ts | 29 ++++++++++++-- src/material/form-field/form-field.html | 19 +++++---- src/material/input/input.spec.ts | 40 +++++++++++++++++++ 5 files changed, 79 insertions(+), 12 deletions(-) diff --git a/goldens/material/form-field/index.api.md b/goldens/material/form-field/index.api.md index a9ef59692e37..e2e4e6e7b429 100644 --- a/goldens/material/form-field/index.api.md +++ b/goldens/material/form-field/index.api.md @@ -16,8 +16,10 @@ import * as i2 from '@angular/cdk/observers'; import { InjectionToken } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Observable } from 'rxjs'; +import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { QueryList } from '@angular/core'; +import { SimpleChanges } from '@angular/core'; // @public export type FloatLabelType = 'always' | 'auto'; diff --git a/goldens/material/input/index.api.md b/goldens/material/input/index.api.md index ebebf92f5181..3ba480b29f37 100644 --- a/goldens/material/input/index.api.md +++ b/goldens/material/input/index.api.md @@ -25,6 +25,7 @@ import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { Platform } from '@angular/cdk/platform'; import { QueryList } from '@angular/core'; +import { SimpleChanges } from '@angular/core'; import { Subject } from 'rxjs'; import { WritableSignal } from '@angular/core'; diff --git a/src/material/form-field/directives/notched-outline.ts b/src/material/form-field/directives/notched-outline.ts index 6400a2ae6b70..60a05f5ce24f 100644 --- a/src/material/form-field/directives/notched-outline.ts +++ b/src/material/form-field/directives/notched-outline.ts @@ -13,6 +13,8 @@ import { ElementRef, Input, NgZone, + OnChanges, + SimpleChanges, ViewChild, ViewEncapsulation, inject, @@ -36,22 +38,30 @@ import { changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) -export class MatFormFieldNotchedOutline implements AfterViewInit { +export class MatFormFieldNotchedOutline implements AfterViewInit, OnChanges { private _elementRef = inject>(ElementRef); private _ngZone = inject(NgZone); /** Whether the notch should be opened. */ @Input('matFormFieldNotchedOutlineOpen') open: boolean = false; + /** Whether the floating label is present. */ + @Input('matFormFieldHasFloatingLabel') hasFloatingLabel: boolean = false; + @ViewChild('notch') _notch: ElementRef; + /** Gets the HTML element for the floating label. */ + get element(): HTMLElement { + return this._elementRef.nativeElement; + } + constructor(...args: unknown[]); constructor() {} ngAfterViewInit(): void { - const label = this._elementRef.nativeElement.querySelector('.mdc-floating-label'); + const label = this.element.querySelector('.mdc-floating-label'); if (label) { - this._elementRef.nativeElement.classList.add('mdc-notched-outline--upgraded'); + this.element.classList.add('mdc-notched-outline--upgraded'); if (typeof requestAnimationFrame === 'function') { label.style.transitionDuration = '0s'; @@ -60,7 +70,18 @@ export class MatFormFieldNotchedOutline implements AfterViewInit { }); } } else { - this._elementRef.nativeElement.classList.add('mdc-notched-outline--no-label'); + this.element.classList.add('mdc-notched-outline--no-label'); + } + } + + ngOnChanges(changes: SimpleChanges) { + if ( + changes['hasFloatingLabel'] && + this.hasFloatingLabel && + this.element.classList.contains('mdc-notched-outline--no-label') + ) { + this.element.classList.add('mdc-notched-outline--upgraded'); + this.element.classList.remove('mdc-notched-outline--no-label'); } } diff --git a/src/material/form-field/form-field.html b/src/material/form-field/form-field.html index 2697be0d764c..e3ea9a9a1f03 100644 --- a/src/material/form-field/form-field.html +++ b/src/material/form-field/form-field.html @@ -50,7 +50,10 @@ }
@if (_hasOutline()) { -
+
@if (!_forceDisplayInfixLabel()) { } @@ -96,9 +99,8 @@
+ class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align" + [class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"> @let subscriptMessageType = _getSubscriptMessageType(); -
+
@switch (subscriptMessageType) { @case ('error') { diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 4b29f4f3a61f..667b0667e8dc 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -33,6 +33,7 @@ import { import {MatIconModule} from '../icon'; import {By} from '@angular/platform-browser'; import {MAT_INPUT_VALUE_ACCESSOR, MatInput, MatInputModule} from './index'; +import {MatFormFieldNotchedOutline} from '../form-field/directives/notched-outline'; describe('MatMdcInput without forms', () => { it('should default to floating labels', fakeAsync(() => { @@ -607,6 +608,29 @@ describe('MatMdcInput without forms', () => { expect(input.getAttribute('aria-describedby')).toBe(`initial ${hintId}`); })); + it('should show outline label correctly based on initial condition to false', fakeAsync(() => { + const fixture = createComponent(MatInputOutlineWithConditionalLabel); + fixture.detectChanges(); + tick(16); + + const notchedOutline: HTMLElement = fixture.debugElement.query( + By.directive(MatFormFieldNotchedOutline), + ).nativeElement; + + console.log('notchedOutline', notchedOutline.classList); + + expect(notchedOutline.classList).toContain('mdc-notched-outline--no-label'); + expect(notchedOutline.classList).not.toContain('mdc-notched-outline--upgraded'); + + fixture.componentInstance.showLabel = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + tick(16); + + expect(notchedOutline.classList).not.toContain('mdc-notched-outline--no-label'); + expect(notchedOutline.classList).toContain('mdc-notched-outline--upgraded'); + })); + it('supports user binding to aria-describedby', fakeAsync(() => { let fixture = createComponent(MatInputWithSubscriptAndAriaDescribedBy); @@ -2178,6 +2202,22 @@ class MatInputWithAppearance { appearance: MatFormFieldAppearance; } +@Component({ + template: ` + + @if(showLabel) { + My Label + } + + + `, + standalone: false, +}) +class MatInputOutlineWithConditionalLabel { + @ViewChild(MatFormField) formField: MatFormField; + showLabel: boolean = false; +} + @Component({ template: `