,
    'headline-small-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'headline-small-line-height': if($exclude-hardcoded-values, null, 2rem),
    'headline-small-size': if($exclude-hardcoded-values, null, 1.5rem),
    'headline-small-tracking': if($exclude-hardcoded-values, null, 0),
    'headline-small-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.label-large.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'label-large':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-medium')
          if($exclude-hardcoded-values, null, 0.875rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1.25rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'label-large-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'label-large-line-height': if($exclude-hardcoded-values, null, 1.25rem),
    'label-large-size': if($exclude-hardcoded-values, null, 0.875rem),
    'label-large-tracking': if($exclude-hardcoded-values, null, 0.006rem),
    'label-large-weight': map.get($deps, 'md-ref-typeface', 'weight-medium'),
    'label-large-weight-prominent':
      map.get($deps, 'md-ref-typeface', 'weight-bold'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.label-medium.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'label-medium':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-medium')
          if($exclude-hardcoded-values, null, 0.75rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'label-medium-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'label-medium-line-height': if($exclude-hardcoded-values, null, 1rem),
    'label-medium-size': if($exclude-hardcoded-values, null, 0.75rem),
    'label-medium-tracking': if($exclude-hardcoded-values, null, 0.031rem),
    'label-medium-weight': map.get($deps, 'md-ref-typeface', 'weight-medium'),
    'label-medium-weight-prominent':
      map.get($deps, 'md-ref-typeface', 'weight-bold'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.label-small.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'label-small':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-medium')
          if($exclude-hardcoded-values, null, 0.688rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'label-small-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'label-small-line-height': if($exclude-hardcoded-values, null, 1rem),
    'label-small-size': if($exclude-hardcoded-values, null, 0.688rem),
    'label-small-tracking': if($exclude-hardcoded-values, null, 0.031rem),
    'label-small-weight': map.get($deps, 'md-ref-typeface', 'weight-medium'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.title-large.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'title-large':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 1.375rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1.75rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      ),
    'title-large-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'title-large-line-height': if($exclude-hardcoded-values, null, 1.75rem),
    'title-large-size': if($exclude-hardcoded-values, null, 1.375rem),
    'title-large-tracking': if($exclude-hardcoded-values, null, 0),
    'title-large-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.title-medium.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'title-medium':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-medium')
          if($exclude-hardcoded-values, null, 1rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1.5rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'title-medium-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'title-medium-line-height': if($exclude-hardcoded-values, null, 1.5rem),
    'title-medium-size': if($exclude-hardcoded-values, null, 1rem),
    'title-medium-tracking': if($exclude-hardcoded-values, null, 0.009rem),
    'title-medium-weight': map.get($deps, 'md-ref-typeface', 'weight-medium'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.title-small.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'title-small':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-medium')
          if($exclude-hardcoded-values, null, 0.875rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1.25rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'title-small-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'title-small-line-height': if($exclude-hardcoded-values, null, 1.25rem),
    'title-small-size': if($exclude-hardcoded-values, null, 0.875rem),
    'title-small-tracking': if($exclude-hardcoded-values, null, 0.006rem),
    'title-small-weight': map.get($deps, 'md-ref-typeface', 'weight-medium')
  );
}


class MatAutocomplete implements AfterContentInit, OnDestroy {
  private _changeDetectorRef = inject(ChangeDetectorRef);
  private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  protected _defaults = inject<MatAutocompleteDefaultOptions>(MAT_AUTOCOMPLETE_DEFAULT_OPTIONS);

  private _activeOptionChanges = Subscription.EMPTY;

  /** Emits when the panel animation is done. Null if the panel doesn't animate. */
  _animationDone = new EventEmitter<AnimationEvent>();

  /** Manages active item in option list based on key events. */
  _keyManager: ActiveDescendantKeyManager<MatOption>;

  /** Whether the autocomplete panel should be visible, depending on option length. */
  showPanel: boolean = false;

  /** Whether the autocomplete panel is open. */
  get isOpen(): boolean {
    return this._isOpen && this.showPanel;
  }
  _isOpen: boolean = false;

  /** Latest trigger that opened the autocomplete. */
  _latestOpeningTrigger: unknown;

  /** @docs-private Sets the theme color of the panel. */
  _setColor(value: ThemePalette) {
    this._color = value;
    this._changeDetectorRef.markForCheck();
  }
  /** @docs-private theme color of the panel */
  protected _color: ThemePalette;

  // The @ViewChild query for TemplateRef here needs to be static because some code paths
  // lead to the overlay being created before change detection has finished for this component.
  // Notably, another component may trigger `focus` on the autocomplete-trigger.

  /** @docs-private */
  @ViewChild(TemplateRef, {static: true}) template: TemplateRef<any>;

  /** Element for the panel containing the autocomplete options. */
  @ViewChild('panel') panel: ElementRef;

  /** Reference to all options within the autocomplete. */
  @ContentChildren(MatOption, {descendants: true}) options: QueryList<MatOption>;

  /** Reference to all option groups within the autocomplete. */
  @ContentChildren(MAT_OPTGROUP, {descendants: true}) optionGroups: QueryList<MatOptgroup>;

  /** Aria label of the autocomplete. */
  @Input('aria-label') ariaLabel: string;

  /** Input that can be used to specify the `aria-labelledby` attribute. */
  @Input('aria-labelledby') ariaLabelledby: string;

  /** Function that maps an option's control value to its display value in the trigger. */
  @Input() displayWith: ((value: any) => string) | null = null;

  /**
   * Whether the first option should be highlighted when the autocomplete panel is opened.
   * Can be configured globally through the `MAT_AUTOCOMPLETE_DEFAULT_OPTIONS` token.
   */
  @Input({transform: booleanAttribute}) autoActiveFirstOption: boolean;

  /** Whether the active option should be selected as the user is navigating. */
  @Input({transform: booleanAttribute}) autoSelectActiveOption: boolean;

  /**
   * Whether the user is required to make a selection when they're interacting with the
   * autocomplete. If the user moves away from the autocomplete without selecting an option from
   * the list, the value will be reset. If the user opens the panel and closes it without
   * interacting or selecting a value, the initial value will be kept.
   */
  @Input({transform: booleanAttribute}) requireSelection: boolean;

  /**
   * Specify the width of the autocomplete panel.  Can be any CSS sizing value, otherwise it will
   * match the width of its host.
   */
  @Input() panelWidth: string | number;

  /** Whether ripples are disabled within the autocomplete panel. */
  @Input({transform: booleanAttribute}) disableRipple: boolean;

  /** Event that is emitted whenever an option from the list is selected. */
  @Output() readonly optionSelected: EventEmitter<MatAutocompleteSelectedEvent> =
    new EventEmitter<MatAutocompleteSelectedEvent>();

  /** Event that is emitted when the autocomplete panel is opened. */
  @Output() readonly opened: EventEmitter<void> = new EventEmitter<void>();

  /** Event that is emitted when the autocomplete panel is closed. */
  @Output() readonly closed: EventEmitter<void> = new EventEmitter<void>();

  /** Emits whenever an option is activated. */
  @Output() readonly optionActivated: EventEmitter<MatAutocompleteActivatedEvent> =
    new EventEmitter<MatAutocompleteActivatedEvent>();

  /**
   * Takes classes set on the host mat-autocomplete element and applies them to the panel
   * inside the overlay container to allow for easy styling.
   */
  @Input('class')
  set classList(value: string | string[]) {
    this._classList = value;
    this._elementRef.nativeElement.className = '';
  }
  _classList: string | string[];

  /** Whether checkmark indicator for single-selection options is hidden. */
  @Input({transform: booleanAttribute})
  get hideSingleSelectionIndicator(): boolean {
    return this._hideSingleSelectionIndicator;
  }
  set hideSingleSelectionIndicator(value: boolean) {
    this._hideSingleSelectionIndicator = value;
    this._syncParentProperties();
  }
  private _hideSingleSelectionIndicator: boolean;

  /** Syncs the parent state with the individual options. */
  _syncParentProperties(): void {
    if (this.options) {
      for (const option of this.options) {
        option._changeDetectorRef.markForCheck();
      }
    }
  }

  /** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
  id: string = `mat-autocomplete-${_uniqueAutocompleteIdCounter++}`;

  /**
   * Tells any descendant `mat-optgroup` to use the inert a11y pattern.
   * @docs-private
   */
  readonly inertGroups: boolean;

  constructor(...args: unknown[]);

  constructor() {
    const platform = inject(Platform);

    // TODO(crisbeto): the problem that the `inertGroups` option resolves is only present on
    // Safari using VoiceOver. We should occasionally check back to see whether the bug
    // wasn't resolved in VoiceOver, and if it has, we can remove this and the `inertGroups`
    // option altogether.
    this.inertGroups = platform?.SAFARI || false;
    this.autoActiveFirstOption = !!this._defaults.autoActiveFirstOption;
    this.autoSelectActiveOption = !!this._defaults.autoSelectActiveOption;
    this.requireSelection = !!this._defaults.requireSelection;
    this._hideSingleSelectionIndicator = this._defaults.hideSingleSelectionIndicator ?? false;
  }

  ngAfterContentInit() {
    this._keyManager = new ActiveDescendantKeyManager<MatOption>(this.options)
      .withWrap()
      .skipPredicate(this._skipPredicate);
    this._activeOptionChanges = this._keyManager.change.subscribe(index => {
      if (this.isOpen) {
        this.optionActivated.emit({source: this, option: this.options.toArray()[index] || null});
      }
    });

    // Set the initial visibility state.
    this._setVisibility();
  }

  ngOnDestroy() {
    this._keyManager?.destroy();
    this._activeOptionChanges.unsubscribe();
    this._animationDone.complete();
  }

  /**
   * Sets the panel scrollTop. This allows us to manually scroll to display options
   * above or below the fold, as they are not actually being focused when active.
   */
  _setScrollTop(scrollTop: number): void {
    if (this.panel) {
      this.panel.nativeElement.scrollTop = scrollTop;
    }
  }

  /** Returns the panel's scrollTop. */
  _getScrollTop(): number {
    return this.panel ? this.panel.nativeElement.scrollTop : 0;
  }

  /** Panel should hide itself when the option list is empty. */
  _setVisibility() {
    this.showPanel = !!this.options.length;
    this._changeDetectorRef.markForCheck();
  }

  /** Emits the `select` event. */
  _emitSelectEvent(option: MatOption): void {
    const event = new MatAutocompleteSelectedEvent(this, option);
    this.optionSelected.emit(event);
  }

  /** Gets the aria-labelledby for the autocomplete panel. */
  _getPanelAriaLabelledby(labelId: string | null): string | null {
    if (this.ariaLabel) {
      return null;
    }

    const labelExpression = labelId ? labelId + ' ' : '';
    return this.ariaLabelledby ? labelExpression + this.ariaLabelledby : labelId;
  }

  // `skipPredicate` determines if key manager should avoid putting a given option in the tab
  // order. Allow disabled list items to receive focus via keyboard to align with WAI ARIA
  // recommendation.
  //
  // Normally WAI ARIA's instructions are to exclude disabled items from the tab order, but it
  // makes a few exceptions for compound widgets.
  //
  // From [Developing a Keyboard Interface](
  // https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/):
  //   "For the following composite widget elements, keep them focusable when disabled: Options in a
  //   Listbox..."
  //
  // The user can focus disabled options using the keyboard, but the user cannot click disabled
  // options.
  protected _skipPredicate() {
    return false;
  }
}


import {OverlayModule} from '@angular/cdk/overlay';
import {dispatchFakeEvent} from '@angular/cdk/testing/private';
import {
  Component,
  NgZone,
  OnDestroy,
  Provider,
  QueryList,
  Type,
  ViewChild,
  ViewChildren,
  provideZoneChangeDetection,
} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Subscription} from 'rxjs';
import {MatOption} from '../core';
import {MatFormField, MatFormFieldModule} from '../form-field';
import {MatInputModule} from '../input';
import {MatAutocomplete} from './autocomplete';
import {MatAutocompleteTrigger} from './autocomplete-trigger';
import {MatAutocompleteModule} from './module';

describe('MatAutocomplete Zone.js integration', () => {
  // Creates a test component fixture.
  function createComponent<T>(component: Type<T>, providers: Provider[] = []) {
    TestBed.configureTestingModule({
      imports: [
        MatAutocompleteModule,
        MatFormFieldModule,
        MatInputModule,
        FormsModule,
        ReactiveFormsModule,
        NoopAnimationsModule,
        OverlayModule,
      ],
      providers: [provideZoneChangeDetection(), ...providers],
      declarations: [component],
    });

    return TestBed.createComponent<T>(component);
  }

  describe('panel toggling', () => {
    let fixture: ComponentFixture<SimpleAutocomplete>;

    beforeEach(() => {
      fixture = createComponent(SimpleAutocomplete);
      fixture.detectChanges();
    });

    it('should show the panel when the first open is after the initial zone stabilization', waitForAsync(() => {
      // Note that we're running outside the Angular zone, in order to be able
      // to test properly without the subscription from `_subscribeToClosingActions`
      // giving us a false positive.
      fixture.ngZone!.runOutsideAngular(() => {
        fixture.componentInstance.trigger.openPanel();

        Promise.resolve().then(() => {
          expect(fixture.componentInstance.panel.showPanel)
            .withContext(`Expected panel to be visible.`)
            .toBe(true);
        });
      });
    }));
  });

  it('should emit from `autocomplete.closed` after click outside inside the NgZone', waitForAsync(async () => {
    const inZoneSpy = jasmine.createSpy('in zone spy');

    const fixture = createComponent(SimpleAutocomplete);
    fixture.detectChanges();

    fixture.componentInstance.trigger.openPanel();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    const subscription = fixture.componentInstance.trigger.autocomplete.closed.subscribe(() =>
      inZoneSpy(NgZone.isInAngularZone()),
    );
    await new Promise(r => setTimeout(r));

    dispatchFakeEvent(document, 'click');

    expect(inZoneSpy).toHaveBeenCalledWith(true);

    subscription.unsubscribe();
  }));
});

const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
  <mat-form-field [floatLabel]="floatLabel" [style.width.px]="width" [color]="theme">
    @if (hasLabel) {
      <mat-label>State</mat-label>
    }
    <input
      matInput
      placeholder="State"
      [matAutocomplete]="auto"
      [matAutocompletePosition]="position"
      [matAutocompleteDisabled]="autocompleteDisabled"
      [formControl]="stateCtrl">
  </mat-form-field>
  <mat-autocomplete
    #auto="matAutocomplete"
    [class]="panelClass"
    [displayWith]="displayFn"
    [disableRipple]="disableRipple"
    [requireSelection]="requireSelection"
    [aria-label]="ariaLabel"
    [aria-labelledby]="ariaLabelledby"
    (opened)="openedSpy()"
    (closed)="closedSpy()">
    @for (state of filteredStates; track state) {
      <mat-option
        [value]="state"
        [style.height.px]="state.height"
        [disabled]="state.disabled">
        <span>{{ state.code }}: {{ state.name }}</span>
      </mat-option>
    }
  </mat-autocomplete>
`;

@Component({template: SIMPLE_AUTOCOMPLETE_TEMPLATE, standalone: false})
class SimpleAutocomplete implements OnDestroy {
  stateCtrl = new FormControl<{name: string; code: string} | string | null>(null);
  filteredStates: any[];
  valueSub: Subscription;
  floatLabel = 'auto';
  position = 'auto';
  width: number;
  disableRipple = false;
  autocompleteDisabled = false;
  hasLabel = true;
  requireSelection = false;
  ariaLabel: string;
  ariaLabelledby: string;
  panelClass = 'class-one class-two';
  theme: string;
  openedSpy = jasmine.createSpy('autocomplete opened spy');
  closedSpy = jasmine.createSpy('autocomplete closed spy');

  @ViewChild(MatAutocompleteTrigger, {static: true}) trigger: MatAutocompleteTrigger;
  @ViewChild(MatAutocomplete) panel: MatAutocomplete;
  @ViewChild(MatFormField) formField: MatFormField;
  @ViewChildren(MatOption) options: QueryList<MatOption>;

  states: {code: string; name: string; height?: number; disabled?: boolean}[] = [
    {code: 'AL', name: 'Alabama'},
    {code: 'CA', name: 'California'},
    {code: 'FL', name: 'Florida'},
    {code: 'KS', name: 'Kansas'},
    {code: 'MA', name: 'Massachusetts'},
    {code: 'NY', name: 'New York'},
    {code: 'OR', name: 'Oregon'},
    {code: 'PA', name: 'Pennsylvania'},
    {code: 'TN', name: 'Tennessee'},
    {code: 'VA', name: 'Virginia'},
    {code: 'WY', name: 'Wyoming'},
  ];

  constructor() {
    this.filteredStates = this.states;
    this.valueSub = this.stateCtrl.valueChanges.subscribe(val => {
      this.filteredStates = val
        ? this.states.filter(s => s.name.match(new RegExp(val as string, 'gi')))
        : this.states;
    });
  }

  displayFn(value: any): string {
    return value ? value.name : value;
  }

  ngOnDestroy() {
    this.valueSub.unsubscribe();
  }
}


The autocomplete is a normal text input enhanced by a panel of suggested options.

### Simple autocomplete

Start by creating the autocomplete panel and the options displayed inside it. Each option should be
defined by a `mat-option` tag. Set each option's value property to whatever you'd like the value
of the text input to be when that option is selected.

Next, create the input and set the `matAutocomplete` input to refer to the template reference we assigned
to the autocomplete. Let's assume you're using the `formControl` directive from `ReactiveFormsModule` to
track the value of the input.

> Note: It is possible to use template-driven forms instead, if you prefer. We use reactive forms
in this example because it makes subscribing to changes in the input's value easy. For this
example, be sure to import `ReactiveFormsModule` from `@angular/forms` into your `NgModule`.
If you are unfamiliar with using reactive forms, you can read more about the subject in the
[Angular documentation](https://angular.dev/guide/forms/reactive-forms).

Now we'll need to link the text input to its panel. We can do this by exporting the autocomplete
panel instance into a local template variable (here we called it "auto"), and binding that variable
to the input's `matAutocomplete` property.

### Adding a custom filter

At this point, the autocomplete panel should be toggleable on focus and options should be
selectable. But if we want our options to filter when we type, we need to add a custom filter.

You can filter the options in any way you like based on the text input\*. Here we will perform a
simple string test on the option value to see if it matches the input value, starting from the
option's first letter. We already have access to the built-in `valueChanges` Observable on the
`FormControl`, so we can simply map the text input's values to the suggested options by passing
them through this filter. The resulting Observable, `filteredOptions`, can be added to the
template in place of the `options` property using the `async` pipe.

Below we are also priming our value change stream with an empty string so that the options are
filtered by that value on init (before there are any value changes).

\*For optimal accessibility, you may want to consider adding text guidance on the page to explain
filter criteria. This is especially helpful for screenreader users if you're using a non-standard
filter that doesn't limit matches to the beginning of the string.

### Setting separate control and display values

If you want the option's control value (what is saved in the form) to be different than the option's
display value (what is displayed in the text field), you'll need to set the `displayWith`
property on your autocomplete element. A common use case for this might be if you want to save your
data as an object, but display just one of the option's string properties.

To make this work, create a function on your component class that maps the control value to the
desired display value. Then bind it to the autocomplete's `displayWith` property.

### Require an option to be selected

By default, the autocomplete will accept the value that the user typed into the input field.
Instead, if you want to instead ensure that an option from the autocomplete was selected, you can
enable the `requireSelection` input on `mat-autocomplete`. This will change the behavior of
the autocomplete in the following ways:
1. If the user opens the autocomplete, changes its value, but doesn't select anything, the
autocomplete value will be reset back to `null`.
2. If the user opens and closes the autocomplete without changing the value, the old value will
be preserved.

This behavior can be configured globally using the `MAT_AUTOCOMPLETE_DEFAULT_OPTIONS`
injection token.

### Automatically highlighting the first option

If your use case requires for the first autocomplete option to be highlighted when the user opens
the panel, you can do so by setting the `autoActiveFirstOption` input on the `mat-autocomplete`
component. This behavior can be configured globally using the `MAT_AUTOCOMPLETE_DEFAULT_OPTIONS`
injection token.

### Autocomplete on a custom input element

While `mat-autocomplete` supports attaching itself to a `mat-form-field`, you can also set it on
any other `input` element using the `matAutocomplete` attribute. This allows you to customize what
the input looks like without having to bring in the extra functionality from `mat-form-field`.

### Attaching the autocomplete panel to a different element

By default the autocomplete panel will be attached to your input element, however in some cases you
may want it to attach to a different container element. You can change the element that the
autocomplete is attached to using the `matAutocompleteOrigin` directive together with the
`matAutocompleteConnectedTo` input:

```html
<div class="custom-wrapper-example" matAutocompleteOrigin #origin="matAutocompleteOrigin">
  <input
    matInput
    [formControl]="myControl"
    [matAutocomplete]="auto"
    [matAutocompleteConnectedTo]="origin">
</div>

<mat-autocomplete #auto="matAutocomplete">
  @for (option of options; track option) {
    <mat-option [value]="option">{{option}}</mat-option>
  }
</mat-autocomplete>
```

### Keyboard interaction
| Keyboard shortcut                      | Action                                                         |
|----------------------------------------|----------------------------------------------------------------|
| Down Arrow                  | Navigate to the next option.                                   |
| Up Arrow                    | Navigate to the previous option.                               |
| Enter                       | Select the active option.                                      |
| Escape                      | Close the autocomplete panel.                                  |
| Alt + Up Arrow   | Close the autocomplete panel.                                  |
| Alt + Down Arrow | Open the autocomplete panel if there are any matching options. |

### Option groups
`mat-option` can be collected into groups using the `mat-optgroup` element:

### Accessibility

`MatAutocomplete` implements the ARIA combobox interaction pattern. The text input trigger specifies
`role="combobox"` while the content of the pop-up applies `role="listbox"`. Because of this listbox
pattern, you should _not_ put other interactive controls, such as buttons or checkboxes, inside
an autocomplete option. Nesting interactive controls like this interferes with most assistive
technology.

Always provide an accessible label for the autocomplete. This can be done by using a
`<mat-label>` inside of `<mat-form-field>`, a native `<label>` element, the `aria-label`
attribute, or the `aria-labelledby` attribute.

`MatAutocomplete` preserves focus on the text trigger, using `aria-activedescendant` to support
navigation though the autocomplete options.

By default, `MatAutocomplete` displays a checkmark to identify the selected item. While you can hide
the checkmark indicator via `hideSingleSelectionIndicator`, this makes the component less accessible
by making it harder or impossible for users to visually identify selected items.


it('should update the panel direction if it changes for the trigger', () => {
    const dirProvider = {value: 'rtl', change: EMPTY};
    const rtlFixture = createComponent(SimpleAutocomplete, [
      {provide: Directionality, useFactory: () => dirProvider},
    ]);

    rtlFixture.detectChanges();
    rtlFixture.componentInstance.trigger.openPanel();
    rtlFixture.detectChanges();

    let boundingBox = overlayContainerElement.querySelector(
      '.cdk-overlay-connected-position-bounding-box',
    )!;
    expect(boundingBox.getAttribute('dir')).toEqual('rtl');

    rtlFixture.componentInstance.trigger.closePanel();
    rtlFixture.detectChanges();

    dirProvider.value = 'ltr';
    rtlFixture.componentInstance.trigger.openPanel();
    rtlFixture.detectChanges();

    boundingBox = overlayContainerElement.querySelector(
      '.cdk-overlay-connected-position-bounding-box',
    )!;
    expect(boundingBox.getAttribute('dir')).toEqual('ltr');
  });

  it('should be able to set a custom value for the `autocomplete` attribute', () => {
    const fixture = createComponent(AutocompleteWithNativeAutocompleteAttribute);
    const input = fixture.nativeElement.querySelector('input');

    fixture.detectChanges();

    expect(input.getAttribute('autocomplete')).toBe('changed');
  });

  it('should not throw when typing in an element with a null and disabled autocomplete', () => {
    const fixture = createComponent(InputWithoutAutocompleteAndDisabled);
    fixture.detectChanges();

    expect(() => {
      dispatchKeyboardEvent(fixture.nativeElement.querySelector('input'), 'keydown', SPACE);
      fixture.detectChanges();
    }).not.toThrow();
  });

  it('should clear the selected option if it no longer matches the input text while typing', waitForAsync(async () => {
    const fixture = createComponent(SimpleAutocomplete);
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    fixture.componentInstance.trigger.openPanel();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    // Select an option and reopen the panel.
    (overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));
    fixture.detectChanges();
    fixture.componentInstance.trigger.openPanel();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    expect(fixture.componentInstance.options.first.selected).toBe(true);

    const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
    input.value = '';
    typeInElement(input, 'Ala');
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    expect(fixture.componentInstance.options.first.selected).toBe(false);
  }));

  it('should not clear the selected option if it no longer matches the input text while typing with requireSelection', waitForAsync(async () => {
    const fixture = createComponent(SimpleAutocomplete);
    fixture.componentInstance.requireSelection = true;
    fixture.changeDetectorRef.markForCheck();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    fixture.componentInstance.trigger.openPanel();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    // Select an option and reopen the panel.
    (overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));
    fixture.detectChanges();
    fixture.componentInstance.trigger.openPanel();
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    expect(fixture.componentInstance.options.first.selected).toBe(true);

    const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
    input.value = '';
    typeInElement(input, 'Ala');
    fixture.detectChanges();
    await new Promise(r => setTimeout(r));

    expect(fixture.componentInstance.options.first.selected).toBe(true);
  }));


it('should preserve the value if a selection is required, and there are no options', waitForAsync(async () => {
      const input = fixture.nativeElement.querySelector('input');
      const {stateCtrl, trigger, states} = fixture.componentInstance;
      fixture.componentInstance.requireSelection = true;
      fixture.changeDetectorRef.markForCheck();
      stateCtrl.setValue(states[1]);
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      expect(input.value).toBe('California');
      expect(stateCtrl.value).toEqual({code: 'CA', name: 'California'});

      fixture.componentInstance.states = fixture.componentInstance.filteredStates = [];
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();

      trigger.openPanel();
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      const spy = jasmine.createSpy('optionSelected spy');
      const subscription = trigger.optionSelections.subscribe(spy);

      dispatchFakeEvent(document, 'click');
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      expect(input.value).toBe('California');
      expect(stateCtrl.value).toEqual({code: 'CA', name: 'California'});
      expect(spy).not.toHaveBeenCalled();
      subscription.unsubscribe();
    }));

    it('should clear the value if requireSelection is enabled and the user edits the input before clicking away', waitForAsync(async () => {
      const input = fixture.nativeElement.querySelector('input');
      const {stateCtrl, trigger} = fixture.componentInstance;
      fixture.componentInstance.requireSelection = true;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      // Simulate opening the input and clicking the first option.
      trigger.openPanel();
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));
      (overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
      await new Promise(r => setTimeout(r));
      fixture.detectChanges();

      expect(trigger.panelOpen).toBe(false);
      expect(input.value).toBe('Alabama');
      expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'});

      // Simulate pressing backspace while focus is still on the input.
      dispatchFakeEvent(input, 'keydown');
      input.value = 'Alabam';
      fixture.detectChanges();
      dispatchFakeEvent(input, 'input');
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      expect(trigger.panelOpen).toBe(true);
      expect(input.value).toBe('Alabam');
      expect(stateCtrl.value).toEqual({code: 'AL', name: 'Alabama'});

      // Simulate clicking away.
      input.blur();
      dispatchFakeEvent(document, 'click');
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      expect(trigger.panelOpen).toBe(false);
      expect(input.value).toBe('');
      expect(stateCtrl.value).toBe(null);
    }));
  });

  describe('panel closing', () => {
    let fixture: ComponentFixture<SimpleAutocomplete>;
    let input: HTMLInputElement;
    let trigger: MatAutocompleteTrigger;
    let closingActionSpy: jasmine.Spy;
    let closingActionsSub: Subscription;

    beforeEach(fakeAsync(() => {
      fixture = createComponent(SimpleAutocomplete);
      fixture.detectChanges();

      trigger = fixture.componentInstance.trigger;
      closingActionSpy = jasmine.createSpy('closing action listener');
      closingActionsSub = trigger.panelClosingActions.subscribe(closingActionSpy);

      input = fixture.debugElement.query(By.css('input'))!.nativeElement;

      fixture.componentInstance.trigger.openPanel();
      fixture.detectChanges();
      flush();
    }));

    afterEach(() => {
      closingActionsSub.unsubscribe();
    });

    it('should emit panel close event when clicking away', () => {
      expect(closingActionSpy).not.toHaveBeenCalled();
      dispatchFakeEvent(document, 'click');
      expect(closingActionSpy).toHaveBeenCalledWith(null);
    });

    it('should emit panel close event when tabbing out', () => {
      const tabEvent = createKeyboardEvent('keydown', TAB);
      input.focus();

      expect(closingActionSpy).not.toHaveBeenCalled();
      trigger._handleKeydown(tabEvent);
      expect(closingActionSpy).toHaveBeenCalledWith(null);
    });

    it('should not emit when tabbing away from a closed panel', waitForAsync(async () => {
      const tabEvent = createKeyboardEvent('keydown', TAB);

      input.focus();
      await new Promise(r => setTimeout(r));

      trigger._handleKeydown(tabEvent);

      // Ensure that it emitted once while the panel was open.
      expect(closingActionSpy).toHaveBeenCalledTimes(1);

      trigger._handleKeydown(tabEvent);

      // Ensure that it didn't emit again when tabbing out again.
      expect(closingActionSpy).toHaveBeenCalledTimes(1);
    }));

    it('should emit panel close event when selecting an option', () => {
      const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;

      expect(closingActionSpy).not.toHaveBeenCalled();
      option.click();
      expect(closingActionSpy).toHaveBeenCalledWith(jasmine.any(MatOptionSelectionChange));
    });

    it('should close the panel when pressing escape', () => {
      expect(closingActionSpy).not.toHaveBeenCalled();
      dispatchKeyboardEvent(document.body, 'keydown', ESCAPE);
      expect(closingActionSpy).toHaveBeenCalledWith(null);
    });

    // TODO(mmalerba): This test previously only passed because it wasn't properly flushed.
    //  We should figure out if this is indeed the desired behavior, and if so fix the
    //  implementation.
    // tslint:disable-next-line:ban
    xit('should not prevent escape key propagation when there are no options', waitForAsync(async () => {
      fixture.componentInstance.filteredStates = fixture.componentInstance.states = [];
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      const event = createKeyboardEvent('keydown', ESCAPE);
      spyOn(event, 'stopPropagation').and.callThrough();
      dispatchEvent(document.body, event);
      fixture.detectChanges();

      expect(event.stopPropagation).not.toHaveBeenCalled();
    }));
  });

  describe('without matInput', () => {
    let fixture: ComponentFixture<AutocompleteWithNativeInput>;

    beforeEach(() => {
      fixture = createComponent(AutocompleteWithNativeInput);
      fixture.detectChanges();
    });

    it('should not throw when clicking outside', fakeAsync(() => {
      dispatchFakeEvent(fixture.debugElement.query(By.css('input'))!.nativeElement, 'focus');
      fixture.detectChanges();
      flush();

      expect(() => dispatchFakeEvent(document, 'click')).not.toThrow();
    }));
  });

  describe('with panel classes in the default options', () => {
    it('should apply them if provided as string', () => {
      const fixture = createComponent(SimpleAutocomplete, [
        {provide: MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, useValue: {overlayPanelClass: 'default1'}},
      ]);

      fixture.detectChanges();
      fixture.componentInstance.trigger.openPanel();
      fixture.detectChanges();

      const panelClassList = overlayContainerElement.querySelector('.cdk-overlay-pane')!.classList;
      expect(panelClassList).toContain('default1');
    });

    it('should apply them if provided as array', () => {
      const fixture = createComponent(SimpleAutocomplete, [
        {
          provide: MAT_AUTOCOMPLETE_DEFAULT_OPTIONS,
          useValue: {overlayPanelClass: ['default1', 'default2']},
        },
      ]);

      fixture.detectChanges();
      fixture.componentInstance.trigger.openPanel();
      fixture.detectChanges();

      const panelClassList = overlayContainerElement.querySelector('.cdk-overlay-pane')!.classList;
      expect(panelClassList).toContain('default1');
      expect(panelClassList).toContain('default2');
    });
  });


describe('misc', () => {
    it('should allow basic use without any forms directives', () => {
      expect(() => {
        const fixture = createComponent(AutocompleteWithoutForms);
        fixture.detectChanges();

        const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
        typeInElement(input, 'd');
        fixture.detectChanges();

        const options = overlayContainerElement.querySelectorAll(
          'mat-option',
        ) as NodeListOf<HTMLElement>;
        expect(options.length).toBe(1);
      }).not.toThrowError();
    });

    it('should display an empty input when the value is undefined with ngModel', () => {
      const fixture = createComponent(AutocompleteWithNgModel);
      fixture.detectChanges();

      expect(fixture.debugElement.query(By.css('input'))!.nativeElement.value).toBe('');
    });

    it('should display the number when the selected option is the number zero', fakeAsync(() => {
      const fixture = createComponent(AutocompleteWithNumbers);

      fixture.componentInstance.selectedNumber = 0;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();
      tick();

      expect(fixture.debugElement.query(By.css('input'))!.nativeElement.value).toBe('0');
    }));

    it('should work when input is wrapped in ngIf', () => {
      const fixture = createComponent(NgIfAutocomplete);
      fixture.detectChanges();

      dispatchFakeEvent(fixture.debugElement.query(By.css('input'))!.nativeElement, 'focusin');
      fixture.detectChanges();

      expect(fixture.componentInstance.trigger.panelOpen)
        .withContext(`Expected panel state to read open when input is focused.`)
        .toBe(true);
      expect(overlayContainerElement.textContent)
        .withContext(`Expected panel to display when input is focused.`)
        .toContain('One');
      expect(overlayContainerElement.textContent)
        .withContext(`Expected panel to display when input is focused.`)
        .toContain('Two');
    });

    it('should filter properly with ngIf after setting the active item', () => {
      const fixture = createComponent(NgIfAutocomplete);
      fixture.detectChanges();

      fixture.componentInstance.trigger.openPanel();
      fixture.detectChanges();

      const DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
      fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
      fixture.detectChanges();

      const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
      typeInElement(input, 'o');
      fixture.detectChanges();

      expect(fixture.componentInstance.matOptions.length).toBe(2);
    });

    it('should throw if the user attempts to open the panel too early', () => {
      const fixture = createComponent(AutocompleteWithoutPanel);
      fixture.detectChanges();

      expect(() => {
        fixture.componentInstance.trigger.openPanel();
      }).toThrow(getMatAutocompleteMissingPanelError());
    });

    it('should not throw on init, even if the panel is not defined', fakeAsync(() => {
      expect(() => {
        const fixture = createComponent(AutocompleteWithoutPanel);
        fixture.componentInstance.control.setValue('Something');
        fixture.detectChanges();
        tick();
      }).not.toThrow();
    }));

    it('should transfer the mat-autocomplete classes to the panel element', fakeAsync(() => {
      const fixture = createComponent(SimpleAutocomplete);
      fixture.detectChanges();

      fixture.componentInstance.trigger.openPanel();
      tick();
      fixture.detectChanges();

      const autocomplete = fixture.debugElement.nativeElement.querySelector('mat-autocomplete');
      const panel = overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!;

      expect(autocomplete.classList).not.toContain('class-one');
      expect(autocomplete.classList).not.toContain('class-two');

      expect(panel.classList).toContain('class-one');
      expect(panel.classList).toContain('class-two');
    }));

    it('should remove old classes when the panel class changes', fakeAsync(() => {
      const fixture = createComponent(SimpleAutocomplete);
      fixture.detectChanges();

      fixture.componentInstance.trigger.openPanel();
      tick();
      fixture.detectChanges();

      const classList = overlayContainerElement.querySelector(
        '.mat-mdc-autocomplete-panel',
      )!.classList;

      expect(classList).toContain('mat-mdc-autocomplete-visible');
      expect(classList).toContain('class-one');
      expect(classList).toContain('class-two');

      fixture.componentInstance.panelClass = 'class-three class-four';
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();

      expect(classList).not.toContain('class-one');
      expect(classList).not.toContain('class-two');
      expect(classList).toContain('mat-mdc-autocomplete-visible');
      expect(classList).toContain('class-three');
      expect(classList).toContain('class-four');
    }));

    it('should reset correctly when closed programmatically', waitForAsync(async () => {
      const scrolledSubject = new Subject();
      const fixture = createComponent(SimpleAutocomplete, [
        {
          provide: ScrollDispatcher,
          useValue: {scrolled: () => scrolledSubject},
        },
        {
          provide: MAT_AUTOCOMPLETE_SCROLL_STRATEGY,
          useFactory: (overlay: Overlay) => () => overlay.scrollStrategies.close(),
          deps: [Overlay],
        },
      ]);

      fixture.detectChanges();
      const trigger = fixture.componentInstance.trigger;

      trigger.openPanel();
      fixture.detectChanges();
      await new Promise(r => setTimeout(r));

      expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true);

      scrolledSubject.next();
      fixture.detectChanges();

      expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false);
    }));

    it('should handle autocomplete being attached to number inputs', () => {
      const fixture = createComponent(AutocompleteWithNumberInputAndNgModel);
      fixture.detectChanges();
      const input = fixture.debugElement.query(By.css('input'))!.nativeElement;

      typeInElement(input, '1337');
      fixture.detectChanges();

      expect(fixture.componentInstance.selectedValue).toBe(1337);
    });

    it('should not focus the option when DOWN key is pressed', () => {
      const fixture = createComponent(SimpleAutocomplete);
      const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
      fixture.detectChanges();
      const spy = spyOn(console, 'error');

      dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
      dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW);
      fixture.detectChanges();

      // Note: for some reason the error here gets logged using console.error, rather than being
      // thrown, hence why we use a spy to assert against it, rather than `.not.toThrow`.
      expect(spy).not.toHaveBeenCalled();
    });
  });


@Component({
  template: `
    @if (isVisible) {
<mat-form-field>
      <input matInput placeholder="Choose" [matAutocomplete]="auto" [formControl]="optionCtrl">
    </mat-form-field>
}

    <mat-autocomplete #auto="matAutocomplete">
      @for (option of filteredOptions | async; track option) {
  <mat-option [value]="option">
         {{option}}
      </mat-option>
}
    </mat-autocomplete>
  `,
  standalone: false,
})
class NgIfAutocomplete {
  optionCtrl = new FormControl('');
  filteredOptions: Observable<any>;
  isVisible = true;
  options = ['One', 'Two', 'Three'];

  @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
  @ViewChildren(MatOption) matOptions: QueryList<MatOption>;

  constructor() {
    this.filteredOptions = this.optionCtrl.valueChanges.pipe(
      startWith(null),
      map(val => {
        return val
          ? this.options.filter(option => new RegExp(val, 'gi').test(option))
          : this.options.slice();
      }),
    );
  }
}

@Component({
  template: `
    <mat-form-field>
      <input matInput placeholder="State" [matAutocomplete]="auto"
      (input)="onInput($event.target?.value)">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @for (state of filteredStates; track state) {
        <mat-option [value]="state">
           {{ state }}
        </mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithoutForms {
  filteredStates: any[];
  states = ['Alabama', 'California', 'Florida'];

  constructor() {
    this.filteredStates = this.states.slice();
  }

  onInput(value: any) {
    this.filteredStates = this.states.filter(s => new RegExp(value, 'gi').test(s));
  }
}

@Component({
  template: `
    <mat-form-field>
      <input matInput placeholder="State" [matAutocomplete]="auto" [(ngModel)]="selectedState"
      (ngModelChange)="onInput($event)">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @for (state of filteredStates; track state) {
        <mat-option [value]="state">
          {{ state }}
        </mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithNgModel {
  filteredStates: any[];
  selectedState: string;
  states = ['New York', 'Washington', 'Oregon'];

  @ViewChild(MatAutocompleteTrigger, {static: true}) trigger: MatAutocompleteTrigger;

  constructor() {
    this.filteredStates = this.states.slice();
  }

  onInput(value: any) {
    this.filteredStates = this.states.filter(s => new RegExp(value, 'gi').test(s));
  }
}

@Component({
  template: `
    <mat-form-field>
      <input matInput placeholder="Number" [matAutocomplete]="auto" [(ngModel)]="selectedNumber">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @for (number of numbers; track number) {
        <mat-option [value]="number">
          {{ number }}
        </mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithNumbers {
  selectedNumber: number;
  numbers = [0, 1, 2];
}

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <mat-form-field>
      <input type="text" matInput [matAutocomplete]="auto">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @for (option of options; track option) {
        <mat-option [value]="option">{{ option }}</mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithOnPushDelay implements OnInit {
  @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
  options: string[];

  ngOnInit() {
    setTimeout(() => {
      this.options = ['One'];
    }, 1000);
  }
}

@Component({
  template: `
    <input placeholder="Choose" [matAutocomplete]="auto" [formControl]="optionCtrl">

    <mat-autocomplete #auto="matAutocomplete">
      @for (option of filteredOptions | async; track option) {
        <mat-option [value]="option">{{option}}</mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithNativeInput {
  optionCtrl = new FormControl('');
  filteredOptions: Observable<any>;
  options = ['En', 'To', 'Tre', 'Fire', 'Fem'];

  @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
  @ViewChildren(MatOption) matOptions: QueryList<MatOption>;

  constructor() {
    this.filteredOptions = this.optionCtrl.valueChanges.pipe(
      startWith(null),
      map(val => {
        return val
          ? this.options.filter(option => new RegExp(val, 'gi').test(option))
          : this.options.slice();
      }),
    );
  }
}

@Component({
  template: `<input placeholder="Choose" [matAutocomplete]="auto" [formControl]="control">`,
  standalone: false,
})
class AutocompleteWithoutPanel {
  @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
  control = new FormControl('');
}

@Component({
  template: `
    <mat-form-field>
      <input matInput placeholder="State" [matAutocomplete]="auto" [(ngModel)]="selectedState">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @for (group of stateGroups; track group) {
        <mat-optgroup [label]="group.title">
          @for (state of group.states; track state) {
            <mat-option [value]="state">
              {{ state }}
            </mat-option>
          }
        </mat-optgroup>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithGroups {
  @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
  selectedState: string;
  stateGroups = [
    {
      title: 'One',
      states: ['Alabama', 'California', 'Florida', 'Oregon'],
    },
    {
      title: 'Two',
      states: ['Kansas', 'Massachusetts', 'New York', 'Pennsylvania'],
    },
    {
      title: 'Three',
      states: ['Tennessee', 'Virginia', 'Wyoming', 'Alaska'],
    },
  ];
}

@Component({
  template: `
    <mat-form-field>
      <input matInput placeholder="State" [matAutocomplete]="auto" [(ngModel)]="selectedState">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @if (true) {
        @for (group of stateGroups; track group) {
          <mat-optgroup [label]="group.title">
            @for (state of group.states; track state) {
              <mat-option [value]="state">
                {{ state }}
              </mat-option>
            }
          </mat-optgroup>
        }
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithIndirectGroups extends AutocompleteWithGroups {}

@Component({
  template: `
    <mat-form-field>
      <input matInput placeholder="State" [matAutocomplete]="auto" [(ngModel)]="selectedState">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete" (optionSelected)="optionSelected($event)">
      @for (state of states; track state) {
        <mat-option [value]="state">
          {{ state }}
        </mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithSelectEvent {
  selectedState: string;
  states = ['New York', 'Washington', 'Oregon'];
  optionSelected = jasmine.createSpy('optionSelected callback');

  @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
  @ViewChild(MatAutocomplete) autocomplete: MatAutocomplete;
}

@Component({
  template: `
    <input [formControl]="formControl" [matAutocomplete]="auto"/>
    <mat-autocomplete #auto="matAutocomplete"></mat-autocomplete>
  `,
  standalone: false,
})
class PlainAutocompleteInputWithFormControl {
  formControl = new FormControl('');
}

@Component({
  template: `
    <mat-form-field>
      <input type="number" matInput [matAutocomplete]="auto" [(ngModel)]="selectedValue">
    </mat-form-field>

    <mat-autocomplete #auto="matAutocomplete">
      @for (value of values; track value) {
        <mat-option [value]="value">{{value}}</mat-option>
      }
    </mat-autocomplete>
  `,
  standalone: false,
})
class AutocompleteWithNumberInputAndNgModel {
  selectedValue: number;
  values = [1, 2, 3];
}


Chips allow users to view information, make selections, filter content, and enter data.

### Static Chips

Chips are always used inside a container. To create chips, start with a `<mat-chip-set>` element. Then, nest `<mat-chip>` elements inside the `<mat-chip-set>`.

<!-- example(chips-overview) -->

By default, `<mat-chip>` renders a chip with Material Design styles applied. For a chip with no styles applied, use `<mat-basic-chip>`.

#### Disabled appearance

Although `<mat-chip>` is not interactive, you can set the `disabled` Input to give it disabled appearance.

```html
<mat-chip disabled>Orange</mat-chip>
```

### Selection Chips

Use `<mat-chip-listbox>` and `<mat-chip-option>` for selecting one or many items from a list. Start with creating a `<mat-chip-listbox>` element. If the user may select more than one option, add the `multiple` attribute. Nest a `<mat-chip-option>` element inside the `<mat-chip-listbox>` for each available option.

#### Disabled `<mat-chip-option>`

Use the `disabled` Input to disable a `<mat-chip-option>`. This gives the `<mat-chip-option>` a disabled appearance and prevents the user from interacting with it.

```html
<mat-chip-option disabled>Orange</mat-chip-option>
```

#### Keyboard Interactions

Users can move through the chips using the arrow keys and select/deselect them with space. Chips also gain focus when clicked, ensuring keyboard navigation starts at the currently focused chip.

### Chips connected to an input field

Use `<mat-chip-grid>` and `<mat-chip-row>` for assisting users with text entry.

Chips are always used inside a container. To create chips connected to an input field, start by creating a `<mat-chip-grid>` as the container. Add an `<input/>` element, and register it to the `<mat-chip-grid>` by passing the `matChipInputFor` Input. Always use an `<input/>` element with `<mat-chip-grid>`. Nest a `<mat-chip-row>` element inside the `<mat-chip-grid>` for each piece of data entered by the user. An example of using chips for text input.

<!-- example(chips-input) -->

### Use with `@angular/forms`
Chips are compatible with `@angular/forms` and supports both `FormsModule`
and `ReactiveFormsModule`.

<!-- example(chips-template-form) -->
<!-- example(chips-reactive-form) -->

#### Disabled `<mat-chip-row>`

Use the `disabled` Input to disable a `<mat-chip-row>`. This gives the `<mat-chip-row>` a disabled appearance and prevents the user from interacting with it.

```html
<mat-chip-row disabled>Orange</mat-chip-row>
```

#### Keyboard Interactions

Users can move through the chips using the arrow keys and select/deselect them with the space. Chips also gain focus when clicked, ensuring keyboard navigation starts at the appropriate chip.

Users can press delete to remove a chip. Pressing delete triggers the `removed` Output on the chip, so be sure to implement `removed` if you require that functionality.

#### Autocomplete

A `<mat-chip-grid>` can be combined with `<mat-autocomplete>` to enable free-form chip input with suggestions.

<!-- example(chips-autocomplete) -->

### Icons

You can add icons to chips to identify entities (like individuals) and provide additional functionality.

#### Adding up to two icons with content projection

You can add two additional icons to an individual chip. A chip has two slots to display icons using content projection. All variants of chips support adding icons including `<mat-chip>`, `<mat-chip-option>`, and `<mat-chip-row>`.

A chip has a front slot for adding an avatar image. To add an avatar, nest an element with `matChipAvatar` attribute inside of `<mat-chip>`.

<!-- example(chips-avatar) -->

You can add an additional icon to the back slot by nesting an element with either the `matChipTrailingIcon` or `matChipRemove` attribute.

#### Remove Button

Sometimes the end user would like the ability to remove a chip. You can provide that functionality using `matChipRemove`. `matChipRemove` renders to the back slot of a chip and triggers the `removed` Output when clicked.

To create a remove button, nest a `<button>` element with `matChipRemove` attribute inside the `<mat-chip-option>`. Be sure to implement the `removed` Output.

```html
 <mat-chip-option>
  Orange
  <button matChipRemove aria-label="Remove orange">
    <mat-icon>cancel</mat-icon>
  </button>
</mat-chip-option>
```

See the [accessibility](#accessibility) section for best practices on implementing the `removed` Output and creating accessible icons.

### Orientation

By default, chips are displayed horizontally. To stack chips vertically, apply the `mat-mdc-chip-set-stacked` class to `<mat-chip-set>`, `<mat-chip-listbox>` or `<mat-chip-grid>`.

<!-- example(chips-stacked) -->

### Specifying global configuration defaults

Use the `MAT_CHIPS_DEFAULT_OPTIONS` token to specify default options for the chips module.

```html
@NgModule({
  providers: [
    {
      provide: MAT_CHIPS_DEFAULT_OPTIONS,
      useValue: {
        separatorKeyCodes: [COMMA, SPACE]
      }
    }
  ]
})
```

### Interaction Patterns

The chips components support 3 user interaction patterns, each with its own container and chip elements:

#### Listbox

`<mat-chip-listbox>` and `<mat-chip-option>` : These elements implement a listbox accessibility pattern. Use them to present set of user selectable options.

```html
<mat-chip-listbox aria-label="select a shirt size">
  <mat-chip-option> Small </mat-chip-option>
  <mat-chip-option> Medium </mat-chip-option>
  <mat-chip-option> Large </mat-chip-option>
</mat-chip-listbox>
```

#### Text Entry

`<mat-chip-grid>` and `<mat-chip-row>` : These elements implement a grid accessibility pattern. Use them as part of a free form input that allows users to enter text to add chips.

```html
<mat-form-field>
  <mat-chip-grid #myChipGrid [(ngModel)]="mySelection"
    aria-label="enter sandwich fillings">
    @for (filling of fillings; track filling) {
      <mat-chip-row (removed)="remove(filling)">
        {{filling.name}}
        <button matChipRemove>
          <mat-icon>cancel</mat-icon>
        </button>
      </mat-chip-row>
    }
    <input [matChipInputFor]="myChipGrid"
           [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
           (matChipInputTokenEnd)="add($event)" />
  </mat-chip-grid>
</mat-form-field>
```

#### Static Content

`<mat-chip-set>` and `<mat-chip>` as an unordered list : Present a list of items that are not interactive. This interaction pattern mimics using `ul` and `li` elements. Apply role="list" to the `<mat-list>`. Apply role="listitem" to each `<mat-list-item>`.

```html
<mat-chip-set role="list">
  <mat-chip role="listitem"> Sugar </mat-chip>
  <mat-chip role="listitem"> Spice </mat-chip>
  <mat-chip role="listitem"> Everything Nice </mat-chip>
</mat-chip-set>
```

`<mat-chip-set>` and `<mat-chip>` : These elements do not implement any specific accessibility pattern. Add the appropriate accessibility depending on the context. Note that Angular Material does not intend `<mat-chip>`, `<mat-basic-chip>`, and `<mat-chip-set>` to be interactive.

```html
<mat-chip-set>
  <mat-chip> John </mat-chip>
  <mat-chip> Paul </mat-chip>
  <mat-chip> James </mat-chip>
</mat-chip-set>
```

### Accessibility

The [Interaction Patterns](#interaction-patterns) section describes the three variants of chips available. Choose the chip variant that best matches your use case.

For both MatChipGrid and MatChipListbox, always apply an accessible label to the control via `aria-label` or `aria-labelledby`.

Always apply MatChipRemove to a `<button>` element, never a `<mat-icon>` element.

When using MatChipListbox, never nest other interactive controls inside of the `<mat-chip-option>` element. Nesting controls degrades the experience for assistive technology users.

By default, `MatChipListbox` displays a checkmark to identify selected items. While you can hide the checkmark indicator for single-selection via `hideSingleSelectionIndicator`, this makes the component less accessible by making it harder or impossible for users to visually identify selected items.

When a chip is editable, provide instructions to assistive technology how to edit the chip using a keyboard. One way to accomplish this is adding an `aria-description` attribute with instructions to press enter to edit the chip.


{
  protected _elementRef =
    inject<ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>>(ElementRef);
  protected _platform = inject(Platform);
  ngControl = inject(NgControl, {optional: true, self: true})!;
  private _autofillMonitor = inject(AutofillMonitor);
  private _ngZone = inject(NgZone);
  protected _formField? = inject<MatFormField>(MAT_FORM_FIELD, {optional: true});

  protected _uid = `mat-input-${nextUniqueId++}`;
  protected _previousNativeValue: any;
  private _inputValueAccessor: {value: any};
  private _signalBasedValueAccessor?: {value: WritableSignal<any>};
  private _previousPlaceholder: string | null;
  private _errorStateTracker: _ErrorStateTracker;
  private _webkitBlinkWheelListenerAttached = false;
  private _config = inject(MAT_INPUT_CONFIG, {optional: true});

  /** Whether the component is being rendered on the server. */
  readonly _isServer: boolean;

  /** Whether the component is a native html select. */
  readonly _isNativeSelect: boolean;

  /** Whether the component is a textarea. */
  readonly _isTextarea: boolean;

  /** Whether the input is inside of a form field. */
  readonly _isInFormField: boolean;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  focused: boolean = false;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  readonly stateChanges: Subject<void> = new Subject<void>();

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  controlType: string = 'mat-input';

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  autofilled = false;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  @Input()
  get disabled(): boolean {
    return this._disabled;
  }
  set disabled(value: BooleanInput) {
    this._disabled = coerceBooleanProperty(value);

    // Browsers may not fire the blur event if the input is disabled too quickly.
    // Reset from here to ensure that the element doesn't become stuck.
    if (this.focused) {
      this.focused = false;
      this.stateChanges.next();
    }
  }
  protected _disabled = false;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  @Input()
  get id(): string {
    return this._id;
  }
  set id(value: string) {
    this._id = value || this._uid;
  }
  protected _id: string;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  @Input() placeholder: string;

  /**
   * Name of the input.
   * @docs-private
   */
  @Input() name: string;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  @Input()
  get required(): boolean {
    return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false;
  }
  set required(value: BooleanInput) {
    this._required = coerceBooleanProperty(value);
  }
  protected _required: boolean | undefined;

  /** Input type of the element. */
  @Input()
  get type(): string {
    return this._type;
  }
  set type(value: string) {
    this._type = value || 'text';
    this._validateType();

    // When using Angular inputs, developers are no longer able to set the properties on the native
    // input element. To ensure that bindings for `type` work, we need to sync the setter
    // with the native property. Textarea elements don't support the type property or attribute.
    if (!this._isTextarea && getSupportedInputTypes().has(this._type)) {
      (this._elementRef.nativeElement as HTMLInputElement).type = this._type;
    }

    this._ensureWheelDefaultBehavior();
  }
  protected _type = 'text';

  /** An object used to control when error messages are shown. */
  @Input()
  get errorStateMatcher() {
    return this._errorStateTracker.matcher;
  }
  set errorStateMatcher(value: ErrorStateMatcher) {
    this._errorStateTracker.matcher = value;
  }

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  @Input('aria-describedby') userAriaDescribedBy: string;

  /**
   * Implemented as part of MatFormFieldControl.
   * @docs-private
   */
  @Input()
  get value(): string {
    return this._signalBasedValueAccessor
      ? this._signalBasedValueAccessor.value()
      : this._inputValueAccessor.value;
  }
  set value(value: any) {
    if (value !== this.value) {
      if (this._signalBasedValueAccessor) {
        this._signalBasedValueAccessor.value.set(value);
      } else {
        this._inputValueAccessor.value = value;
      }

      this.stateChanges.next();
    }
  }

  /** Whether the element is readonly. */
  @Input()
  get readonly(): boolean {
    return this._readonly;
  }
  set readonly(value: BooleanInput) {
    this._readonly = coerceBooleanProperty(value);
  }
  private _readonly = false;

  /** Whether the input should remain interactive when it is disabled. */
  @Input({transform: booleanAttribute})
  disabledInteractive: boolean;

  /** Whether the input is in an error state. */
  get errorState() {
    return this._errorStateTracker.errorState;
  }
  set errorState(value: boolean) {
    this._errorStateTracker.errorState = value;
  }

  protected _neverEmptyInputTypes = [
    'date',
    'datetime',
    'datetime-local',
    'month',
    'time',
    'week',
  ].filter(t => getSupportedInputTypes().has(t));

  constructor(...args: unknown[]);

  constructor() {
    const parentForm = inject(NgForm, {optional: true});
    const parentFormGroup = inject(FormGroupDirective, {optional: true});
    const defaultErrorStateMatcher = inject(ErrorStateMatcher);
    const accessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true});

    const element = this._elementRef.nativeElement;
    const nodeName = element.nodeName.toLowerCase();

    if (accessor) {
      if (isSignal(accessor.value)) {
        this._signalBasedValueAccessor = accessor;
      } else {
        this._inputValueAccessor = accessor;
      }
    } else {
      // If no input value accessor was explicitly specified, use the element as the input value
      // accessor.
      this._inputValueAccessor = element;
    }

    this._previousNativeValue = this.value;

    // Force setter to be called in case id was not specified.
    this.id = this.id;

    // On some versions of iOS the caret gets stuck in the wrong place when holding down the delete
    // key. In order to get around this we need to "jiggle" the caret loose. Since this bug only
    // exists on iOS, we only bother to install the listener on iOS.
    if (this._platform.IOS) {
      this._ngZone.runOutsideAngular(() => {
        element.addEventListener('keyup', this._iOSKeyupListener);
      });
    }

    this._errorStateTracker = new _ErrorStateTracker(
      defaultErrorStateMatcher,
      this.ngControl,
      parentFormGroup,
      parentForm,
      this.stateChanges,
    );
    this._isServer = !this._platform.isBrowser;
    this._isNativeSelect = nodeName === 'select';
    this._isTextarea = nodeName === 'textarea';
    this._isInFormField = !!this._formField;
    this.disabledInteractive = this._config?.disabledInteractive || false;

    if (this._isNativeSelect) {
      this.controlType = (element as HTMLSelectElement).multiple
        ? 'mat-native-select-multiple'
        : 'mat-native-select';
    }

    if (this._signalBasedValueAccessor) {
      effect(() => {
        // Read the value so the effect can register the dependency.
        this._signalBasedValueAccessor!.value();
        this.stateChanges.next();
      });
    }
  }

  ngAfterViewInit() {
    if (this._platform.isBrowser) {
      this._autofillMonitor.monitor(this._elementRef.nativeElement).subscribe(event => {
        this.autofilled = event.isAutofilled;
        this.stateChanges.next();
      });
    }
  }

  ngOnChanges() {
    this.stateChanges.next();
  }

  ngOnDestroy() {
    this.stateChanges.complete();

    if (this._platform.isBrowser) {
      this._autofillMonitor.stopMonitoring(this._elementRef.nativeElement);
    }

    if (this._platform.IOS) {
      this._elementRef.nativeElement.removeEventListener('keyup', this._iOSKeyupListener);
    }

    if (this._webkitBlinkWheelListenerAttached) {
      this._elementRef.nativeElement.removeEventListener('wheel', this._webkitBlinkWheelListener);
    }
  }


Please see the official documentation at https://material.angular.io/components/component/input


class MatSlideToggle
  implements OnDestroy, AfterContentInit, OnChanges, ControlValueAccessor, Validator
{
  private _elementRef = inject(ElementRef);
  protected _focusMonitor = inject(FocusMonitor);
  protected _changeDetectorRef = inject(ChangeDetectorRef);
  defaults = inject<MatSlideToggleDefaultOptions>(MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS);

  private _onChange = (_: any) => {};
  private _onTouched = () => {};
  private _validatorOnChange = () => {};

  private _uniqueId: string;
  private _checked: boolean = false;

  private _createChangeEvent(isChecked: boolean) {
    return new MatSlideToggleChange(this, isChecked);
  }

  /** Unique ID for the label element. */
  _labelId: string;

  /** Returns the unique id for the visual hidden button. */
  get buttonId(): string {
    return `${this.id || this._uniqueId}-button`;
  }

  /** Reference to the MDC switch element. */
  @ViewChild('switch') _switchElement: ElementRef<HTMLElement>;

  /** Focuses the slide-toggle. */
  focus(): void {
    this._switchElement.nativeElement.focus();
  }
  /** Whether noop animations are enabled. */
  _noopAnimations: boolean;

  /** Whether the slide toggle is currently focused. */
  _focused: boolean;

  /** Name value will be applied to the input element if present. */
  @Input() name: string | null = null;

  /** A unique id for the slide-toggle input. If none is supplied, it will be auto-generated. */
  @Input() id: string;

  /** Whether the label should appear after or before the slide-toggle. Defaults to 'after'. */
  @Input() labelPosition: 'before' | 'after' = 'after';

  /** Used to set the aria-label attribute on the underlying input element. */
  @Input('aria-label') ariaLabel: string | null = null;

  /** Used to set the aria-labelledby attribute on the underlying input element. */
  @Input('aria-labelledby') ariaLabelledby: string | null = null;

  /** Used to set the aria-describedby attribute on the underlying input element. */
  @Input('aria-describedby') ariaDescribedby: string;

  /** Whether the slide-toggle is required. */
  @Input({transform: booleanAttribute}) required: boolean;

  // TODO(crisbeto): this should be a ThemePalette, but some internal apps were abusing
  // the lack of type checking previously and assigning random strings.
  /**
   * Theme color of the slide toggle. This API is supported in M2 themes only,
   * it has no effect in M3 themes.
   *
   * For information on applying color variants in M3, see
   * https://material.angular.io/guide/theming#using-component-color-variants.
   */
  @Input() color: string | undefined;

  /** Whether the slide toggle is disabled. */
  @Input({transform: booleanAttribute}) disabled: boolean = false;

  /** Whether the slide toggle has a ripple. */
  @Input({transform: booleanAttribute}) disableRipple: boolean = false;

  /** Tabindex of slide toggle. */
  @Input({transform: (value: unknown) => (value == null ? 0 : numberAttribute(value))})
  tabIndex: number = 0;

  /** Whether the slide-toggle element is checked or not. */
  @Input({transform: booleanAttribute})
  get checked(): boolean {
    return this._checked;
  }
  set checked(value: boolean) {
    this._checked = value;
    this._changeDetectorRef.markForCheck();
  }

  /** Whether to hide the icon inside of the slide toggle. */
  @Input({transform: booleanAttribute}) hideIcon: boolean;

  /** Whether the slide toggle should remain interactive when it is disabled. */
  @Input({transform: booleanAttribute}) disabledInteractive: boolean;

  /** An event will be dispatched each time the slide-toggle changes its value. */
  @Output() readonly change = new EventEmitter<MatSlideToggleChange>();

  /**
   * An event will be dispatched each time the slide-toggle input is toggled.
   * This event is always emitted when the user toggles the slide toggle, but this does not mean
   * the slide toggle's value has changed.
   */
  @Output() readonly toggleChange: EventEmitter<void> = new EventEmitter<void>();

  /** Returns the unique id for the visual hidden input. */
  get inputId(): string {
    return `${this.id || this._uniqueId}-input`;
  }

  constructor(...args: unknown[]);

  constructor() {
    inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);
    const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true});
    const defaults = this.defaults;
    const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true});

    this.tabIndex = tabIndex == null ? 0 : parseInt(tabIndex) || 0;
    this.color = defaults.color || 'accent';
    this._noopAnimations = animationMode === 'NoopAnimations';
    this.id = this._uniqueId = `mat-mdc-slide-toggle-${++nextUniqueId}`;
    this.hideIcon = defaults.hideIcon ?? false;
    this.disabledInteractive = defaults.disabledInteractive ?? false;
    this._labelId = this._uniqueId + '-label';
  }

  ngAfterContentInit() {
    this._focusMonitor.monitor(this._elementRef, true).subscribe(focusOrigin => {
      if (focusOrigin === 'keyboard' || focusOrigin === 'program') {
        this._focused = true;
        this._changeDetectorRef.markForCheck();
      } else if (!focusOrigin) {
        // When a focused element becomes disabled, the browser *immediately* fires a blur event.
        // Angular does not expect events to be raised during change detection, so any state
        // change (such as a form control's ng-touched) will cause a changed-after-checked error.
        // See https://github.com/angular/angular/issues/17793. To work around this, we defer
        // telling the form control it has been touched until the next tick.
        Promise.resolve().then(() => {
          this._focused = false;
          this._onTouched();
          this._changeDetectorRef.markForCheck();
        });
      }
    });
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['required']) {
      this._validatorOnChange();
    }
  }

  ngOnDestroy() {
    this._focusMonitor.stopMonitoring(this._elementRef);
  }

  /** Implemented as part of ControlValueAccessor. */
  writeValue(value: any): void {
    this.checked = !!value;
  }

  /** Implemented as part of ControlValueAccessor. */
  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  /** Implemented as part of ControlValueAccessor. */
  registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }

  /** Implemented as a part of Validator. */
  validate(control: AbstractControl<boolean>): ValidationErrors | null {
    return this.required && control.value !== true ? {'required': true} : null;
  }

  /** Implemented as a part of Validator. */
  registerOnValidatorChange(fn: () => void): void {
    this._validatorOnChange = fn;
  }

  /** Implemented as a part of ControlValueAccessor. */
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
    this._changeDetectorRef.markForCheck();
  }

  /** Toggles the checked state of the slide-toggle. */
  toggle(): void {
    this.checked = !this.checked;
    this._onChange(this.checked);
  }

  /**
   * Emits a change event on the `change` output. Also notifies the FormControl about the change.
   */
  protected _emitChangeEvent() {
    this._onChange(this.checked);
    this.change.emit(this._createChangeEvent(this.checked));
  }

  /** Method being called whenever the underlying button is clicked. */
  _handleClick() {
    if (!this.disabled) {
      this.toggleChange.emit();

      if (!this.defaults.disableToggleValue) {
        this.checked = !this.checked;
        this._onChange(this.checked);
        this.change.emit(new MatSlideToggleChange(this, this.checked));
      }
    }
  }

  _getAriaLabelledBy() {
    if (this.ariaLabelledby) {
      return this.ariaLabelledby;
    }

    // Even though we have a `label` element with a `for` pointing to the button, we need the
    // `aria-labelledby`, because the button gets flagged as not having a label by tools like axe.
    return this.ariaLabel ? null : this._labelId;
  }
}


The `MatDialog` service can be used to open modal dialogs with Material Design styling and
animations.

<!-- example(dialog-overview) -->

A dialog is opened by calling the `open` method with a component to be loaded and an optional
config object. The `open` method will return an instance of `MatDialogRef`:

```ts
let dialogRef = dialog.open(UserProfileComponent, {
  height: '400px',
  width: '600px',
});
```

The `MatDialogRef` provides a handle on the opened dialog. It can be used to close the dialog and to
receive notifications when the dialog has been closed. Any notification Observables will complete when the dialog closes.

```ts
dialogRef.afterClosed().subscribe(result => {
  console.log(`Dialog result: ${result}`); // Pizza!
});

dialogRef.close('Pizza!');
```

Components created via `MatDialog` can _inject_ `MatDialogRef` and use it to close the dialog
in which they are contained. When closing, an optional result value can be provided. This result
value is forwarded as the result of the `afterClosed` Observable.

```ts
@Component({/* ... */})
export class YourDialog {
  constructor(public dialogRef: MatDialogRef<YourDialog>) { }

  closeDialog() {
    this.dialogRef.close('Pizza!');
  }
}
```

### Specifying global configuration defaults
Default dialog options can be specified by providing an instance of `MatDialogConfig` for
MAT_DIALOG_DEFAULT_OPTIONS in your application's root module.

```ts
@NgModule({
  providers: [
    {provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: false}}
  ]
})
```

### Sharing data with the Dialog component.
If you want to share data with your dialog, you can use the `data`
option to pass information to the dialog component.

```ts
let dialogRef = dialog.open(YourDialog, {
  data: { name: 'austin' },
});
```

To access the data in your dialog component, you have to use the MAT_DIALOG_DATA injection token:

```ts
import {Component, Inject} from '@angular/core';
import {MAT_DIALOG_DATA} from '@angular/material/dialog';

@Component({
  selector: 'your-dialog',
  template: 'passed in {{ data.name }}',
})
export class YourDialog {
  constructor(@Inject(MAT_DIALOG_DATA) public data: {name: string}) { }
}
```

Note that if you're using a template dialog (one that was opened with a `TemplateRef`), the data
will be available implicitly in the template:

```html
<ng-template let-data>
  Hello, {{data.name}}
</ng-template>
```

<!-- example(dialog-data) -->

### Dialog content
Several directives are available to make it easier to structure your dialog content:

| Name                   | Description                                                                                |
|------------------------|---------------------------------------------------------------------------------------------------------------|
| `mat-dialog-title`     | \[Attr] Dialog title, applied to a heading element (e.g., `<h1>`, `<h2>`)                                     |
| `<mat-dialog-content>` | Primary scrollable content of the dialog.                                                                     |
| `<mat-dialog-actions>` | Container for action buttons at the bottom of the dialog. Button alignment can be controlled via the `align` attribute which can be set to `end` and `center`.                                                      |
| `mat-dialog-close`     | \[Attr] Added to a `<button>`, makes the button close the dialog with an optional result from the bound value.|

For example:
```html
<h2 mat-dialog-title>Delete all elements?</h2>
<mat-dialog-content>This will delete all elements that are currently on this page and cannot be undone.</mat-dialog-content>
<mat-dialog-actions>
  <button mat-button mat-dialog-close>Cancel</button>
  <!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
  <button mat-button [mat-dialog-close]="true">Delete</button>
</mat-dialog-actions>
```

Once a dialog opens, the dialog will automatically focus the first tabbable element.

You can control which elements are tab stops with the `tabindex` attribute

```html
<button mat-button tabindex="-1">Not Tabbable</button>
```

<!-- example(dialog-content) -->

### Controlling the dialog animation
You can control the duration of the dialog's enter and exit animations using the
`enterAnimationDuration` and `exitAnimationDuration` options. If you want to disable the dialog's
animation completely, you can do so by setting the properties to `0ms`.

<!-- example(dialog-animations) -->

### Accessibility

`MatDialog` creates modal dialogs that implements the ARIA `role="dialog"` pattern by default.
You can change the dialog's role to `alertdialog` via `MatDialogConfig`.

You should provide an accessible label to this root dialog element by setting the `ariaLabel` or
`ariaLabelledBy` properties of `MatDialogConfig`. You can additionally specify a description element
ID via the `ariaDescribedBy` property of `MatDialogConfig`.

#### Keyboard interaction
By default, the escape key closes `MatDialog`. While you can disable this behavior via
the `disableClose` property of `MatDialogConfig`, doing this breaks the expected interaction
pattern for the ARIA `role="dialog"` pattern.

#### Focus management

When opened, `MatDialog` traps browser focus such that it cannot escape the root
`role="dialog"` element. By default, the first tabbable element in the dialog receives focus.
You can customize which element receives focus with the `autoFocus` property of
`MatDialogConfig`, which supports the following values.

| Value            | Behavior                                                                 |
|------------------|--------------------------------------------------------------------------|
| `first-tabbable` | Focus the first tabbable element. This is the default setting.           |
| `first-header`   | Focus the first header element (`role="heading"`, `h1` through `h6`)     |
| `dialog`         | Focus the root `role="dialog"` element.                                  |
| Any CSS selector | Focus the first element matching the given selector.                     |

While the default setting applies the best behavior for most applications, special cases may benefit
from these alternatives. Always test your application to verify the behavior that works best for
your users.

#### Focus restoration

When closed, `MatDialog` restores focus to the element that previously held focus when the
dialog opened. However, if that previously focused element no longer exists, you must
add additional handling to return focus to an element that makes sense for the user's workflow.
Opening a dialog from a menu is one common pattern that causes this situation. The menu
closes upon clicking an item, thus the focused menu item is no longer in the DOM when the bottom
sheet attempts to restore focus.

You can add handling for this situation with the `afterClosed()` observable from `MatDialogRef`.

<!-- example({"example":"dialog-from-menu",
              "file":"dialog-from-menu-example.ts",
              "region":"focus-restoration"}) -->


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {ViewContainerRef, Injector} from '@angular/core';
import {Direction} from '@angular/cdk/bidi';
import {ScrollStrategy} from '@angular/cdk/overlay';
import {_defaultParams} from './dialog-animations';

/** Options for where to set focus to automatically on dialog open */
export type AutoFocusTarget = 'dialog' | 'first-tabbable' | 'first-heading';

/** Valid ARIA roles for a dialog element. */
export type DialogRole = 'dialog' | 'alertdialog';

/** Possible overrides for a dialog's position. */
export interface DialogPosition {
  /** Override for the dialog's top position. */
  top?: string;

  /** Override for the dialog's bottom position. */
  bottom?: string;

  /** Override for the dialog's left position. */
  left?: string;

  /** Override for the dialog's right position. */
  right?: string;
}

/**
 * Configuration for opening a modal dialog with the MatDialog service.
 */
export class MatDialogConfig<D = any> {
  /**
   * Where the attached component should live in Angular's *logical* component tree.
   * This affects what is available for injection and the change detection order for the
   * component instantiated inside of the dialog. This does not affect where the dialog
   * content will be rendered.
   */
  viewContainerRef?: ViewContainerRef;

  /**
   * Injector used for the instantiation of the component to be attached. If provided,
   * takes precedence over the injector indirectly provided by `ViewContainerRef`.
   */
  injector?: Injector;

  /** ID for the dialog. If omitted, a unique one will be generated. */
  id?: string;

  /** The ARIA role of the dialog element. */
  role?: DialogRole = 'dialog';

  /** Custom class for the overlay pane. */
  panelClass?: string | string[] = '';

  /** Whether the dialog has a backdrop. */
  hasBackdrop?: boolean = true;

  /** Custom class for the backdrop. */
  backdropClass?: string | string[] = '';

  /** Whether the user can use escape or clicking on the backdrop to close the modal. */
  disableClose?: boolean = false;

  /** Width of the dialog. */
  width?: string = '';

  /** Height of the dialog. */
  height?: string = '';

  /** Min-width of the dialog. If a number is provided, assumes pixel units. */
  minWidth?: number | string;

  /** Min-height of the dialog. If a number is provided, assumes pixel units. */
  minHeight?: number | string;

  /** Max-width of the dialog. If a number is provided, assumes pixel units. Defaults to 80vw. */
  maxWidth?: number | string;

  /** Max-height of the dialog. If a number is provided, assumes pixel units. */
  maxHeight?: number | string;

  /** Position overrides. */
  position?: DialogPosition;

  /** Data being injected into the child component. */
  data?: D | null = null;

  /** Layout direction for the dialog's content. */
  direction?: Direction;

  /** ID of the element that describes the dialog. */
  ariaDescribedBy?: string | null = null;

  /** ID of the element that labels the dialog. */
  ariaLabelledBy?: string | null = null;

  /** Aria label to assign to the dialog element. */
  ariaLabel?: string | null = null;

  /** Whether this is a modal dialog. Used to set the `aria-modal` attribute. */
  ariaModal?: boolean = true;

  /**
   * Where the dialog should focus on open.
   * @breaking-change 14.0.0 Remove boolean option from autoFocus. Use string or
   * AutoFocusTarget instead.
   */
  autoFocus?: AutoFocusTarget | string | boolean = 'first-tabbable';

  /**
   * Whether the dialog should restore focus to the
   * previously-focused element, after it's closed.
   */
  restoreFocus?: boolean = true;

  /** Whether to wait for the opening animation to finish before trapping focus. */
  delayFocusTrap?: boolean = true;

  /** Scroll strategy to be used for the dialog. */
  scrollStrategy?: ScrollStrategy;

  /**
   * Whether the dialog should close when the user goes backwards/forwards in history.
   * Note that this usually doesn't include clicking on links (unless the user is using
   * the `HashLocationStrategy`).
   */
  closeOnNavigation?: boolean = true;

  /**
   * Alternate `ComponentFactoryResolver` to use when resolving the associated component.
   * @deprecated No longer used. Will be removed.
   * @breaking-change 20.0.0
   */
  componentFactoryResolver?: unknown;

  /**
   * Duration of the enter animation in ms.
   * Should be a number, string type is deprecated.
   * @breaking-change 17.0.0 Remove string signature.
   */
  enterAnimationDuration?: string | number;

  /**
   * Duration of the exit animation in ms.
   * Should be a number, string type is deprecated.
   * @breaking-change 17.0.0 Remove string signature.
   */
  exitAnimationDuration?: string | number;

  // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling.
}


import {Component, inject} from '@angular/core';
import {TestBed, fakeAsync, flush} from '@angular/core/testing';
import {MAT_DIALOG_DATA, MatDialogRef, MatDialogState} from '@angular/material/dialog';
import {MatTestDialogOpener, MatTestDialogOpenerModule} from '@angular/material/dialog/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';

describe('MatTestDialogOpener', () => {
  beforeEach(fakeAsync(() => {
    TestBed.configureTestingModule({
      imports: [MatTestDialogOpenerModule, NoopAnimationsModule, ExampleComponent],
    });
  }));

  it('should open a dialog when created', fakeAsync(() => {
    const fixture = TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent));
    flush();
    expect(fixture.componentInstance.dialogRef.getState()).toBe(MatDialogState.OPEN);
    expect(document.querySelector('mat-dialog-container')).toBeTruthy();
  }));

  it('should throw an error if no dialog component is provided', () => {
    expect(() => TestBed.createComponent(MatTestDialogOpener)).toThrow(
      Error('MatTestDialogOpener does not have a component provided.'),
    );
  });

  it('should pass data to the component', async () => {
    const config = {data: 'test'};
    const fixture = TestBed.createComponent(
      MatTestDialogOpener.withComponent(ExampleComponent, config),
    );
    fixture.detectChanges();
    await fixture.whenStable();
    const dialogContainer = document.querySelector('mat-dialog-container');
    expect(dialogContainer!.innerHTML).toContain('Data: test');
  });

  it('should get closed result data', fakeAsync(() => {
    const config = {data: 'test'};
    const fixture = TestBed.createComponent(
      MatTestDialogOpener.withComponent<ExampleComponent, ExampleDialogResult>(
        ExampleComponent,
        config,
      ),
    );
    flush();
    const closeButton = document.querySelector('#close-btn') as HTMLElement;
    closeButton.click();
    flush();
    expect(fixture.componentInstance.closedResult).toEqual({reason: 'closed'});
  }));
});

interface ExampleDialogResult {
  reason: string;
}

/** Simple component for testing MatTestDialogOpener. */
@Component({
  template: `
    Data: {{data}}
    <button id="close-btn" (click)="close()">Close</button>
  `,
  standalone: true,
})
class ExampleComponent {
  dialogRef = inject<MatDialogRef<ExampleComponent, ExampleDialogResult>>(MatDialogRef);
  data = inject(MAT_DIALOG_DATA);

  close() {
    this.dialogRef.close({reason: 'closed'});
  }
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {ComponentType} from '@angular/cdk/overlay';
import {
  ChangeDetectionStrategy,
  Component,
  NgModule,
  NgZone,
  OnDestroy,
  ViewEncapsulation,
  inject,
} from '@angular/core';
import {MatDialog, MatDialogConfig, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Subscription} from 'rxjs';

/** Test component that immediately opens a dialog when bootstrapped. */
@Component({
  selector: 'mat-test-dialog-opener',
  template: '',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
})
export class MatTestDialogOpener<T = unknown, R = unknown> implements OnDestroy {
  dialog = inject(MatDialog);

  /** Component that should be opened with the MatDialog `open` method. */
  protected static component: ComponentType<unknown> | undefined;

  /** Config that should be provided to the MatDialog `open` method. */
  protected static config: MatDialogConfig | undefined;

  /** MatDialogRef returned from the MatDialog `open` method. */
  dialogRef: MatDialogRef<T, R>;

  /** Data passed to the `MatDialog` close method. */
  closedResult: R | undefined;

  private readonly _afterClosedSubscription: Subscription;

  private readonly _ngZone = inject(NgZone);

  /** Static method that prepares this class to open the provided component. */
  static withComponent<T = unknown, R = unknown>(
    component: ComponentType<T>,
    config?: MatDialogConfig,
  ) {
    MatTestDialogOpener.component = component;
    MatTestDialogOpener.config = config;
    return MatTestDialogOpener as ComponentType<MatTestDialogOpener<T, R>>;
  }

  constructor(...args: unknown[]);

  constructor() {
    if (!MatTestDialogOpener.component) {
      throw new Error(`MatTestDialogOpener does not have a component provided.`);
    }

    this.dialogRef = this._ngZone.run(() =>
      this.dialog.open<T, R>(
        MatTestDialogOpener.component as ComponentType<T>,
        MatTestDialogOpener.config || {},
      ),
    );
    this._afterClosedSubscription = this.dialogRef.afterClosed().subscribe(result => {
      this.closedResult = result;
    });
  }

  ngOnDestroy() {
    this._afterClosedSubscription.unsubscribe();
    MatTestDialogOpener.component = undefined;
    MatTestDialogOpener.config = undefined;
  }
}

@NgModule({
  imports: [MatDialogModule, NoopAnimationsModule, MatTestDialogOpener],
})
export class MatTestDialogOpenerModule {}