5
5
* Use of this source code is governed by an MIT-style license that can be
6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
+ import { _IdGenerator } from '@angular/cdk/a11y' ;
8
9
import { Directionality } from '@angular/cdk/bidi' ;
9
10
import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
10
11
import { Platform } from '@angular/cdk/platform' ;
@@ -27,16 +28,16 @@ import {
27
28
QueryList ,
28
29
ViewChild ,
29
30
ViewEncapsulation ,
30
- afterRender ,
31
+ afterRenderEffect ,
31
32
computed ,
32
33
contentChild ,
33
34
inject ,
35
+ signal ,
34
36
} from '@angular/core' ;
35
37
import { AbstractControlDirective , ValidatorFn } from '@angular/forms' ;
36
- import { _animationsDisabled , ThemePalette } from '../core' ;
37
- import { _IdGenerator } from '@angular/cdk/a11y' ;
38
38
import { Subject , Subscription , merge } from 'rxjs' ;
39
- import { map , pairwise , takeUntil , filter , startWith } from 'rxjs/operators' ;
39
+ import { filter , map , pairwise , startWith , takeUntil } from 'rxjs/operators' ;
40
+ import { ThemePalette , _animationsDisabled } from '../core' ;
40
41
import { MAT_ERROR , MatError } from './directives/error' ;
41
42
import {
42
43
FLOATING_LABEL_PARENT ,
@@ -250,10 +251,9 @@ export class MatFormField
250
251
/** The form field appearance style. */
251
252
@Input ( )
252
253
get appearance ( ) : MatFormFieldAppearance {
253
- return this . _appearance ;
254
+ return this . _appearanceSignal ( ) ;
254
255
}
255
256
set appearance ( value : MatFormFieldAppearance ) {
256
- const oldValue = this . _appearance ;
257
257
const newAppearance = value || this . _defaults ?. appearance || DEFAULT_APPEARANCE ;
258
258
if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
259
259
if ( newAppearance !== 'fill' && newAppearance !== 'outline' ) {
@@ -262,15 +262,9 @@ export class MatFormField
262
262
) ;
263
263
}
264
264
}
265
- this . _appearance = newAppearance ;
266
- if ( this . _appearance === 'outline' && this . _appearance !== oldValue ) {
267
- // If the appearance has been switched to `outline`, the label offset needs to be updated.
268
- // The update can happen once the view has been re-checked, but not immediately because
269
- // the view has not been updated and the notched-outline floating label is not present.
270
- this . _needsOutlineLabelOffsetUpdate = true ;
271
- }
265
+ this . _appearanceSignal . set ( newAppearance ) ;
272
266
}
273
- private _appearance : MatFormFieldAppearance = DEFAULT_APPEARANCE ;
267
+ private _appearanceSignal = signal ( DEFAULT_APPEARANCE ) ;
274
268
275
269
/**
276
270
* Whether the form field should reserve space for one line of hint/error text (default)
@@ -319,7 +313,6 @@ export class MatFormField
319
313
private _destroyed = new Subject < void > ( ) ;
320
314
private _isFocused : boolean | null = null ;
321
315
private _explicitFormFieldControl : MatFormFieldControl < any > ;
322
- private _needsOutlineLabelOffsetUpdate = false ;
323
316
private _previousControl : MatFormFieldControl < unknown > | null = null ;
324
317
private _previousControlValidatorFn : ValidatorFn | null = null ;
325
318
private _stateChanges : Subscription | undefined ;
@@ -399,6 +392,7 @@ export class MatFormField
399
392
}
400
393
401
394
ngOnDestroy ( ) {
395
+ this . _outlineLabelOffsetResizeObserver . disconnect ( ) ;
402
396
this . _stateChanges ?. unsubscribe ( ) ;
403
397
this . _valueChanges ?. unsubscribe ( ) ;
404
398
this . _describedByChanges ?. unsubscribe ( ) ;
@@ -546,6 +540,10 @@ export class MatFormField
546
540
) ;
547
541
}
548
542
543
+ private _outlineLabelOffsetResizeObserver = new ResizeObserver ( ( ) =>
544
+ this . _updateOutlineLabelOffset ( ) ,
545
+ ) ;
546
+
549
547
/**
550
548
* The floating label in the docked state needs to account for prefixes. The horizontal offset
551
549
* is calculated whenever the appearance changes to `outline`, the prefixes change, or when the
@@ -554,26 +552,33 @@ export class MatFormField
554
552
*/
555
553
private _initializeOutlineLabelOffsetSubscriptions ( ) {
556
554
// Whenever the prefix changes, schedule an update of the label offset.
557
- // TODO(mmalerba): Use ResizeObserver to better support dynamically changing prefix content.
558
- this . _prefixChildren . changes . subscribe ( ( ) => ( this . _needsOutlineLabelOffsetUpdate = true ) ) ;
559
-
560
555
// TODO(mmalerba): Split this into separate `afterRender` calls using the `EarlyRead` and
561
556
// `Write` phases.
562
- afterRender (
557
+ afterRenderEffect (
563
558
( ) => {
564
- if ( this . _needsOutlineLabelOffsetUpdate ) {
565
- this . _needsOutlineLabelOffsetUpdate = false ;
559
+ if ( this . _appearanceSignal ( ) === 'outline' ) {
560
+ // Trigger effect on directionality changes.
561
+ this . _dir . valueSignal ( ) ;
562
+ const prefixSuffixEls = [
563
+ this . _textPrefixContainer ,
564
+ this . _iconPrefixContainer ,
565
+ this . _textSuffixContainer ,
566
+ this . _iconSuffixContainer ,
567
+ ]
568
+ . filter ( e => e != null )
569
+ . map ( e => e . nativeElement ) ;
570
+ for ( const el of prefixSuffixEls ) {
571
+ this . _outlineLabelOffsetResizeObserver . observe ( el , { box : 'border-box' } ) ;
572
+ }
566
573
this . _updateOutlineLabelOffset ( ) ;
574
+ } else {
575
+ this . _outlineLabelOffsetResizeObserver . disconnect ( ) ;
567
576
}
568
577
} ,
569
578
{
570
579
injector : this . _injector ,
571
580
} ,
572
581
) ;
573
-
574
- this . _dir . change
575
- . pipe ( takeUntil ( this . _destroyed ) )
576
- . subscribe ( ( ) => ( this . _needsOutlineLabelOffsetUpdate = true ) ) ;
577
582
}
578
583
579
584
/** Whether the floating label should always float or not. */
@@ -732,7 +737,6 @@ export class MatFormField
732
737
// If the form field is not attached to the DOM yet (e.g. in a tab), we defer
733
738
// the label offset update until the zone stabilizes.
734
739
if ( ! this . _isAttachedToDom ( ) ) {
735
- this . _needsOutlineLabelOffsetUpdate = true ;
736
740
return ;
737
741
}
738
742
const iconPrefixContainer = this . _iconPrefixContainer ?. nativeElement ;
0 commit comments