@@ -106,6 +106,11 @@ export const MAT_FORM_FIELD_DEFAULT_OPTIONS = new InjectionToken<MatFormFieldDef
106106 'MAT_FORM_FIELD_DEFAULT_OPTIONS' ,
107107) ;
108108
109+ /** Styles that are to be applied to the label elements in the outlined appearance. */
110+ type OutlinedLabelStyles =
111+ | [ floatingLabelTransform : string , notchedOutlineWidth : number | null ]
112+ | null ;
113+
109114/** Default appearance used by the form field. */
110115const DEFAULT_APPEARANCE : MatFormFieldAppearance = 'fill' ;
111116
@@ -567,27 +572,27 @@ export class MatFormField
567572 * trigger the label offset update.
568573 */
569574 private _syncOutlineLabelOffset ( ) {
570- // Whenever the prefix changes, schedule an update of the label offset.
571- // TODO(mmalerba): Split this into separate `afterRender` calls using the `EarlyRead` and
572- // `Write` phases.
573- afterRenderEffect ( ( ) => {
574- if ( this . _appearanceSignal ( ) === 'outline' ) {
575- this . _updateOutlineLabelOffset ( ) ;
576- if ( ! globalThis . ResizeObserver ) {
577- return ;
575+ afterRenderEffect ( {
576+ earlyRead : ( ) => {
577+ if ( this . _appearanceSignal ( ) !== 'outline' ) {
578+ this . _outlineLabelOffsetResizeObserver ?. disconnect ( ) ;
579+ return null ;
578580 }
579581
580582 // Setup a resize observer to monitor changes to the size of the prefix / suffix and
581583 // readjust the label offset.
582- this . _outlineLabelOffsetResizeObserver ||= new globalThis . ResizeObserver ( ( ) =>
583- this . _updateOutlineLabelOffset ( ) ,
584- ) ;
585- for ( const el of this . _prefixSuffixContainers ( ) ) {
586- this . _outlineLabelOffsetResizeObserver . observe ( el , { box : 'border-box' } ) ;
584+ if ( globalThis . ResizeObserver ) {
585+ this . _outlineLabelOffsetResizeObserver ||= new globalThis . ResizeObserver ( ( ) => {
586+ this . _writeOutlinedLabelStyles ( this . _getOutlinedLabelOffset ( ) ) ;
587+ } ) ;
588+ for ( const el of this . _prefixSuffixContainers ( ) ) {
589+ this . _outlineLabelOffsetResizeObserver . observe ( el , { box : 'border-box' } ) ;
590+ }
587591 }
588- } else {
589- this . _outlineLabelOffsetResizeObserver ?. disconnect ( ) ;
590- }
592+
593+ return this . _getOutlinedLabelOffset ( ) ;
594+ } ,
595+ write : labelStyles => this . _writeOutlinedLabelStyles ( labelStyles ( ) ) ,
591596 } ) ;
592597 }
593598
@@ -740,30 +745,28 @@ export class MatFormField
740745 }
741746
742747 /**
743- * Updates the horizontal offset of the label in the outline appearance. In the outline
748+ * Calculates the horizontal offset of the label in the outline appearance. In the outline
744749 * appearance, the notched-outline and label are not relative to the infix container because
745750 * the outline intends to surround prefixes, suffixes and the infix. This means that the
746751 * floating label by default overlaps prefixes in the docked state. To avoid this, we need to
747752 * horizontally offset the label by the width of the prefix container. The MDC text-field does
748753 * not need to do this because they use a fixed width for prefixes. Hence, they can simply
749754 * incorporate the horizontal offset into their default text-field styles.
750755 */
751- private _updateOutlineLabelOffset ( ) {
756+ private _getOutlinedLabelOffset ( ) : OutlinedLabelStyles {
752757 const dir = this . _dir . valueSignal ( ) ;
753758 if ( ! this . _hasOutline ( ) || ! this . _floatingLabel ) {
754- return ;
759+ return null ;
755760 }
756- const floatingLabel = this . _floatingLabel . element ;
757761 // If no prefix is displayed, reset the outline label offset from potential
758762 // previous label offset updates.
759- if ( ! ( this . _iconPrefixContainer || this . _textPrefixContainer ) ) {
760- floatingLabel . style . transform = '' ;
761- return ;
763+ if ( ! this . _iconPrefixContainer && ! this . _textPrefixContainer ) {
764+ return [ '' , null ] ;
762765 }
763766 // If the form field is not attached to the DOM yet (e.g. in a tab), we defer
764767 // the label offset update until the zone stabilizes.
765768 if ( ! this . _isAttachedToDom ( ) ) {
766- return ;
769+ return null ;
767770 }
768771 const iconPrefixContainer = this . _iconPrefixContainer ?. nativeElement ;
769772 const textPrefixContainer = this . _textPrefixContainer ?. nativeElement ;
@@ -783,19 +786,33 @@ export class MatFormField
783786 // Update the translateX of the floating label to account for the prefix container,
784787 // but allow the CSS to override this setting via a CSS variable when the label is
785788 // floating.
786- floatingLabel . style . transform = `var(
787- --mat-mdc-form-field-label-transform,
788- ${ FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM } translateX(${ labelHorizontalOffset } )
789- )` ;
789+ const floatingLabelTransform =
790+ 'var(--mat-mdc-form-field-label-transform, ' +
791+ `${ FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM } translateX(${ labelHorizontalOffset } ))` ;
790792
791793 // Prevent the label from overlapping the suffix when in resting position.
792- const prefixAndSuffixWidth =
794+ const notchedOutlineWidth =
793795 iconPrefixContainerWidth +
794796 textPrefixContainerWidth +
795797 iconSuffixContainerWidth +
796798 textSuffixContainerWidth ;
797799
798- this . _notchedOutline ?. _setMaxWidth ( prefixAndSuffixWidth ) ;
800+ return [ floatingLabelTransform , notchedOutlineWidth ] ;
801+ }
802+
803+ /** Writes the styles produced by `_getOutlineLabelOffset` synchronously to the DOM. */
804+ private _writeOutlinedLabelStyles ( styles : OutlinedLabelStyles ) : void {
805+ if ( styles !== null ) {
806+ const [ floatingLabelTransform , notchedOutlineWidth ] = styles ;
807+
808+ if ( this . _floatingLabel ) {
809+ this . _floatingLabel . element . style . transform = floatingLabelTransform ;
810+ }
811+
812+ if ( notchedOutlineWidth !== null ) {
813+ this . _notchedOutline ?. _setMaxWidth ( notchedOutlineWidth ) ;
814+ }
815+ }
799816 }
800817
801818 /** Checks whether the form field is attached to the DOM. */
0 commit comments