From d9f90e3151d947a177ad849a7e992a527d9006bf Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sun, 20 Jul 2025 09:07:18 +0200 Subject: [PATCH] fix(material/form-field): ensure that focused classes are in sync This is something that showed up in some internal tests a while ago. Because we set `mat-focused` through a host binding while `mdc-text-field--focused` is set through direct DOM manipulation, nothing guarantees that they'll be in sync and in some internal tests they aren't. These changes sync both of them up from the same place. --- src/material/form-field/form-field.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index c737e5e2eed9..241d22409a60 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -160,7 +160,6 @@ interface MatFormFieldControl extends _MatFormFieldControl {} '[class.mat-form-field-appearance-fill]': 'appearance == "fill"', '[class.mat-form-field-appearance-outline]': 'appearance == "outline"', '[class.mat-form-field-hide-placeholder]': '_hasFloatingLabel() && !_shouldLabelFloat()', - '[class.mat-focused]': '_control.focused', '[class.mat-primary]': 'color !== "accent" && color !== "warn"', '[class.mat-accent]': 'color === "accent"', '[class.mat-warn]': 'color === "warn"', @@ -340,6 +339,7 @@ export class MatFormField private _stateChanges: Subscription | undefined; private _valueChanges: Subscription | undefined; private _describedByChanges: Subscription | undefined; + private _outlineLabelOffsetResizeObserver: ResizeObserver | null = null; protected readonly _animationsDisabled = _animationsDisabled(); constructor(...args: unknown[]); @@ -544,27 +544,25 @@ export class MatFormField } private _updateFocusState() { + const controlFocused = this._control.focused; + // Usually the MDC foundation would call "activateFocus" and "deactivateFocus" whenever // certain DOM events are emitted. This is not possible in our implementation of the // form field because we support abstract form field controls which are not necessarily // of type input, nor do we have a reference to a native form field control element. Instead // we handle the focus by checking if the abstract form field control focused state changes. - if (this._control.focused && !this._isFocused) { + if (controlFocused && !this._isFocused) { this._isFocused = true; this._lineRipple?.activate(); - } else if (!this._control.focused && (this._isFocused || this._isFocused === null)) { + } else if (!controlFocused && (this._isFocused || this._isFocused === null)) { this._isFocused = false; this._lineRipple?.deactivate(); } - this._textField?.nativeElement.classList.toggle( - 'mdc-text-field--focused', - this._control.focused, - ); + this._elementRef.nativeElement.classList.toggle('mat-focused', controlFocused); + this._textField?.nativeElement.classList.toggle('mdc-text-field--focused', controlFocused); } - private _outlineLabelOffsetResizeObserver: ResizeObserver | null = null; - /** * The floating label in the docked state needs to account for prefixes. The horizontal offset * is calculated whenever the appearance changes to `outline`, the prefixes change, or when the