Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions src/material/chips/chip-grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -899,9 +899,9 @@ describe('MatChipGrid', () => {
expect(errorTestComponent.formControl.untouched)
.withContext('Expected untouched form control')
.toBe(true);
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected no error message')
.toBe(0);
.toBe(1);
expect(chipGridEl.getAttribute('aria-invalid'))
.withContext('Expected aria-invalid to be set to "false".')
.toBe('false');
Expand All @@ -911,9 +911,9 @@ describe('MatChipGrid', () => {
expect(errorTestComponent.formControl.invalid)
.withContext('Expected form control to be invalid')
.toBe(true);
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected no error message')
.toBe(0);
.toBe(1);

errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();
Expand All @@ -922,9 +922,9 @@ describe('MatChipGrid', () => {
expect(containerEl.classList)
.withContext('Expected container to have the invalid CSS class.')
.toContain('mat-form-field-invalid');
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected one error message to have been rendered.')
.toBe(1);
.toBe(0);
expect(chipGridEl.getAttribute('aria-invalid'))
.withContext('Expected aria-invalid to be set to "true".')
.toBe('true');
Expand All @@ -937,9 +937,9 @@ describe('MatChipGrid', () => {
expect(errorTestComponent.formControl.invalid)
.withContext('Expected form control to be invalid')
.toBe(true);
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected no error message')
.toBe(0);
.toBe(1);

dispatchFakeEvent(fixture.debugElement.query(By.css('form'))!.nativeElement, 'submit');
flush();
Expand All @@ -952,9 +952,9 @@ describe('MatChipGrid', () => {
expect(containerEl.classList)
.withContext('Expected container to have the invalid CSS class.')
.toContain('mat-form-field-invalid');
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected one error message to have been rendered.')
.toBe(1);
.toBe(0);
expect(chipGridEl.getAttribute('aria-invalid'))
.withContext('Expected aria-invalid to be set to "true".')
.toBe('true');
Expand All @@ -971,12 +971,12 @@ describe('MatChipGrid', () => {
expect(containerEl.classList)
.withContext('Expected container to have the invalid CSS class.')
.toContain('mat-form-field-invalid');
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected one error message to have been rendered.')
.toBe(1);
expect(containerEl.querySelectorAll('mat-hint').length)
.withContext('Expected no hints to be shown.')
.toBe(0);
expect(containerEl.querySelectorAll('.mat-mdc-form-field-hint-wrapper--hidden').length)
.withContext('Expected no hints to be shown.')
.toBe(1);

errorTestComponent.formControl.setValue('something');
flush();
Expand All @@ -987,9 +987,9 @@ describe('MatChipGrid', () => {
'mat-form-field-invalid',
'Expected container not to have the invalid class when valid.',
);
expect(containerEl.querySelectorAll('mat-error').length)
expect(containerEl.querySelectorAll('.mat-mdc-form-field-error-wrapper--hidden').length)
.withContext('Expected no error messages when the input is valid.')
.toBe(0);
.toBe(1);
expect(containerEl.querySelectorAll('mat-hint').length)
.withContext('Expected one hint to be shown once the input is valid.')
.toBe(1);
Expand Down
37 changes: 17 additions & 20 deletions src/material/form-field/form-field.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,25 +99,22 @@
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
>
@switch (_getDisplayedMessages()) {
@case ('error') {
<div
class="mat-mdc-form-field-error-wrapper"
[@transitionMessages]="_subscriptAnimationState"
>
<ng-content select="mat-error, [matError]"></ng-content>
</div>
}

@case ('hint') {
<div class="mat-mdc-form-field-hint-wrapper" [@transitionMessages]="_subscriptAnimationState">
@if (hintLabel) {
<mat-hint [id]="_hintLabelId">{{hintLabel}}</mat-hint>
}
<ng-content select="mat-hint:not([align='end'])"></ng-content>
<div class="mat-mdc-form-field-hint-spacer"></div>
<ng-content select="mat-hint[align='end']"></ng-content>
</div>
<div
class="mat-mdc-form-field-error-wrapper mat-mdc-form-field-error-wrapper--hidden"
[@transitionMessages]="_subscriptAnimationState"
>
<ng-content select="mat-error, [matError]"></ng-content>
</div>

<div
class="mat-mdc-form-field-hint-wrapper mat-mdc-form-field-hint-wrapper--hidden"
[@transitionMessages]="_subscriptAnimationState"
>
@if (hintLabel) {
<mat-hint [id]="_hintLabelId">{{hintLabel}}</mat-hint>
}
}
<ng-content select="mat-hint:not([align='end'])"></ng-content>
<div class="mat-mdc-form-field-hint-spacer"></div>
<ng-content select="mat-hint[align='end']"></ng-content>
</div>
</div>
8 changes: 8 additions & 0 deletions src/material/form-field/form-field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,11 @@ $_icon-prefix-infix-padding: 4px;
.mdc-notched-outline--upgraded .mdc-floating-label--float-above {
max-width: calc(100% * 4 / 3 + 1px);
}

.mat-mdc-form-field-error-wrapper--hidden {
visibility: hidden;
}

.mat-mdc-form-field-hint-wrapper--hidden {
visibility: hidden;
}
54 changes: 54 additions & 0 deletions src/material/form-field/form-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ export class MatFormField
this._stateChanges = control.stateChanges.subscribe(() => {
this._updateFocusState();
this._syncDescribedByIds();
this._showOrHideSubscript();
this._changeDetectorRef.markForCheck();
});

Expand Down Expand Up @@ -478,17 +479,20 @@ export class MatFormField
// Re-validate when the number of hints changes.
this._hintChildren.changes.subscribe(() => {
this._processHints();
this._showOrHideSubscript();
this._changeDetectorRef.markForCheck();
});

// Update the aria-described by when the number of errors changes.
this._errorChildren.changes.subscribe(() => {
this._syncDescribedByIds();
this._showOrHideSubscript();
this._changeDetectorRef.markForCheck();
});

// Initial mat-hint validation and subscript describedByIds sync.
this._validateHints();
this._showOrHideSubscript();
this._syncDescribedByIds();
}

Expand Down Expand Up @@ -658,6 +662,7 @@ export class MatFormField
}

if (this._getDisplayedMessages() === 'hint') {
this._showOrHideSubscript();
const startHint = this._hintChildren
? this._hintChildren.find(hint => hint.align === 'start')
: null;
Expand All @@ -682,6 +687,55 @@ export class MatFormField
}
}

/**
* Solves https://github.com/angular/components/issues/29616
* Issues with certain browser and screen reader pairings not able to announce mat-error
* when it's added to the DOM rather than changing the visibility of the hint/error wrappers.
* Changing visibility instead of adding the div wrappers works for all browsers and sreen
* readers.
*
* If there is an 'error' or 'hint' message being returned, remove visibility: hidden
* style class and show error or hint section of code. If no 'error' or 'hint' messages are
* being returned and no error children showing in query list, add visibility: hidden
* style class back to error wrapper.
*/
private _showOrHideSubscript() {
switch (this._getDisplayedMessages()) {
case 'error': {
console.log(this._elementRef.nativeElement.children[1].children[0].classList);
this._elementRef.nativeElement.children[1].children[0].classList.remove(
'mat-mdc-form-field-error-wrapper--hidden',
);
// Can't show error message and hint at same time
this._elementRef.nativeElement.children[1].children[0].classList.add(
'mat-mdc-form-field-hint-wrapper--hidden',
);
console.log(this._elementRef.nativeElement.children[1].children[0].classList);
break;
}
case 'hint': {
console.log(this._elementRef.nativeElement.children[1].children[1].classList);
this._elementRef.nativeElement.children[1].children[1].classList.remove(
'mat-mdc-form-field-hint-wrapper--hidden',
);
console.log(this._elementRef.nativeElement.children[1].children[1].classList);
break;
}
}

if (!this._errorChildren || this._errorChildren.length === 0 || !this._control.errorState) {
this._elementRef.nativeElement.children[1].children[0].classList.add(
'mat-mdc-form-field-error-wrapper--hidden',
);
}

if (!this._hintChildren) {
this._elementRef.nativeElement.children[1].children[1].classList.add(
'mat-mdc-form-field-hint-wrapper--hidden',
);
}
}

/**
* Updates the horizontal offset of the label in the outline appearance. In the outline
* appearance, the notched-outline and label are not relative to the infix container because
Expand Down
Loading
Loading