Skip to content

Commit a86c547

Browse files
authored
perf(material/form-field): split DOM accesses into read and write (#31086)
In the outlined appearance we have to check the width of prefixes and suffixes and then write it to the floating label. These changes split it into `read` and `write` phases to reduce the amount of layout thrashing.
1 parent 90413f9 commit a86c547

File tree

1 file changed

+47
-30
lines changed

1 file changed

+47
-30
lines changed

src/material/form-field/form-field.ts

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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. */
110115
const 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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy