import * as i0 from '@angular/core';
import { Directive, InjectionToken, Attribute, Input, inject, NgZone, Component, ChangeDetectionStrategy, ViewEncapsulation, ViewChild, Optional, Inject, ContentChild, ContentChildren, NgModule } from '@angular/core';
import * as i1 from '@angular/cdk/bidi';
import * as i2 from '@angular/cdk/platform';
import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations';
import { Subscription, Subject, merge } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { SharedResizeObserver } from '@angular/cdk/observers/private';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { trigger, state, style, transition, animate } from '@angular/animations';
import * as i3 from '@angular/common';
import { DOCUMENT, CommonModule } from '@angular/common';
import { ObserversModule } from '@angular/cdk/observers';
import { MatCommonModule } from '@angular/material/core';
/** The floating label for a `mat-form-field`. */
class MatLabel {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatLabel, selector: "mat-label", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatLabel, decorators: [{
type: Directive,
args: [{
selector: 'mat-label',
}]
}] });
let nextUniqueId$2 = 0;
/**
* Injection token that can be used to reference instances of `MatError`. It serves as
* alternative token to the actual `MatError` class which could cause unnecessary
* retention of the class and its directive metadata.
*/
const MAT_ERROR = new InjectionToken('MatError');
/** Single error message to be shown underneath the form-field. */
class MatError {
constructor(ariaLive, elementRef) {
this.id = `mat-mdc-error-${nextUniqueId$2++}`;
// If no aria-live value is set add 'polite' as a default. This is preferred over setting
// role='alert' so that screen readers do not interrupt the current task to read this aloud.
if (!ariaLive) {
elementRef.nativeElement.setAttribute('aria-live', 'polite');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatError, deps: [{ token: 'aria-live', attribute: true }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatError, selector: "mat-error, [matError]", inputs: { id: "id" }, host: { attributes: { "aria-atomic": "true" }, properties: { "id": "id" }, classAttribute: "mat-mdc-form-field-error mat-mdc-form-field-bottom-align" }, providers: [{ provide: MAT_ERROR, useExisting: MatError }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatError, decorators: [{
type: Directive,
args: [{
selector: 'mat-error, [matError]',
host: {
'class': 'mat-mdc-form-field-error mat-mdc-form-field-bottom-align',
'aria-atomic': 'true',
'[id]': 'id',
},
providers: [{ provide: MAT_ERROR, useExisting: MatError }],
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Attribute,
args: ['aria-live']
}] }, { type: i0.ElementRef }]; }, propDecorators: { id: [{
type: Input
}] } });
let nextUniqueId$1 = 0;
/** Hint text to be shown underneath the form field control. */
class MatHint {
constructor() {
/** Whether to align the hint label at the start or end of the line. */
this.align = 'start';
/** Unique ID for the hint. Used for the aria-describedby on the form field control. */
this.id = `mat-mdc-hint-${nextUniqueId$1++}`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatHint, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatHint, selector: "mat-hint", inputs: { align: "align", id: "id" }, host: { properties: { "class.mat-mdc-form-field-hint-end": "align === \"end\"", "id": "id", "attr.align": "null" }, classAttribute: "mat-mdc-form-field-hint mat-mdc-form-field-bottom-align" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatHint, decorators: [{
type: Directive,
args: [{
selector: 'mat-hint',
host: {
'class': 'mat-mdc-form-field-hint mat-mdc-form-field-bottom-align',
'[class.mat-mdc-form-field-hint-end]': 'align === "end"',
'[id]': 'id',
// Remove align attribute to prevent it from interfering with layout.
'[attr.align]': 'null',
},
}]
}], propDecorators: { align: [{
type: Input
}], id: [{
type: Input
}] } });
/**
* Injection token that can be used to reference instances of `MatPrefix`. It serves as
* alternative token to the actual `MatPrefix` class which could cause unnecessary
* retention of the class and its directive metadata.
*/
const MAT_PREFIX = new InjectionToken('MatPrefix');
/** Prefix to be placed in front of the form field. */
class MatPrefix {
constructor() {
this._isText = false;
}
set _isTextSelector(value) {
this._isText = true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatPrefix, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: { _isTextSelector: ["matTextPrefix", "_isTextSelector"] }, providers: [{ provide: MAT_PREFIX, useExisting: MatPrefix }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatPrefix, decorators: [{
type: Directive,
args: [{
selector: '[matPrefix], [matIconPrefix], [matTextPrefix]',
providers: [{ provide: MAT_PREFIX, useExisting: MatPrefix }],
}]
}], propDecorators: { _isTextSelector: [{
type: Input,
args: ['matTextPrefix']
}] } });
/**
* Injection token that can be used to reference instances of `MatSuffix`. It serves as
* alternative token to the actual `MatSuffix` class which could cause unnecessary
* retention of the class and its directive metadata.
*/
const MAT_SUFFIX = new InjectionToken('MatSuffix');
/** Suffix to be placed at the end of the form field. */
class MatSuffix {
constructor() {
this._isText = false;
}
set _isTextSelector(value) {
this._isText = true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatSuffix, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: { _isTextSelector: ["matTextSuffix", "_isTextSelector"] }, providers: [{ provide: MAT_SUFFIX, useExisting: MatSuffix }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatSuffix, decorators: [{
type: Directive,
args: [{
selector: '[matSuffix], [matIconSuffix], [matTextSuffix]',
providers: [{ provide: MAT_SUFFIX, useExisting: MatSuffix }],
}]
}], propDecorators: { _isTextSelector: [{
type: Input,
args: ['matTextSuffix']
}] } });
/** An injion token for the parent form-field. */
const FLOATING_LABEL_PARENT = new InjectionToken('FloatingLabelParent');
/**
* Internal directive that maintains a MDC floating label. This directive does not
* use the `MDCFloatingLabelFoundation` class, as it is not worth the size cost of
* including it just to measure the label width and toggle some classes.
*
* The use of a directive allows us to conditionally render a floating label in the
* template without having to manually manage instantiation and destruction of the
* floating label component based on.
*
* The component is responsible for setting up the floating label styles, measuring label
* width for the outline notch, and providing inputs that can be used to toggle the
* label's floating or required state.
*/
class MatFormFieldFloatingLabel {
/** Whether the label is floating. */
get floating() {
return this._floating;
}
set floating(value) {
this._floating = value;
if (this.monitorResize) {
this._handleResize();
}
}
/** Whether to monitor for resize events on the floating label. */
get monitorResize() {
return this._monitorResize;
}
set monitorResize(value) {
this._monitorResize = value;
if (this._monitorResize) {
this._subscribeToResize();
}
else {
this._resizeSubscription.unsubscribe();
}
}
constructor(_elementRef) {
this._elementRef = _elementRef;
this._floating = false;
this._monitorResize = false;
/** The shared ResizeObserver. */
this._resizeObserver = inject(SharedResizeObserver);
/** The Angular zone. */
this._ngZone = inject(NgZone);
/** The parent form-field. */
this._parent = inject(FLOATING_LABEL_PARENT);
/** The current resize event subscription. */
this._resizeSubscription = new Subscription();
}
ngOnDestroy() {
this._resizeSubscription.unsubscribe();
}
/** Gets the width of the label. Used for the outline notch. */
getWidth() {
return estimateScrollWidth(this._elementRef.nativeElement);
}
/** Gets the HTML element for the floating label. */
get element() {
return this._elementRef.nativeElement;
}
/** Handles resize events from the ResizeObserver. */
_handleResize() {
// In the case where the label grows in size, the following sequence of events occurs:
// 1. The label grows by 1px triggering the ResizeObserver
// 2. The notch is expanded to accommodate the entire label
// 3. The label expands to its full width, triggering the ResizeObserver again
//
// This is expected, but If we allow this to all happen within the same macro task it causes an
// error: `ResizeObserver loop limit exceeded`. Therefore we push the notch resize out until
// the next macro task.
setTimeout(() => this._parent._handleLabelResized());
}
/** Subscribes to resize events. */
_subscribeToResize() {
this._resizeSubscription.unsubscribe();
this._ngZone.runOutsideAngular(() => {
this._resizeSubscription = this._resizeObserver
.observe(this._elementRef.nativeElement, { box: 'border-box' })
.subscribe(() => this._handleResize());
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldFloatingLabel, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatFormFieldFloatingLabel, selector: "label[matFormFieldFloatingLabel]", inputs: { floating: "floating", monitorResize: "monitorResize" }, host: { properties: { "class.mdc-floating-label--float-above": "floating" }, classAttribute: "mdc-floating-label mat-mdc-floating-label" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldFloatingLabel, decorators: [{
type: Directive,
args: [{
selector: 'label[matFormFieldFloatingLabel]',
host: {
'class': 'mdc-floating-label mat-mdc-floating-label',
'[class.mdc-floating-label--float-above]': 'floating',
},
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { floating: [{
type: Input
}], monitorResize: [{
type: Input
}] } });
/**
* Estimates the scroll width of an element.
* via https://github.com/material-components/material-components-web/blob/c0a11ef0d000a098fd0c372be8f12d6a99302855/packages/mdc-dom/ponyfill.ts
*/
function estimateScrollWidth(element) {
// Check the offsetParent. If the element inherits display: none from any
// parent, the offsetParent property will be null (see
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent).
// This check ensures we only clone the node when necessary.
const htmlEl = element;
if (htmlEl.offsetParent !== null) {
return htmlEl.scrollWidth;
}
const clone = htmlEl.cloneNode(true);
clone.style.setProperty('position', 'absolute');
clone.style.setProperty('transform', 'translate(-9999px, -9999px)');
document.documentElement.appendChild(clone);
const scrollWidth = clone.scrollWidth;
clone.remove();
return scrollWidth;
}
/** Class added when the line ripple is active. */
const ACTIVATE_CLASS = 'mdc-line-ripple--active';
/** Class added when the line ripple is being deactivated. */
const DEACTIVATING_CLASS = 'mdc-line-ripple--deactivating';
/**
* Internal directive that creates an instance of the MDC line-ripple component. Using a
* directive allows us to conditionally render a line-ripple in the template without having
* to manually create and destroy the `MDCLineRipple` component whenever the condition changes.
*
* The directive sets up the styles for the line-ripple and provides an API for activating
* and deactivating the line-ripple.
*/
class MatFormFieldLineRipple {
constructor(_elementRef, ngZone) {
this._elementRef = _elementRef;
this._handleTransitionEnd = (event) => {
const classList = this._elementRef.nativeElement.classList;
const isDeactivating = classList.contains(DEACTIVATING_CLASS);
if (event.propertyName === 'opacity' && isDeactivating) {
classList.remove(ACTIVATE_CLASS, DEACTIVATING_CLASS);
}
};
ngZone.runOutsideAngular(() => {
_elementRef.nativeElement.addEventListener('transitionend', this._handleTransitionEnd);
});
}
activate() {
const classList = this._elementRef.nativeElement.classList;
classList.remove(DEACTIVATING_CLASS);
classList.add(ACTIVATE_CLASS);
}
deactivate() {
this._elementRef.nativeElement.classList.add(DEACTIVATING_CLASS);
}
ngOnDestroy() {
this._elementRef.nativeElement.removeEventListener('transitionend', this._handleTransitionEnd);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldLineRipple, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatFormFieldLineRipple, selector: "div[matFormFieldLineRipple]", host: { classAttribute: "mdc-line-ripple" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldLineRipple, decorators: [{
type: Directive,
args: [{
selector: 'div[matFormFieldLineRipple]',
host: {
'class': 'mdc-line-ripple',
},
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }]; } });
/**
* Internal component that creates an instance of the MDC notched-outline component.
*
* The component sets up the HTML structure and styles for the notched-outline. It provides
* inputs to toggle the notch state and width.
*/
class MatFormFieldNotchedOutline {
constructor(_elementRef, _ngZone) {
this._elementRef = _elementRef;
this._ngZone = _ngZone;
/** Whether the notch should be opened. */
this.open = false;
}
ngAfterViewInit() {
const label = this._elementRef.nativeElement.querySelector('.mdc-floating-label');
if (label) {
this._elementRef.nativeElement.classList.add('mdc-notched-outline--upgraded');
if (typeof requestAnimationFrame === 'function') {
label.style.transitionDuration = '0s';
this._ngZone.runOutsideAngular(() => {
requestAnimationFrame(() => (label.style.transitionDuration = ''));
});
}
}
else {
this._elementRef.nativeElement.classList.add('mdc-notched-outline--no-label');
}
}
_setNotchWidth(labelWidth) {
if (!this.open || !labelWidth) {
this._notch.nativeElement.style.width = '';
}
else {
const NOTCH_ELEMENT_PADDING = 8;
const NOTCH_ELEMENT_BORDER = 1;
this._notch.nativeElement.style.width = `calc(${labelWidth}px * var(--mat-mdc-form-field-floating-label-scale, 0.75) + ${NOTCH_ELEMENT_PADDING + NOTCH_ELEMENT_BORDER}px)`;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldNotchedOutline, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: MatFormFieldNotchedOutline, selector: "div[matFormFieldNotchedOutline]", inputs: { open: ["matFormFieldNotchedOutlineOpen", "open"] }, host: { properties: { "class.mdc-notched-outline--notched": "open" }, classAttribute: "mdc-notched-outline" }, viewQueries: [{ propertyName: "_notch", first: true, predicate: ["notch"], descendants: true }], ngImport: i0, template: "
\n
\n \n
\n\n", changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldNotchedOutline, decorators: [{
type: Component,
args: [{ selector: 'div[matFormFieldNotchedOutline]', host: {
'class': 'mdc-notched-outline',
// Besides updating the notch state through the MDC component, we toggle this class through
// a host binding in order to ensure that the notched-outline renders correctly on the server.
'[class.mdc-notched-outline--notched]': 'open',
}, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "\n
\n \n
\n\n" }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { open: [{
type: Input,
args: ['matFormFieldNotchedOutlineOpen']
}], _notch: [{
type: ViewChild,
args: ['notch']
}] } });
/**
* Animations used by the MatFormField.
* @docs-private
*/
const matFormFieldAnimations = {
/** Animation that transitions the form field's error and hint messages. */
transitionMessages: trigger('transitionMessages', [
// TODO(mmalerba): Use angular animations for label animation as well.
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [
style({ opacity: 0, transform: 'translateY(-5px)' }),
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)'),
]),
]),
};
/** An interface which allows a control to work inside of a `MatFormField`. */
class MatFormFieldControl {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldControl, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: MatFormFieldControl, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormFieldControl, decorators: [{
type: Directive
}] });
/** @docs-private */
function getMatFormFieldPlaceholderConflictError() {
return Error('Placeholder attribute and child element were both specified.');
}
/** @docs-private */
function getMatFormFieldDuplicatedHintError(align) {
return Error(`A hint was already declared for 'align="${align}"'.`);
}
/** @docs-private */
function getMatFormFieldMissingControlError() {
return Error('mat-form-field must contain a MatFormFieldControl.');
}
/**
* Injection token that can be used to inject an instances of `MatFormField`. It serves
* as alternative token to the actual `MatFormField` class which would cause unnecessary
* retention of the `MatFormField` class and its component metadata.
*/
const MAT_FORM_FIELD = new InjectionToken('MatFormField');
/**
* Injection token that can be used to configure the
* default options for all form field within an app.
*/
const MAT_FORM_FIELD_DEFAULT_OPTIONS = new InjectionToken('MAT_FORM_FIELD_DEFAULT_OPTIONS');
let nextUniqueId = 0;
/** Default appearance used by the form field. */
const DEFAULT_APPEARANCE = 'fill';
/**
* Whether the label for form fields should by default float `always`,
* `never`, or `auto`.
*/
const DEFAULT_FLOAT_LABEL = 'auto';
/** Default way that the subscript element height is set. */
const DEFAULT_SUBSCRIPT_SIZING = 'fixed';
/**
* Default transform for docked floating labels in a MDC text-field. This value has been
* extracted from the MDC text-field styles because we programmatically modify the docked
* label transform, but do not want to accidentally discard the default label transform.
*/
const FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM = `translateY(-50%)`;
/** Container for form controls that applies Material Design styling and behavior. */
class MatFormField {
/** Whether the required marker should be hidden. */
get hideRequiredMarker() {
return this._hideRequiredMarker;
}
set hideRequiredMarker(value) {
this._hideRequiredMarker = coerceBooleanProperty(value);
}
/** Whether the label should always float or float as the user types. */
get floatLabel() {
return this._floatLabel || this._defaults?.floatLabel || DEFAULT_FLOAT_LABEL;
}
set floatLabel(value) {
if (value !== this._floatLabel) {
this._floatLabel = value;
// For backwards compatibility. Custom form field controls or directives might set
// the "floatLabel" input and expect the form field view to be updated automatically.
// e.g. autocomplete trigger. Ideally we'd get rid of this and the consumers would just
// emit the "stateChanges" observable. TODO(devversion): consider removing.
this._changeDetectorRef.markForCheck();
}
}
/** The form field appearance style. */
get appearance() {
return this._appearance;
}
set appearance(value) {
const oldValue = this._appearance;
const newAppearance = value || this._defaults?.appearance || DEFAULT_APPEARANCE;
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (newAppearance !== 'fill' && newAppearance !== 'outline') {
throw new Error(`MatFormField: Invalid appearance "${newAppearance}", valid values are "fill" or "outline".`);
}
}
this._appearance = newAppearance;
if (this._appearance === 'outline' && this._appearance !== oldValue) {
// If the appearance has been switched to `outline`, the label offset needs to be updated.
// The update can happen once the view has been re-checked, but not immediately because
// the view has not been updated and the notched-outline floating label is not present.
this._needsOutlineLabelOffsetUpdateOnStable = true;
}
}
/**
* Whether the form field should reserve space for one line of hint/error text (default)
* or to have the spacing grow from 0px as needed based on the size of the hint/error content.
* Note that when using dynamic sizing, layout shifts will occur when hint/error text changes.
*/
get subscriptSizing() {
return this._subscriptSizing || this._defaults?.subscriptSizing || DEFAULT_SUBSCRIPT_SIZING;
}
set subscriptSizing(value) {
this._subscriptSizing = value || this._defaults?.subscriptSizing || DEFAULT_SUBSCRIPT_SIZING;
}
/** Text for the form field hint. */
get hintLabel() {
return this._hintLabel;
}
set hintLabel(value) {
this._hintLabel = value;
this._processHints();
}
/** Gets the current form field control */
get _control() {
return this._explicitFormFieldControl || this._formFieldControl;
}
set _control(value) {
this._explicitFormFieldControl = value;
}
constructor(_elementRef, _changeDetectorRef, _ngZone, _dir, _platform, _defaults, _animationMode,
/**
* @deprecated not needed, to be removed.
* @breaking-change 17.0.0 remove this param
*/
_unusedDocument) {
this._elementRef = _elementRef;
this._changeDetectorRef = _changeDetectorRef;
this._ngZone = _ngZone;
this._dir = _dir;
this._platform = _platform;
this._defaults = _defaults;
this._animationMode = _animationMode;
this._hideRequiredMarker = false;
/** The color palette for the form field. */
this.color = 'primary';
this._appearance = DEFAULT_APPEARANCE;
this._subscriptSizing = null;
this._hintLabel = '';
this._hasIconPrefix = false;
this._hasTextPrefix = false;
this._hasIconSuffix = false;
this._hasTextSuffix = false;
// Unique id for the internal form field label.
this._labelId = `mat-mdc-form-field-label-${nextUniqueId++}`;
// Unique id for the hint label.
this._hintLabelId = `mat-mdc-hint-${nextUniqueId++}`;
/** State of the mat-hint and mat-error animations. */
this._subscriptAnimationState = '';
this._destroyed = new Subject();
this._isFocused = null;
this._needsOutlineLabelOffsetUpdateOnStable = false;
if (_defaults) {
if (_defaults.appearance) {
this.appearance = _defaults.appearance;
}
this._hideRequiredMarker = Boolean(_defaults?.hideRequiredMarker);
if (_defaults.color) {
this.color = _defaults.color;
}
}
}
ngAfterViewInit() {
// Initial focus state sync. This happens rarely, but we want to account for
// it in case the form field control has "focused" set to true on init.
this._updateFocusState();
// Enable animations now. This ensures we don't animate on initial render.
this._subscriptAnimationState = 'enter';
// Because the above changes a value used in the template after it was checked, we need
// to trigger CD or the change might not be reflected if there is no other CD scheduled.
this._changeDetectorRef.detectChanges();
}
ngAfterContentInit() {
this._assertFormFieldControl();
this._initializeControl();
this._initializeSubscript();
this._initializePrefixAndSuffix();
this._initializeOutlineLabelOffsetSubscriptions();
}
ngAfterContentChecked() {
this._assertFormFieldControl();
}
ngOnDestroy() {
this._destroyed.next();
this._destroyed.complete();
}
/**
* Gets the id of the label element. If no label is present, returns `null`.
*/
getLabelId() {
return this._hasFloatingLabel() ? this._labelId : null;
}
/**
* Gets an ElementRef for the element that a overlay attached to the form field
* should be positioned relative to.
*/
getConnectedOverlayOrigin() {
return this._textField || this._elementRef;
}
/** Animates the placeholder up and locks it in position. */
_animateAndLockLabel() {
// This is for backwards compatibility only. Consumers of the form field might use
// this method. e.g. the autocomplete trigger. This method has been added to the non-MDC
// form field because setting "floatLabel" to "always" caused the label to float without
// animation. This is different in MDC where the label always animates, so this method
// is no longer necessary. There doesn't seem any benefit in adding logic to allow changing
// the floating label state without animations. The non-MDC implementation was inconsistent
// because it always animates if "floatLabel" is set away from "always".
// TODO(devversion): consider removing this method when releasing the MDC form field.
if (this._hasFloatingLabel()) {
this.floatLabel = 'always';
}
}
/** Initializes the registered form field control. */
_initializeControl() {
const control = this._control;
if (control.controlType) {
this._elementRef.nativeElement.classList.add(`mat-mdc-form-field-type-${control.controlType}`);
}
// Subscribe to changes in the child control state in order to update the form field UI.
control.stateChanges.subscribe(() => {
this._updateFocusState();
this._syncDescribedByIds();
this._changeDetectorRef.markForCheck();
});
// Run change detection if the value changes.
if (control.ngControl && control.ngControl.valueChanges) {
control.ngControl.valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(() => this._changeDetectorRef.markForCheck());
}
}
_checkPrefixAndSuffixTypes() {
this._hasIconPrefix = !!this._prefixChildren.find(p => !p._isText);
this._hasTextPrefix = !!this._prefixChildren.find(p => p._isText);
this._hasIconSuffix = !!this._suffixChildren.find(s => !s._isText);
this._hasTextSuffix = !!this._suffixChildren.find(s => s._isText);
}
/** Initializes the prefix and suffix containers. */
_initializePrefixAndSuffix() {
this._checkPrefixAndSuffixTypes();
// Mark the form field as dirty whenever the prefix or suffix children change. This
// is necessary because we conditionally display the prefix/suffix containers based
// on whether there is projected content.
merge(this._prefixChildren.changes, this._suffixChildren.changes).subscribe(() => {
this._checkPrefixAndSuffixTypes();
this._changeDetectorRef.markForCheck();
});
}
/**
* Initializes the subscript by validating hints and synchronizing "aria-describedby" ids
* with the custom form field control. Also subscribes to hint and error changes in order
* to be able to validate and synchronize ids on change.
*/
_initializeSubscript() {
// Re-validate when the number of hints changes.
this._hintChildren.changes.subscribe(() => {
this._processHints();
this._changeDetectorRef.markForCheck();
});
// Update the aria-described by when the number of errors changes.
this._errorChildren.changes.subscribe(() => {
this._syncDescribedByIds();
this._changeDetectorRef.markForCheck();
});
// Initial mat-hint validation and subscript describedByIds sync.
this._validateHints();
this._syncDescribedByIds();
}
/** Throws an error if the form field's control is missing. */
_assertFormFieldControl() {
if (!this._control && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getMatFormFieldMissingControlError();
}
}
_updateFocusState() {
// 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) {
this._isFocused = true;
this._lineRipple?.activate();
}
else if (!this._control.focused && (this._isFocused || this._isFocused === null)) {
this._isFocused = false;
this._lineRipple?.deactivate();
}
this._textField?.nativeElement.classList.toggle('mdc-text-field--focused', this._control.focused);
}
/**
* 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
* form field is added to the DOM. This method sets up all subscriptions which are needed to
* trigger the label offset update. In general, we want to avoid performing measurements often,
* so we rely on the `NgZone` as indicator when the offset should be recalculated, instead of
* checking every change detection cycle.
*/
_initializeOutlineLabelOffsetSubscriptions() {
// Whenever the prefix changes, schedule an update of the label offset.
this._prefixChildren.changes.subscribe(() => (this._needsOutlineLabelOffsetUpdateOnStable = true));
// Note that we have to run outside of the `NgZone` explicitly, in order to avoid
// throwing users into an infinite loop if `zone-patch-rxjs` is included.
this._ngZone.runOutsideAngular(() => {
this._ngZone.onStable.pipe(takeUntil(this._destroyed)).subscribe(() => {
if (this._needsOutlineLabelOffsetUpdateOnStable) {
this._needsOutlineLabelOffsetUpdateOnStable = false;
this._updateOutlineLabelOffset();
}
});
});
this._dir.change
.pipe(takeUntil(this._destroyed))
.subscribe(() => (this._needsOutlineLabelOffsetUpdateOnStable = true));
}
/** Whether the floating label should always float or not. */
_shouldAlwaysFloat() {
return this.floatLabel === 'always';
}
_hasOutline() {
return this.appearance === 'outline';
}
/**
* Whether the label should display in the infix. Labels in the outline appearance are
* displayed as part of the notched-outline and are horizontally offset to account for
* form field prefix content. This won't work in server side rendering since we cannot
* measure the width of the prefix container. To make the docked label appear as if the
* right offset has been calculated, we forcibly render the label inside the infix. Since
* the label is part of the infix, the label cannot overflow the prefix content.
*/
_forceDisplayInfixLabel() {
return !this._platform.isBrowser && this._prefixChildren.length && !this._shouldLabelFloat();
}
_hasFloatingLabel() {
return !!this._labelChildNonStatic || !!this._labelChildStatic;
}
_shouldLabelFloat() {
return this._control.shouldLabelFloat || this._shouldAlwaysFloat();
}
/**
* Determines whether a class from the AbstractControlDirective
* should be forwarded to the host element.
*/
_shouldForward(prop) {
const control = this._control ? this._control.ngControl : null;
return control && control[prop];
}
/** Determines whether to display hints or errors. */
_getDisplayedMessages() {
return this._errorChildren && this._errorChildren.length > 0 && this._control.errorState
? 'error'
: 'hint';
}
/** Handle label resize events. */
_handleLabelResized() {
this._refreshOutlineNotchWidth();
}
/** Refreshes the width of the outline-notch, if present. */
_refreshOutlineNotchWidth() {
if (!this._hasOutline() || !this._floatingLabel || !this._shouldLabelFloat()) {
this._notchedOutline?._setNotchWidth(0);
}
else {
this._notchedOutline?._setNotchWidth(this._floatingLabel.getWidth());
}
}
/** Does any extra processing that is required when handling the hints. */
_processHints() {
this._validateHints();
this._syncDescribedByIds();
}
/**
* Ensure that there is a maximum of one of each "mat-hint" alignment specified. The hint
* label specified set through the input is being considered as "start" aligned.
*
* This method is a noop if Angular runs in production mode.
*/
_validateHints() {
if (this._hintChildren && (typeof ngDevMode === 'undefined' || ngDevMode)) {
let startHint;
let endHint;
this._hintChildren.forEach((hint) => {
if (hint.align === 'start') {
if (startHint || this.hintLabel) {
throw getMatFormFieldDuplicatedHintError('start');
}
startHint = hint;
}
else if (hint.align === 'end') {
if (endHint) {
throw getMatFormFieldDuplicatedHintError('end');
}
endHint = hint;
}
});
}
}
/**
* Sets the list of element IDs that describe the child control. This allows the control to update
* its `aria-describedby` attribute accordingly.
*/
_syncDescribedByIds() {
if (this._control) {
let ids = [];
// TODO(wagnermaciel): Remove the type check when we find the root cause of this bug.
if (this._control.userAriaDescribedBy &&
typeof this._control.userAriaDescribedBy === 'string') {
ids.push(...this._control.userAriaDescribedBy.split(' '));
}
if (this._getDisplayedMessages() === 'hint') {
const startHint = this._hintChildren
? this._hintChildren.find(hint => hint.align === 'start')
: null;
const endHint = this._hintChildren
? this._hintChildren.find(hint => hint.align === 'end')
: null;
if (startHint) {
ids.push(startHint.id);
}
else if (this._hintLabel) {
ids.push(this._hintLabelId);
}
if (endHint) {
ids.push(endHint.id);
}
}
else if (this._errorChildren) {
ids.push(...this._errorChildren.map(error => error.id));
}
this._control.setDescribedByIds(ids);
}
}
/**
* Updates the horizontal offset of the label in the outline appearance. In the outline
* appearance, the notched-outline and label are not relative to the infix container because
* the outline intends to surround prefixes, suffixes and the infix. This means that the
* floating label by default overlaps prefixes in the docked state. To avoid this, we need to
* horizontally offset the label by the width of the prefix container. The MDC text-field does
* not need to do this because they use a fixed width for prefixes. Hence, they can simply
* incorporate the horizontal offset into their default text-field styles.
*/
_updateOutlineLabelOffset() {
if (!this._platform.isBrowser || !this._hasOutline() || !this._floatingLabel) {
return;
}
const floatingLabel = this._floatingLabel.element;
// If no prefix is displayed, reset the outline label offset from potential
// previous label offset updates.
if (!(this._iconPrefixContainer || this._textPrefixContainer)) {
floatingLabel.style.transform = '';
return;
}
// If the form field is not attached to the DOM yet (e.g. in a tab), we defer
// the label offset update until the zone stabilizes.
if (!this._isAttachedToDom()) {
this._needsOutlineLabelOffsetUpdateOnStable = true;
return;
}
const iconPrefixContainer = this._iconPrefixContainer?.nativeElement;
const textPrefixContainer = this._textPrefixContainer?.nativeElement;
const iconPrefixContainerWidth = iconPrefixContainer?.getBoundingClientRect().width ?? 0;
const textPrefixContainerWidth = textPrefixContainer?.getBoundingClientRect().width ?? 0;
// If the directionality is RTL, the x-axis transform needs to be inverted. This
// is because `transformX` does not change based on the page directionality.
const negate = this._dir.value === 'rtl' ? '-1' : '1';
const prefixWidth = `${iconPrefixContainerWidth + textPrefixContainerWidth}px`;
const labelOffset = `var(--mat-mdc-form-field-label-offset-x, 0px)`;
const labelHorizontalOffset = `calc(${negate} * (${prefixWidth} + ${labelOffset}))`;
// Update the translateX of the floating label to account for the prefix container,
// but allow the CSS to override this setting via a CSS variable when the label is
// floating.
floatingLabel.style.transform = `var(
--mat-mdc-form-field-label-transform,
${FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM} translateX(${labelHorizontalOffset})
)`;
}
/** Checks whether the form field is attached to the DOM. */
_isAttachedToDom() {
const element = this._elementRef.nativeElement;
if (element.getRootNode) {
const rootNode = element.getRootNode();
// If the element is inside the DOM the root node will be either the document
// or the closest shadow root, otherwise it'll be the element itself.
return rootNode && rootNode !== element;
}
// Otherwise fall back to checking if it's in the document. This doesn't account for
// shadow DOM, however browser that support shadow DOM should support `getRootNode` as well.
return document.documentElement.contains(element);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: MatFormField, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i1.Directionality }, { token: i2.Platform }, { token: MAT_FORM_FIELD_DEFAULT_OPTIONS, optional: true }, { token: ANIMATION_MODULE_TYPE, optional: true }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: MatFormField, selector: "mat-form-field", inputs: { hideRequiredMarker: "hideRequiredMarker", color: "color", floatLabel: "floatLabel", appearance: "appearance", subscriptSizing: "subscriptSizing", hintLabel: "hintLabel" }, host: { properties: { "class.mat-mdc-form-field-label-always-float": "_shouldAlwaysFloat()", "class.mat-mdc-form-field-has-icon-prefix": "_hasIconPrefix", "class.mat-mdc-form-field-has-icon-suffix": "_hasIconSuffix", "class.mat-form-field-invalid": "_control.errorState", "class.mat-form-field-disabled": "_control.disabled", "class.mat-form-field-autofilled": "_control.autofilled", "class.mat-form-field-no-animations": "_animationMode === \"NoopAnimations\"", "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\"", "class.ng-untouched": "_shouldForward(\"untouched\")", "class.ng-touched": "_shouldForward(\"touched\")", "class.ng-pristine": "_shouldForward(\"pristine\")", "class.ng-dirty": "_shouldForward(\"dirty\")", "class.ng-valid": "_shouldForward(\"valid\")", "class.ng-invalid": "_shouldForward(\"invalid\")", "class.ng-pending": "_shouldForward(\"pending\")" }, classAttribute: "mat-mdc-form-field" }, providers: [
{ provide: MAT_FORM_FIELD, useExisting: MatFormField },
{ provide: FLOATING_LABEL_PARENT, useExisting: MatFormField },
], queries: [{ propertyName: "_labelChildNonStatic", first: true, predicate: MatLabel, descendants: true }, { propertyName: "_labelChildStatic", first: true, predicate: MatLabel, descendants: true, static: true }, { propertyName: "_formFieldControl", first: true, predicate: MatFormFieldControl, descendants: true }, { propertyName: "_prefixChildren", predicate: MAT_PREFIX, descendants: true }, { propertyName: "_suffixChildren", predicate: MAT_SUFFIX, descendants: true }, { propertyName: "_errorChildren", predicate: MAT_ERROR, descendants: true }, { propertyName: "_hintChildren", predicate: MatHint, descendants: true }], viewQueries: [{ propertyName: "_textField", first: true, predicate: ["textField"], descendants: true }, { propertyName: "_iconPrefixContainer", first: true, predicate: ["iconPrefixContainer"], descendants: true }, { propertyName: "_textPrefixContainer", first: true, predicate: ["textPrefixContainer"], descendants: true }, { propertyName: "_floatingLabel", first: true, predicate: MatFormFieldFloatingLabel, descendants: true }, { propertyName: "_notchedOutline", first: true, predicate: MatFormFieldNotchedOutline, descendants: true }, { propertyName: "_lineRipple", first: true, predicate: MatFormFieldLineRipple, descendants: true }], exportAs: ["matFormField"], ngImport: i0, template: "\n \n \n\n\n