Skip to content

Commit

Permalink
fix(material/form-field): hiding a label after it has been (#29461)
Browse files Browse the repository at this point in the history
shown leaves a blank space

There is a method _shouldLabelFloat that determines if the label
should float. A check was added `_hasFloatingLabel` to see if a
floating label exists before deciding whether the label should
float. Examples were added at the end of the input-demo file,
where you can see inputs without labels (both fixed and dynamic).
Removing the solution also allows you to simulate the described
error. Unit tests were added to validate the solution.

Fixes #29401

(cherry picked from commit 13aef8c)
  • Loading branch information
jullierme authored and crisbeto committed Jul 22, 2024
1 parent 15238d2 commit c79ec26
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 3 deletions.
35 changes: 35 additions & 0 deletions src/dev-app/input/input-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,41 @@ <h4>Custom control</h4>
</mat-card-content>
</mat-card>

<mat-card class="demo-card demo-basic">
<mat-toolbar color="primary">Without label</mat-toolbar>
<mat-card-content>
<h4>Label removed</h4>
<p>
<mat-form-field appearance="outline">
@if (hasLabel$ | async){
<mat-label>My input</mat-label>
}
<input matInput type="text" />
</mat-form-field>
</p>
<p>
<mat-form-field>
@if (hasLabel$ | async){
<mat-label>My input</mat-label>
}
<input matInput type="text" />
</mat-form-field>
</p>
<h4>No defined label</h4>
<p>
<mat-form-field appearance="outline">
<input matInput type="text"/>
</mat-form-field>
</p>
<p>
<mat-form-field>
<input matInput type="text"/>
</mat-form-field>
</p>
</mat-card-content>
</mat-card>


<mat-card class="demo-card demo-basic">
<mat-card-content>
<button (click)="showHidden = !showHidden">Show/hide hidden form-field</button>
Expand Down
8 changes: 7 additions & 1 deletion src/dev-app/input/input-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {MatIconModule} from '@angular/material/icon';
import {MatTabsModule} from '@angular/material/tabs';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {BehaviorSubject} from 'rxjs';

let max = 5;

Expand Down Expand Up @@ -100,8 +101,13 @@ export class InputDemo {
fillAppearance: string;
outlineAppearance: string;

hasLabel$ = new BehaviorSubject(true);

constructor() {
setTimeout(() => this.delayedFormControl.setValue('hello'), 100);
setTimeout(() => {
this.delayedFormControl.setValue('hello');
this.hasLabel$.next(false);
}, 100);
}

addABunch(n: number) {
Expand Down
5 changes: 4 additions & 1 deletion src/material/form-field/form-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,10 @@ export class MatFormField

_hasFloatingLabel = computed(() => !!this._labelChild());

_shouldLabelFloat() {
_shouldLabelFloat(): boolean {
if (!this._hasFloatingLabel()) {
return false;
}
return this._control.shouldLabelFloat || this._shouldAlwaysFloat();
}

Expand Down
81 changes: 80 additions & 1 deletion src/material/input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ describe('MatMdcInput without forms', () => {
fixture.detectChanges();

expect(formField._control.empty).toBe(false);
expect(formField._shouldLabelFloat()).toBe(true);
// should not float label if there is no label
expect(formField._shouldLabelFloat()).toBe(false);
}));

it('should not be empty when the value set before view init', fakeAsync(() => {
Expand Down Expand Up @@ -1531,6 +1532,62 @@ describe('MatFormField default options', () => {
).toBe(true);
});
});
describe('MatFormField without label', () => {
it('should not float the label when no label is defined.', () => {
let fixture = createComponent(MatInputWithoutDefinedLabel);
fixture.detectChanges();

const inputEl = fixture.debugElement.query(By.css('input'))!;
const formField = fixture.debugElement.query(By.directive(MatFormField))!
.componentInstance as MatFormField;

// Update the value of the input and set focus.
inputEl.nativeElement.value = 'Text';
fixture.detectChanges();

// should not float label if there is no label
expect(formField._shouldLabelFloat()).toBe(false);
});

it('should not float the label when the label is removed after it has been shown', () => {
let fixture = createComponent(MatInputWithCondictionalLabel);
fixture.detectChanges();
const inputEl = fixture.debugElement.query(By.css('input'))!;
const formField = fixture.debugElement.query(By.directive(MatFormField))!
.componentInstance as MatFormField;

// initially, label is present
expect(fixture.nativeElement.querySelector('label')).not.toBeNull();

// removing label after it has been shown
fixture.componentInstance.hasLabel = false;
inputEl.nativeElement.value = 'Text';
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();

// now expected to not have a label
expect(fixture.nativeElement.querySelector('label')).toBeNull();
// should not float label since there is no label
expect(formField._shouldLabelFloat()).toBe(false);
});

it('should float the label when the label is not removed', () => {
let fixture = createComponent(MatInputWithCondictionalLabel);
fixture.detectChanges();
const inputEl = fixture.debugElement.query(By.css('input'))!;
const formField = fixture.debugElement.query(By.directive(MatFormField))!
.componentInstance as MatFormField;

inputEl.nativeElement.value = 'Text';
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();

// Expected to have a label
expect(fixture.nativeElement.querySelector('label')).not.toBeNull();
// should float label since there is a label
expect(formField._shouldLabelFloat()).toBe(true);
});
});

function configureTestingModule(
component: Type<any>,
Expand Down Expand Up @@ -1787,6 +1844,28 @@ class MatInputWithDynamicLabel {
shouldFloat: 'always' | 'auto' = 'always';
}

@Component({
template: `
<mat-form-field>
<input matInput placeholder="Label">
</mat-form-field>
`,
})
class MatInputWithoutDefinedLabel {}

@Component({
template: `
<mat-form-field>
@if (hasLabel) {
<mat-label>Label</mat-label>
}
<input matInput>
</mat-form-field>`,
})
class MatInputWithCondictionalLabel {
hasLabel = true;
}

@Component({
template: `
<mat-form-field>
Expand Down

0 comments on commit c79ec26

Please sign in to comment.