@@ -106,6 +106,11 @@ export const MAT_FORM_FIELD_DEFAULT_OPTIONS = new InjectionToken<MatFormFieldDef
106
106
'MAT_FORM_FIELD_DEFAULT_OPTIONS' ,
107
107
) ;
108
108
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
+
109
114
/** Default appearance used by the form field. */
110
115
const DEFAULT_APPEARANCE : MatFormFieldAppearance = 'fill' ;
111
116
@@ -567,27 +572,27 @@ export class MatFormField
567
572
* trigger the label offset update.
568
573
*/
569
574
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 ;
578
580
}
579
581
580
582
// Setup a resize observer to monitor changes to the size of the prefix / suffix and
581
583
// 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
+ }
587
591
}
588
- } else {
589
- this . _outlineLabelOffsetResizeObserver ?. disconnect ( ) ;
590
- }
592
+
593
+ return this . _getOutlinedLabelOffset ( ) ;
594
+ } ,
595
+ write : labelStyles => this . _writeOutlinedLabelStyles ( labelStyles ( ) ) ,
591
596
} ) ;
592
597
}
593
598
@@ -740,30 +745,28 @@ export class MatFormField
740
745
}
741
746
742
747
/**
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
744
749
* appearance, the notched-outline and label are not relative to the infix container because
745
750
* the outline intends to surround prefixes, suffixes and the infix. This means that the
746
751
* floating label by default overlaps prefixes in the docked state. To avoid this, we need to
747
752
* horizontally offset the label by the width of the prefix container. The MDC text-field does
748
753
* not need to do this because they use a fixed width for prefixes. Hence, they can simply
749
754
* incorporate the horizontal offset into their default text-field styles.
750
755
*/
751
- private _updateOutlineLabelOffset ( ) {
756
+ private _getOutlinedLabelOffset ( ) : OutlinedLabelStyles {
752
757
const dir = this . _dir . valueSignal ( ) ;
753
758
if ( ! this . _hasOutline ( ) || ! this . _floatingLabel ) {
754
- return ;
759
+ return null ;
755
760
}
756
- const floatingLabel = this . _floatingLabel . element ;
757
761
// If no prefix is displayed, reset the outline label offset from potential
758
762
// 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 ] ;
762
765
}
763
766
// If the form field is not attached to the DOM yet (e.g. in a tab), we defer
764
767
// the label offset update until the zone stabilizes.
765
768
if ( ! this . _isAttachedToDom ( ) ) {
766
- return ;
769
+ return null ;
767
770
}
768
771
const iconPrefixContainer = this . _iconPrefixContainer ?. nativeElement ;
769
772
const textPrefixContainer = this . _textPrefixContainer ?. nativeElement ;
@@ -783,19 +786,33 @@ export class MatFormField
783
786
// Update the translateX of the floating label to account for the prefix container,
784
787
// but allow the CSS to override this setting via a CSS variable when the label is
785
788
// 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 } ))` ;
790
792
791
793
// Prevent the label from overlapping the suffix when in resting position.
792
- const prefixAndSuffixWidth =
794
+ const notchedOutlineWidth =
793
795
iconPrefixContainerWidth +
794
796
textPrefixContainerWidth +
795
797
iconSuffixContainerWidth +
796
798
textSuffixContainerWidth ;
797
799
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
+ }
799
816
}
800
817
801
818
/** Checks whether the form field is attached to the DOM. */
0 commit comments