`<mat-form-field>` is a component used to wrap several Angular Material components and apply common
[Text field](https://material.io/guidelines/components/text-fields.html) styles such as the
underline, floating label, and hint messages.

In this document, "form field" refers to the wrapper component `<mat-form-field>` and
"form field control" refers to the component that the `<mat-form-field>` is wrapping
(e.g. the input, textarea, select, etc.)

The following Angular Material components are designed to work inside a `<mat-form-field>`:

- [`<input matNativeControl>` & `<textarea matNativeControl>`](https://material.angular.io/components/input/overview)
- [`<select matNativeControl>`](https://material.angular.io/components/select/overview)
- [`<mat-select>`](https://material.angular.io/components/select/overview)
- [`<mat-chip-list>`](https://material.angular.io/components/chips/overview)

<!-- example(form-field-overview) -->

### Form field appearance variants

`mat-form-field` supports two different appearance variants which can be set via the `appearance`
input: `fill` and `outline`. The `fill` appearance displays the form field with a filled background
box and an underline, while the `outline` appearance shows the form field with a border all the way
around.

Out of the box, if you do not specify an `appearance` for the `<mat-form-field>` it will default to
`fill`. However, this can be configured using a global provider to choose a different default
appearance for your app.

```ts
@NgModule({
  providers: [
    {provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {appearance: 'outline'}}
  ]
})
```

<!-- example(form-field-appearance) -->

### Floating label

The floating label is a text label displayed on top of the form field control when
the control does not contain any text or when `<select matNativeControl>` does not show any option
text. By default, when text is present the floating label floats above the form field control. The
label for a form field can be specified by adding a `mat-label` element.

If the form field control is marked with a `required` attribute, an asterisk will be appended to the
label to indicate the fact that it is a required field. If unwanted, this can be disabled by
setting the `hideRequiredMarker` property on `<mat-form-field>`

The `floatLabel` property of `<mat-form-field>` can be used to change this default floating
behavior. It can be set to `always` to float the label even when no text is present in the form
field control, or to `auto` to restore the default behavior.

<!-- example(form-field-label) -->

The floating label behavior can be adjusted globally by providing a value for
`MAT_FORM_FIELD_DEFAULT_OPTIONS` in your application's root module. Like the `floatLabel` input,
the option can be either set to `always` or `auto`.

```ts
@NgModule({
  providers: [
    {provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {floatLabel: 'always'}}
  ]
})
```

### Hint labels

Hint labels are additional descriptive text that appears below the form field's underline. A
`<mat-form-field>` can have up to two hint labels; one start-aligned (left in an LTR language, right
in RTL), and one end-aligned.

Hint labels are specified in one of two ways: either by using the `hintLabel` property of
`<mat-form-field>`, or by adding a `<mat-hint>` element inside the form field. When adding a hint
via the `hintLabel` property, it will be treated as the start hint. Hints added via the
`<mat-hint>` hint element can be added to either side by setting the `align` property on
`<mat-hint>` to either `start` or `end`. Attempting to add multiple hints to the same side will
raise an error.

<!-- example(form-field-hint) -->

### Error messages

Error messages can be shown under the form field underline by adding `mat-error` elements inside the
form field. Errors are hidden initially and will be displayed on invalid form fields after the user
has interacted with the element or the parent form has been submitted. Since the errors occupy the
same space as the hints, the hints are hidden when the errors are shown.

If a form field can have more than one error state, it is up to the consumer to toggle which
messages should be displayed. This can be done with CSS, `@if` or `@switch`. Multiple error
messages can be shown at the same time if desired, but the `<mat-form-field>` only reserves enough
space to display one error message at a time. Ensuring that enough space is available to display
multiple errors is up to the user.

<!-- example(form-field-error) -->

### Prefix & suffix

Custom content can be included before and after the input tag, as a prefix or suffix. It will be
included within the visual container that wraps the form control as per the Material specification.

Adding the `matPrefix` directive to an element inside the `<mat-form-field>` will designate it as
the prefix. Similarly, adding `matSuffix` will designate it as the suffix.

If the prefix/suffix content is purely text-based, it is recommended to use the `matTextPrefix` or
`matTextSuffix` directives which ensure that the text is aligned with the form control.

<!-- example(form-field-prefix-suffix) -->

### Custom form field controls

In addition to the form field controls that Angular Material provides, it is possible to create
custom form field controls that work with `<mat-form-field>` in the same way. For additional
information on this see the guide on
[Creating Custom mat-form-field Controls](/guide/creating-a-custom-form-field-control).

### Theming

The color of the form-field can be changed by specifying a `$color-variant` when applying the
`mat.form-field-theme` or `mat.form-field-color` mixins (see the
[theming guide](/guide/theming#using-component-color-variants) to learn more.) By default, the
form-field uses the theme's primary palette. This can be changed to `'secondary'`, `'tertiary'`, or
`'error'`.

### Accessibility

By itself, `MatFormField` does not apply any additional accessibility treatment to a control.
However, several of the form field's optional features interact with the control contained within
the form field.

When you provide a label via `<mat-label>`, `MatFormField` automatically associates this label with
the field's control via a native `<label>` element, using the `for` attribute to reference the
control's ID.

If a floating label is specified, it will be automatically used as the label for the form
field control. If no floating label is specified, the user should label the form field control
themselves using `aria-label`, `aria-labelledby` or `<label for=...>`.

When you provide informational text via `<mat-hint>` or `<mat-error>`, `MatFormField` automatically
adds these elements' IDs to the control's `aria-describedby` attribute. Additionally, `MatError`
applies `aria-live="polite"` by default such that assistive technology will announce errors when
they appear.

### Troubleshooting

#### Error: A hint was already declared for align="..."

This error occurs if you have added multiple hints for the same side. Keep in mind that the
`hintLabel` property adds a hint to the start side.

#### Error: mat-form-field must contain a MatFormFieldControl

This error occurs when you have not added a form field control to your form field. If your form
field contains a native `<input>` or `<textarea>` element, make sure you've added the `matInput`
directive to it and have imported `MatInputModule`. Other components that can act as a form field
control include `<mat-select>`, `<mat-chip-list>`, and any custom form field controls you've
created.


@Component({
  template: `

      <ng-container matColumnDef="column_a">
         Column A
         {{row.a}}
         Footer A
      </ng-container>

      <ng-container matColumnDef="column_b">
         Column B
         {{row.b}}
         Footer B
      </ng-container>

      <ng-container matColumnDef="column_c">
         Column C
         {{row.c}}
         Footer C
      </ng-container>

    <mat-paginator [pageSize]="5"></mat-paginator>
  `,
  standalone: true,
  imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class ArrayDataSourceMatTableApp implements AfterViewInit {
  underlyingDataSource = new FakeDataSource();
  dataSource = new MatTableDataSource<TestData>();
  columnsToRender = ['column_a', 'column_b', 'column_c'];

  @ViewChild(MatTable) table: MatTable<TestData>;
  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatSortHeader) sortHeader: MatSortHeader;

  constructor() {
    this.underlyingDataSource.data = [];

    // Add three rows of data
    this.underlyingDataSource.addData();
    this.underlyingDataSource.addData();
    this.underlyingDataSource.addData();

    this.underlyingDataSource.connect().subscribe(data => {
      this.dataSource.data = data;
    });
  }

  ngAfterViewInit() {
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;
  }
}

@Component({
  template: `

      <ng-container matColumnDef="column_a">
         Column A
         {{row.a}}
      </ng-container>

      <ng-container matColumnDef="column_b">
         Column B
         {{row.b}}
      </ng-container>

      <ng-container matColumnDef="column_c">
         Column C
         {{row.c}}
      </ng-container>

  `,
  standalone: true,
  imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class MatTableWithSortApp implements OnInit {
  underlyingDataSource = new FakeDataSource();
  dataSource = new MatTableDataSource<TestData>();
  columnsToRender = ['column_a', 'column_b', 'column_c'];

  @ViewChild(MatTable) table: MatTable<TestData>;
  @ViewChild(MatSort) sort: MatSort;

  constructor() {
    this.underlyingDataSource.data = [];

    // Add three rows of data
    this.underlyingDataSource.addData();
    this.underlyingDataSource.addData();
    this.underlyingDataSource.addData();

    this.underlyingDataSource.connect().subscribe(data => {
      this.dataSource.data = data;
    });
  }

  ngOnInit() {
    this.dataSource!.sort = this.sort;
  }
}

@Component({
  template: `

      <ng-container matColumnDef="column_a">
         Column A
         {{row.a}}
      </ng-container>

      <ng-container matColumnDef="column_b">
         Column B
         {{row.b}}
      </ng-container>

      <ng-container matColumnDef="column_c">
         Column C
         {{row.c}}
      </ng-container>

    <mat-paginator [pageSize]="5"></mat-paginator>
  `,
  standalone: true,
  imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class MatTableWithPaginatorApp implements OnInit {
  underlyingDataSource = new FakeDataSource();
  dataSource = new MatTableDataSource<TestData>();
  columnsToRender = ['column_a', 'column_b', 'column_c'];

  @ViewChild(MatTable) table: MatTable<TestData>;
  @ViewChild(MatPaginator) paginator: MatPaginator;

  constructor() {
    this.underlyingDataSource.data = [];

    // Add three rows of data
    this.underlyingDataSource.addData();
    this.underlyingDataSource.addData();
    this.underlyingDataSource.addData();

    this.underlyingDataSource.connect().subscribe(data => {
      this.dataSource.data = data;
    });
  }

  ngOnInit() {
    this.dataSource!.paginator = this.paginator;
  }
}

@Component({
  template: `

      <ng-container matColumnDef="column_a">
        Column A
        {{row.a}}
      </ng-container>

      <ng-container *matRowDef="let row; columns: columnsToRender">

      </ng-container>

  `,
  standalone: true,
  imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class TableWithNgContainerRow {
  dataSource: FakeDataSource | null = new FakeDataSource();
  columnsToRender = ['column_a'];
}

@Component({
  template: `
    <mat-table [dataSource]="dataSource">
      <ng-container matColumnDef="column_a">
        <mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
        <mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
        <mat-footer-cell *matFooterCellDef> Footer A</mat-footer-cell>
      </ng-container>

      <ng-container matColumnDef="column_b">
        <mat-header-cell *matHeaderCellDef> Column B</mat-header-cell>
        <mat-cell *matCellDef="let row"> {{row.b}}</mat-cell>
        <mat-footer-cell *matFooterCellDef> Footer B</mat-footer-cell>
      </ng-container>

      <ng-container matColumnDef="column_c">
        <mat-header-cell *matHeaderCellDef> Column C</mat-header-cell>
        <mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
        <mat-footer-cell *matFooterCellDef> Footer C</mat-footer-cell>
      </ng-container>

      <ng-container matColumnDef="special_column">
        <mat-cell *matCellDef="let row"> fourth_row </mat-cell>
      </ng-container>

      <mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row>
      <mat-row *matRowDef="let row; columns: columnsToRender"></mat-row>

No data

      <mat-footer-row *matFooterRowDef="columnsToRender"></mat-footer-row>
    </mat-table>
  `,
  standalone: true,
  imports: [MatTableModule, MatPaginatorModule, MatSortModule],
})
class MatFlexTableApp {
  dataSource: FakeDataSource | null = new FakeDataSource();
  columnsToRender = ['column_a', 'column_b', 'column_c'];
  @ViewChild(MatTable) table: MatTable<TestData>;
}

function getElements(element: Element, query: string): Element[] {
  return [].slice.call(element.querySelectorAll(query));
}

function getHeaderRows(tableElement: Element): Element[] {
  return [].slice.call(tableElement.querySelectorAll('.mat-mdc-header-row'))!;
}

function getFooterRows(tableElement: Element): Element[] {
  return [].slice.call(tableElement.querySelectorAll('.mat-mdc-footer-row'))!;
}


/**
 * @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 {NgModule} from '@angular/core';
import {MatCommonModule} from '@angular/material/core';
import {MatRecycleRows, MatTable} from './table';
import {CdkTableModule} from '@angular/cdk/table';
import {
  MatCell,
  MatCellDef,
  MatColumnDef,
  MatFooterCell,
  MatFooterCellDef,
  MatHeaderCell,
  MatHeaderCellDef,
} from './cell';
import {
  MatFooterRow,
  MatFooterRowDef,
  MatHeaderRow,
  MatHeaderRowDef,
  MatRow,
  MatRowDef,
  MatNoDataRow,
} from './row';
import {MatTextColumn} from './text-column';

const EXPORTED_DECLARATIONS = [
  // Table
  MatTable,
  MatRecycleRows,

  // Template defs
  MatHeaderCellDef,
  MatHeaderRowDef,
  MatColumnDef,
  MatCellDef,
  MatRowDef,
  MatFooterCellDef,
  MatFooterRowDef,

  // Cell directives
  MatHeaderCell,
  MatCell,
  MatFooterCell,

  // Row directives
  MatHeaderRow,
  MatRow,
  MatFooterRow,
  MatNoDataRow,

  MatTextColumn,
];

@NgModule({
  imports: [MatCommonModule, CdkTableModule, ...EXPORTED_DECLARATIONS],
  exports: [MatCommonModule, EXPORTED_DECLARATIONS],
})
export class MatTableModule {}


### Features

The `MatTable` is focused on a single responsibility: efficiently render rows of data in a
performant and accessible way.

You'll notice that the table itself doesn't come out of the box with a lot of features, but expects
that the table will be included in a composition of components that fills out its features.

For example, you can add sorting and pagination to the table by using MatSort and MatPaginator and
mutating the data provided to the table according to their outputs.

To simplify the use case of having a table that can sort, paginate, and filter an array of data,
the Angular Material library comes with a `MatTableDataSource` that has already implemented
the logic of determining what rows should be rendered according to the current table state. To add
these feature to the table, check out their respective sections below.

#### Pagination

To paginate the table's data, add a `<mat-paginator>` after the table.

If you are using the `MatTableDataSource` for your table's data source, simply provide the
`MatPaginator` to your data source. It will automatically listen for page changes made by the user
and send the right paged data to the table.

Otherwise if you are implementing the logic to paginate your data, you will want to listen to the
paginator's `(page)` output and pass the right slice of data to your table.

For more information on using and configuring the `<mat-paginator>`, check out the
[mat-paginator docs](https://material.angular.io/components/paginator/overview).

The `MatPaginator` is one provided solution to paginating your table's data, but it is not the only
option. In fact, the table can work with any custom pagination UI or strategy since the `MatTable`
and its interface is not tied to any one specific implementation.

<!-- example(table-pagination) -->

#### Sorting

To add sorting behavior to the table, add the `matSort` directive to the table and add
`mat-sort-header` to each column header cell that should trigger sorting. Note that you have to import `MatSortModule` in order to initialize the `matSort` directive (see [API docs](https://material.angular.io/components/sort/api)).

```html
<!-- Name Column -->
<ng-container matColumnDef="position">
  <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
  <td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>
```

If you are using the `MatTableDataSource` for your table's data source, provide the `MatSort`
directive to the data source and it will automatically listen for sorting changes and change the
order of data rendered by the table.

By default, the `MatTableDataSource` sorts with the assumption that the sorted column's name
matches the data property name that the column displays. For example, the following column
definition is named `position`, which matches the name of the property displayed in the row cell.

Note that if the data properties do not match the column names, or if a more complex data property
accessor is required, then a custom `sortingDataAccessor` function can be set to override the
default data accessor on the `MatTableDataSource`.

When updating the data soure asynchronously avoid doing so by recreating the entire `MatTableDataSource` as this could break sorting. Rather update it through the `MatTableDataSource.data` property.

If you are not using the `MatTableDataSource`, but instead implementing custom logic to sort your
data, listen to the sort's `(matSortChange)` event and re-order your data according to the sort state.
If you are providing a data array directly to the table, don't forget to call `renderRows()` on the
table, since it will not automatically check the array for changes.

<!-- example(table-sorting) -->

For more information on using and configuring the sorting behavior, check out the
[matSort docs](https://material.angular.io/components/sort/overview).

The `MatSort` is one provided solution to sorting your table's data, but it is not the only option.
In fact, the table can work with any custom sorting UI or strategy since the `MatTable` and
its interface is not tied to any one specific implementation.

#### Filtering

Angular Material does not provide a specific component to be used for filtering the `MatTable`
since there is no single common approach to adding a filter UI to table data.

A general strategy is to add an input where users can type in a filter string and listen to this
input to change what data is offered from the data source to the table.

If you are using the `MatTableDataSource`, simply provide the filter string to the
`MatTableDataSource`. The data source will reduce each row data to a serialized form and will filter
out the row if it does not contain the filter string. By default, the row data reducing function
will concatenate all the object values and convert them to lowercase.

For example, the data object `{id: 123, name: 'Mr. Smith', favoriteColor: 'blue'}` will be reduced
to `123mr. smithblue`. If your filter string was `blue` then it would be considered a match because
it is contained in the reduced string, and the row would be displayed in the table.

To override the default filtering behavior, a custom `filterPredicate` function can be set which
takes a data object and filter string and returns true if the data object is considered a match.

If you want to show a message when not data matches the filter, you can use the `*matNoDataRow`
directive.

<!--- example(table-filtering) -->

#### Selection

Right now there is no formal support for adding a selection UI to the table, but Angular Material
does offer the right components and pieces to set this up. The following steps are one solution but
it is not the only way to incorporate row selection in your table.

##### 1. Add a selection model

Get started by setting up a `SelectionModel` from `@angular/cdk/collections` that will maintain the
selection state.

```js
const initialSelection = [];
const allowMultiSelect = true;
this.selection = new SelectionModel<MyDataType>(allowMultiSelect, initialSelection);
```

##### 2. Define a selection column

Add a column definition for displaying the row checkboxes, including a main toggle checkbox for
the header. The column name should be added to the list of displayed columns provided to the
header and data row.

```html
<ng-container matColumnDef="select">
  <th mat-header-cell *matHeaderCellDef>
    <mat-checkbox (change)="$event ? toggleAllRows() : null"
                  [checked]="selection.hasValue() && isAllSelected()"
                  [indeterminate]="selection.hasValue() && !isAllSelected()">
    </mat-checkbox>
  </th>
  <td mat-cell *matCellDef="let row">
    <mat-checkbox (click)="$event.stopPropagation()"
                  (change)="$event ? selection.toggle(row) : null"
                  [checked]="selection.isSelected(row)">
    </mat-checkbox>
  </td>
</ng-container>
```

##### 3. Add event handling logic

Implement the behavior in your component's logic to handle the header's main toggle and checking
if all rows are selected.

```js
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
  const numSelected = this.selection.selected.length;
  const numRows = this.dataSource.data.length;
  return numSelected == numRows;
}

/** Selects all rows if they are not all selected; otherwise clear selection. */
toggleAllRows() {
  this.isAllSelected() ?
      this.selection.clear() :
      this.dataSource.data.forEach(row => this.selection.select(row));
}
```

##### 4. Include overflow styling

Finally, adjust the styling for the select column so that its overflow is not hidden. This allows
the ripple effect to extend beyond the cell.

```css
.mat-column-select {
  overflow: initial;
}
```

<!--- example(table-selection) -->

#### Footer row

A footer row can be added to the table by adding a footer row definition to the table and adding
footer cell templates to column definitions. The footer row will be rendered after the rendered
data rows.

```html
<ng-container matColumnDef="cost">
  <th mat-header-cell *matHeaderCellDef> Cost </th>
  <td mat-cell *matCellDef="let data"> {{data.cost}} </td>
  <td mat-footer-cell *matFooterCellDef> {{totalCost}} </td>
</ng-container>

...

<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>
<tr mat-footer-row *matFooterRowDef="columnsToDisplay"></tr>
```

<!--- example(table-footer-row) -->


export class MatTableDataSource<T, P extends MatPaginator = MatPaginator> extends DataSource<T> {
  /** Stream that emits when a new data array is set on the data source. */
  private readonly _data: BehaviorSubject<T[]>;

  /** Stream emitting render data to the table (depends on ordered data changes). */
  private readonly _renderData = new BehaviorSubject<T[]>([]);

  /** Stream that emits when a new filter string is set on the data source. */
  private readonly _filter = new BehaviorSubject<string>('');

  /** Used to react to internal changes of the paginator that are made by the data source itself. */
  private readonly _internalPageChanges = new Subject<void>();

  /**
   * Subscription to the changes that should trigger an update to the table's rendered rows, such
   * as filtering, sorting, pagination, or base data changes.
   */
  _renderChangesSubscription: Subscription | null = null;

  /**
   * The filtered set of data that has been matched by the filter string, or all the data if there
   * is no filter. Useful for knowing the set of data the table represents.
   * For example, a 'selectAll()' function would likely want to select the set of filtered data
   * shown to the user rather than all the data.
   */
  filteredData: T[];

  /** Array of data that should be rendered by the table, where each object represents one row. */
  get data() {
    return this._data.value;
  }

  set data(data: T[]) {
    data = Array.isArray(data) ? data : [];
    this._data.next(data);
    // Normally the `filteredData` is updated by the re-render
    // subscription, but that won't happen if it's inactive.
    if (!this._renderChangesSubscription) {
      this._filterData(data);
    }
  }

  /**
   * Filter term that should be used to filter out objects from the data array. To override how
   * data objects match to this filter string, provide a custom function for filterPredicate.
   */
  get filter(): string {
    return this._filter.value;
  }

  set filter(filter: string) {
    this._filter.next(filter);
    // Normally the `filteredData` is updated by the re-render
    // subscription, but that won't happen if it's inactive.
    if (!this._renderChangesSubscription) {
      this._filterData(this.data);
    }
  }

  /**
   * Instance of the MatSort directive used by the table to control its sorting. Sort changes
   * emitted by the MatSort will trigger an update to the table's rendered data.
   */
  get sort(): MatSort | null {
    return this._sort;
  }

  set sort(sort: MatSort | null) {
    this._sort = sort;
    this._updateChangeSubscription();
  }

  private _sort: MatSort | null;

  /**
   * Instance of the paginator component used by the table to control what page of the data is
   * displayed. Page changes emitted by the paginator will trigger an update to the
   * table's rendered data.
   *
   * Note that the data source uses the paginator's properties to calculate which page of data
   * should be displayed. If the paginator receives its properties as template inputs,
   * e.g. `[pageLength]=100` or `[pageIndex]=1`, then be sure that the paginator's view has been
   * initialized before assigning it to this data source.
   */
  get paginator(): P | null {
    return this._paginator;
  }

  set paginator(paginator: P | null) {
    this._paginator = paginator;
    this._updateChangeSubscription();
  }

  private _paginator: P | null;

  /**
   * Data accessor function that is used for accessing data properties for sorting through
   * the default sortData function.
   * This default function assumes that the sort header IDs (which defaults to the column name)
   * matches the data's properties (e.g. column Xyz represents data['Xyz']).
   * May be set to a custom function for different behavior.
   * @param data Data object that is being accessed.
   * @param sortHeaderId The name of the column that represents the data.
   */
  sortingDataAccessor: (data: T, sortHeaderId: string) => string | number = (
    data: T,
    sortHeaderId: string,
  ): string | number => {
    const value = (data as unknown as Record<string, any>)[sortHeaderId];

    if (_isNumberValue(value)) {
      const numberValue = Number(value);

      // Numbers beyond `MAX_SAFE_INTEGER` can't be compared reliably so we leave them as strings.
      // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
      return numberValue < MAX_SAFE_INTEGER ? numberValue : value;
    }

    return value;
  };

  /**
   * Gets a sorted copy of the data array based on the state of the MatSort. Called
   * after changes are made to the filtered data or when sort changes are emitted from MatSort.
   * By default, the function retrieves the active sort and its direction and compares data
   * by retrieving data using the sortingDataAccessor. May be overridden for a custom implementation
   * of data ordering.
   * @param data The array of data that should be sorted.
   * @param sort The connected MatSort that holds the current sort state.
   */
  sortData: (data: T[], sort: MatSort) => T[] = (data: T[], sort: MatSort): T[] => {
    const active = sort.active;
    const direction = sort.direction;
    if (!active || direction == '') {
      return data;
    }

    return data.sort((a, b) => {
      let valueA = this.sortingDataAccessor(a, active);
      let valueB = this.sortingDataAccessor(b, active);

      // If there are data in the column that can be converted to a number,
      // it must be ensured that the rest of the data
      // is of the same type so as not to order incorrectly.
      const valueAType = typeof valueA;
      const valueBType = typeof valueB;

      if (valueAType !== valueBType) {
        if (valueAType === 'number') {
          valueA += '';
        }
        if (valueBType === 'number') {
          valueB += '';
        }
      }

      // If both valueA and valueB exist (truthy), then compare the two. Otherwise, check if
      // one value exists while the other doesn't. In this case, existing value should come last.
      // This avoids inconsistent results when comparing values to undefined/null.
      // If neither value exists, return 0 (equal).
      let comparatorResult = 0;
      if (valueA != null && valueB != null) {
        // Check if one value is greater than the other; if equal, comparatorResult should remain 0.
        if (valueA > valueB) {
          comparatorResult = 1;
        } else if (valueA < valueB) {
          comparatorResult = -1;
        }
      } else if (valueA != null) {
        comparatorResult = 1;
      } else if (valueB != null) {
        comparatorResult = -1;
      }

      return comparatorResult * (direction == 'asc' ? 1 : -1);
    });
  };

  /**
   * Checks if a data object matches the data source's filter string. By default, each data object
   * is converted to a string of its properties and returns true if the filter has
   * at least one occurrence in that string. By default, the filter string has its whitespace
   * trimmed and the match is case-insensitive. May be overridden for a custom implementation of
   * filter matching.
   * @param data Data object used to check against the filter.
   * @param filter Filter string that has been set on the data source.
   * @returns Whether the filter matches against the data
   */
  filterPredicate: (data: T, filter: string) => boolean = (data: T, filter: string): boolean => {
    // Transform the data into a lowercase string of all property values.
    const dataStr = Object.keys(data as unknown as Record<string, any>)
      .reduce((currentTerm: string, key: string) => {
        // Use an obscure Unicode character to delimit the words in the concatenated string.
        // This avoids matches where the values of two columns combined will match the user's query
        // (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something
        // that has a very low chance of being typed in by somebody in a text field. This one in
        // particular is "White up-pointing triangle with dot" from
        // https://en.wikipedia.org/wiki/List_of_Unicode_characters
        return currentTerm + (data as unknown as Record<string, any>)[key] + '◬';
      }, '')
      .toLowerCase();

    // Transform the filter by converting it to lowercase and removing whitespace.
    const transformedFilter = filter.trim().toLowerCase();

    return dataStr.indexOf(transformedFilter) != -1;
  };


/**
 * @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 {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {SelectionModel} from '@angular/cdk/collections';
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  ElementRef,
  EventEmitter,
  InjectionToken,
  Input,
  OnDestroy,
  OnInit,
  Output,
  QueryList,
  ViewChild,
  ViewEncapsulation,
  inject,
} from '@angular/core';
import {ThemePalette} from '@angular/material/core';
import {MatListBase, MatListItemBase} from './list-base';
import {LIST_OPTION, ListOption, MatListOptionTogglePosition} from './list-option-types';
import {MatListItemLine, MatListItemTitle} from './list-item-sections';
import {NgTemplateOutlet} from '@angular/common';
import {CdkObserveContent} from '@angular/cdk/observers';

/**
 * Injection token that can be used to reference instances of an `SelectionList`. It serves
 * as alternative token to an actual implementation which would result in circular references.
 * @docs-private
 */
export const SELECTION_LIST = new InjectionToken<SelectionList>('SelectionList');

/**
 * Interface describing the containing list of a list option. This is used to avoid
 * circular dependencies between the list-option and the selection list.
 * @docs-private
 */
export interface SelectionList extends MatListBase {
  multiple: boolean;
  color: ThemePalette;
  selectedOptions: SelectionModel<MatListOption>;
  hideSingleSelectionIndicator: boolean;
  compareWith: (o1: any, o2: any) => boolean;
  _value: string[] | null;
  _reportValueChange(): void;
  _emitChangeEvent(options: MatListOption[]): void;
  _onTouched(): void;
}

@Component({
  selector: 'mat-list-option',
  exportAs: 'matListOption',
  styleUrl: 'list-option.css',
  host: {
    'class': 'mat-mdc-list-item mat-mdc-list-option mdc-list-item',
    'role': 'option',
    // As per MDC, only list items without checkbox or radio indicator should receive the
    // `--selected` class.
    '[class.mdc-list-item--selected]':
      'selected && !_selectionList.multiple && _selectionList.hideSingleSelectionIndicator',
    // Based on the checkbox/radio position and whether there are icons or avatars, we apply MDC's
    // list-item `--leading` and `--trailing` classes.
    '[class.mdc-list-item--with-leading-avatar]': '_hasProjected("avatars", "before")',
    '[class.mdc-list-item--with-leading-icon]': '_hasProjected("icons", "before")',
    '[class.mdc-list-item--with-trailing-icon]': '_hasProjected("icons", "after")',
    '[class.mat-mdc-list-option-with-trailing-avatar]': '_hasProjected("avatars", "after")',
    // Based on the checkbox/radio position, we apply the `--leading` or `--trailing` MDC classes
    // which ensure that the checkbox/radio is positioned correctly within the list item.
    '[class.mdc-list-item--with-leading-checkbox]': '_hasCheckboxAt("before")',
    '[class.mdc-list-item--with-trailing-checkbox]': '_hasCheckboxAt("after")',
    '[class.mdc-list-item--with-leading-radio]': '_hasRadioAt("before")',
    '[class.mdc-list-item--with-trailing-radio]': '_hasRadioAt("after")',

    // Utility class that makes it easier to target the case where there's both a leading
    // and a trailing icon. Avoids having to write out all the combinations.
    '[class.mat-mdc-list-item-both-leading-and-trailing]': '_hasBothLeadingAndTrailing()',
    '[class.mat-accent]': 'color !== "primary" && color !== "warn"',
    '[class.mat-warn]': 'color === "warn"',
    '[class._mat-animation-noopable]': '_noopAnimations',
    '[attr.aria-selected]': 'selected',
    '(blur)': '_handleBlur()',
    '(click)': '_toggleOnInteraction()',
  },
  templateUrl: 'list-option.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {provide: MatListItemBase, useExisting: MatListOption},
    {provide: LIST_OPTION, useExisting: MatListOption},
  ],
  imports: [NgTemplateOutlet, CdkObserveContent],
})
export


class MatListOption extends MatListItemBase implements ListOption, OnInit, OnDestroy {
  private _selectionList = inject<SelectionList>(SELECTION_LIST);
  private _changeDetectorRef = inject(ChangeDetectorRef);

  @ContentChildren(MatListItemLine, {descendants: true}) _lines: QueryList<MatListItemLine>;
  @ContentChildren(MatListItemTitle, {descendants: true}) _titles: QueryList<MatListItemTitle>;
  @ViewChild('unscopedContent') _unscopedContent: ElementRef<HTMLSpanElement>;

  /**
   * Emits when the selected state of the option has changed.
   * Use to facilitate two-data binding to the `selected` property.
   * @docs-private
   */
  @Output()
  readonly selectedChange: EventEmitter<boolean> = new EventEmitter<boolean>();

  /** Whether the label should appear before or after the checkbox/radio. Defaults to 'after' */
  @Input() togglePosition: MatListOptionTogglePosition = 'after';

  /**
   * Whether the label should appear before or after the checkbox/radio. Defaults to 'after'
   *
   * @deprecated Use `togglePosition` instead.
   * @breaking-change 17.0.0
   */
  @Input() get checkboxPosition(): MatListOptionTogglePosition {
    return this.togglePosition;
  }
  set checkboxPosition(value: MatListOptionTogglePosition) {
    this.togglePosition = value;
  }

  /**
   * Theme color of the list option. This sets the color of the checkbox/radio.
   * 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()
  get color(): ThemePalette {
    return this._color || this._selectionList.color;
  }
  set color(newValue: ThemePalette) {
    this._color = newValue;
  }
  private _color: ThemePalette;

  /** Value of the option */
  @Input()
  get value(): any {
    return this._value;
  }
  set value(newValue: any) {
    if (this.selected && newValue !== this.value && this._inputsInitialized) {
      this.selected = false;
    }

    this._value = newValue;
  }
  private _value: any;

  /** Whether the option is selected. */
  @Input()
  get selected(): boolean {
    return this._selectionList.selectedOptions.isSelected(this);
  }
  set selected(value: BooleanInput) {
    const isSelected = coerceBooleanProperty(value);

    if (isSelected !== this._selected) {
      this._setSelected(isSelected);

      if (isSelected || this._selectionList.multiple) {
        this._selectionList._reportValueChange();
      }
    }
  }
  private _selected = false;

  /**
   * This is set to true after the first OnChanges cycle so we don't
   * clear the value of `selected` in the first cycle.
   */
  private _inputsInitialized = false;

  ngOnInit() {
    const list = this._selectionList;

    if (list._value && list._value.some(value => list.compareWith(this._value, value))) {
      this._setSelected(true);
    }

    const wasSelected = this._selected;

    // List options that are selected at initialization can't be reported properly to the form
    // control. This is because it takes some time until the selection-list knows about all
    // available options. Also it can happen that the ControlValueAccessor has an initial value
    // that should be used instead. Deferring the value change report to the next tick ensures
    // that the form control value is not being overwritten.
    Promise.resolve().then(() => {
      if (this._selected || wasSelected) {
        this.selected = true;
        this._changeDetectorRef.markForCheck();
      }
    });
    this._inputsInitialized = true;
  }

  override ngOnDestroy(): void {
    super.ngOnDestroy();

    if (this.selected) {
      // We have to delay this until the next tick in order
      // to avoid changed after checked errors.
      Promise.resolve().then(() => {
        this.selected = false;
      });
    }
  }

  /** Toggles the selection state of the option. */
  toggle(): void {
    this.selected = !this.selected;
  }

  /** Allows for programmatic focusing of the option. */
  focus(): void {
    this._hostElement.focus();
  }

  /** Gets the text label of the list option. Used for the typeahead functionality in the list. */
  getLabel() {
    const titleElement = this._titles?.get(0)?._elementRef.nativeElement;
    // If there is no explicit title element, the unscoped text content
    // is treated as the list item title.
    const labelEl = titleElement || this._unscopedContent?.nativeElement;
    return labelEl?.textContent || '';
  }

  /** Whether a checkbox is shown at the given position. */
  _hasCheckboxAt(position: MatListOptionTogglePosition): boolean {
    return this._selectionList.multiple && this._getTogglePosition() === position;
  }

  /** Where a radio indicator is shown at the given position. */
  _hasRadioAt(position: MatListOptionTogglePosition): boolean {
    return (
      !this._selectionList.multiple &&
      this._getTogglePosition() === position &&
      !this._selectionList.hideSingleSelectionIndicator
    );
  }

  /** Whether icons or avatars are shown at the given position. */
  _hasIconsOrAvatarsAt(position: 'before' | 'after'): boolean {
    return this._hasProjected('icons', position) || this._hasProjected('avatars', position);
  }

  /** Gets whether the given type of element is projected at the specified position. */
  _hasProjected(type: 'icons' | 'avatars', position: 'before' | 'after'): boolean {
    // If the checkbox/radio is shown at the specified position, neither icons or
    // avatars can be shown at the position.
    return (
      this._getTogglePosition() !== position &&
      (type === 'avatars' ? this._avatars.length !== 0 : this._icons.length !== 0)
    );
  }

  _handleBlur() {
    this._selectionList._onTouched();
  }

  /** Gets the current position of the checkbox/radio. */
  _getTogglePosition() {
    return this.togglePosition || 'after';
  }

  /**
   * Sets the selected state of the option.
   * @returns Whether the value has changed.
   */
  _setSelected(selected: boolean): boolean {
    if (selected === this._selected) {
      return false;
    }

    this._selected = selected;

    if (selected) {
      this._selectionList.selectedOptions.select(this);
    } else {
      this._selectionList.selectedOptions.deselect(this);
    }

    this.selectedChange.emit(selected);
    this._changeDetectorRef.markForCheck();
    return true;
  }

  /**
   * Notifies Angular that the option needs to be checked in the next change detection run.
   * Mainly used to trigger an update of the list option if the disabled state of the selection
   * list changed.
   */
  _markForCheck() {
    this._changeDetectorRef.markForCheck();
  }

  /** Toggles the option's value based on a user interaction. */
  _toggleOnInteraction() {
    if (!this.disabled) {
      if (this._selectionList.multiple) {
        this.selected = !this.selected;
        this._selectionList._emitChangeEvent([this]);
      } else if (!this.selected) {
        this.selected = true;
        this._selectionList._emitChangeEvent([this]);
      }
    }
  }

  /** Sets the tabindex of the list option. */
  _setTabindex(value: number) {
    this._hostElement.setAttribute('tabindex', value + '');
  }

  protected _hasBothLeadingAndTrailing(): boolean {
    const hasLeading =
      this._hasProjected('avatars', 'before') ||
      this._hasProjected('icons', 'before') ||
      this._hasCheckboxAt('before') ||
      this._hasRadioAt('before');
    const hasTrailing =
      this._hasProjected('icons', 'after') ||
      this._hasProjected('avatars', 'after') ||
      this._hasCheckboxAt('after') ||
      this._hasRadioAt('after');
    return hasLeading && hasTrailing;
  }
}


/**
 * @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 {FocusKeyManager} from '@angular/cdk/a11y';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {SelectionModel} from '@angular/cdk/collections';
import {A, ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  ElementRef,
  EventEmitter,
  Input,
  NgZone,
  OnChanges,
  OnDestroy,
  Output,
  QueryList,
  SimpleChanges,
  ViewEncapsulation,
  forwardRef,
  inject,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {ThemePalette} from '@angular/material/core';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {MatListBase} from './list-base';
import {MatListOption, SELECTION_LIST, SelectionList} from './list-option';

export const MAT_SELECTION_LIST_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => MatSelectionList),
  multi: true,
};

/** Change event that is being fired whenever the selected state of an option changes. */
export class MatSelectionListChange {
  constructor(
    /** Reference to the selection list that emitted the event. */
    public source: MatSelectionList,
    /** Reference to the options that have been changed. */
    public options: MatListOption[],
  ) {}
}

@Component({
  selector: 'mat-selection-list',
  exportAs: 'matSelectionList',
  host: {
    'class': 'mat-mdc-selection-list mat-mdc-list-base mdc-list',
    'role': 'listbox',
    '[attr.aria-multiselectable]': 'multiple',
    '(keydown)': '_handleKeydown($event)',
  },
  template: '<ng-content></ng-content>',
  styleUrl: 'list.css',
  encapsulation: ViewEncapsulation.None,
  providers: [
    MAT_SELECTION_LIST_VALUE_ACCESSOR,
    {provide: MatListBase, useExisting: MatSelectionList},
    {provide: SELECTION_LIST, useExisting: MatSelectionList},
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatSelectionList
  extends MatListBase
  implements SelectionList, ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy


`<mat-list>` is a container component that wraps and formats a series of `<mat-list-item>`. As the
base list component, it provides Material Design styling, but no behavior of its own.

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

List items can be constructed in two ways depending the content they need to show:

### Simple lists

If a list item needs to show a single line of textual information, the text can be inserted
directly into the `<mat-list-item>` element.

```html
<mat-list>
 <mat-list-item>Pepper</mat-list-item>
 <mat-list-item>Salt</mat-list-item>
 <mat-list-item>Paprika</mat-list-item>
</mat-list>
```

### Multi-line lists

List items that have more than one line of text have to use the `matListItemTitle` directive to
indicate their title text for accessibility purposes, in addition to the `matListItemLine` directive
for each subsequent line of text.

```html
<mat-list>
  <mat-list-item>
    <span matListItemTitle>Pepper</span>
    <span matListItemLine>Produced by a plant</span>
  </mat-list-item>
  <mat-list-item>
    <span matListItemTitle>Salt</span>
    <span matListItemLine>Extracted from sea water</span>
  </mat-list-item>
  <mat-list-item>
    <span matListItemTitle>Paprika</span>
    <span matListItemLine>Produced by dried and ground red peppers</span>
  </mat-list-item>
</mat-list>
```

To activate text wrapping, the `lines` input has to be set on the `<mat-list-item>` indicating the
number of lines of text.

The following directives can be used to style the content of a list item:

| Directive           | Description                                                                |
|---------------------|----------------------------------------------------------------------------|
| `matListItemTitle`  | Indicates the title of the list item. Required for multi-line list items.  |
| `matListItemLine`   | Wraps a line of text within a list item.                                   |
| `matListItemIcon`   | Icon typically placed at the beginning of a list item.                     |
| `matListItemAvatar` | Image typically placed at the beginning of a list item.                    |
| `matListItemMeta`   | Inserts content in the meta section at the end of a list item.             |

### Navigation lists

Use `mat-nav-list` tags for navigation lists (i.e. lists that have anchor tags).

Simple navigation lists can use the `mat-list-item` attribute on anchor tag elements directly:

```html
<mat-nav-list>
  @for (link of list; track link) {
    <a mat-list-item href="..." [activated]="link.isActive">{{ link }}</a>
  }
</mat-nav-list>
```

For more complex navigation lists (e.g. with more than one target per item), wrap the anchor
element in an `<mat-list-item>`.

```html
<mat-nav-list>
  @for (link of links; track link) {
    <mat-list-item [activated]="link.isActive">
       <a matListItemTitle href="...">{{ link }}</a>
       <button mat-icon-button (click)="showInfo(link)" matListItemMeta>
          <mat-icon>info</mat-icon>
       </button>
    </mat-list-item>
  }
</mat-nav-list>
```

### Action lists

Use the `<mat-action-list>` element when each item in the list performs some _action_. Each item
in an action list is a `<button>` element.

Simple action lists can use the `mat-list-item` attribute on button tag elements directly:

```html
<mat-action-list>
  <button mat-list-item (click)="save()">Save</button>
  <button mat-list-item (click)="undo()">Undo</button>
</mat-action-list>
```

### Selection lists
A selection list provides an interface for selecting values, where each list item is an option.

<!-- example(list-selection) -->

The options within a selection-list should not contain further interactive controls, such
as buttons and anchors.

### Multi-line lists
For lists that require multiple lines per item, annotate each line with an `matListItemLine`
attribute. Whichever heading tag is appropriate for your DOM hierarchy should be used
(not necessarily `<h3>` as shown in the example).

```html
<!-- two line list -->
<mat-list>
  @for (message of messages; track message) {
    <mat-list-item>
      <h3 matListItemTitle>{{message.from}}</h3>
      <p matListItemLine>
        <span>{{message.subject}}</span>
        <span class="demo-2"> -- {{message.content}}</span>
      </p>
    </mat-list-item>
  }
</mat-list>

<!-- three line list -->
<mat-list>
  @for (message of messages; track message) {
    <mat-list-item>
      <h3 matListItemTitle>{{message.from}}</h3>
      <p matListItemLine>{{message.subject}}</p>
      <p matListItemLine class="demo-2">{{message.content}}</p>
    </mat-list-item>
  }
</mat-list>
```

### Lists with icons

To add an icon to your list item, use the `matListItemIcon` attribute.

```html
<mat-list>
  @for (message of messages; track message) {
    <mat-list-item>
      <mat-icon matListItemIcon>folder</mat-icon>
      <h3 matListItemTitle>{{message.from}}</h3>
      <p matListItemLine>
        <span>{{message.subject}}</span>
        <span class="demo-2"> -- {{message.content}}</span>
      </p>
    </mat-list-item>
  }
</mat-list>
```

### Lists with avatars

To include an avatar image, add an image tag with an `matListItemAvatar` attribute.

```html
<mat-list>
  @for (message of messages; track message) {
    <mat-list-item>
      <img matListItemAvatar src="..." alt="...">
      <h3 matListItemTitle>{{message.from}}</h3>
      <p matListItemLine>
        <span>{{message.subject}}</span>
        <span class="demo-2"> -- {{message.content}}</span>
      </p>
    </mat-list-item>
  }
</mat-list>
```

### Lists with multiple sections

Subheaders can be added to a list by annotating a heading tag with an `matSubheader` attribute.
To add a divider, use `<mat-divider>`.

```html
<mat-list>
   <h3 matSubheader>Folders</h3>
   @for (folder of folders; track folder) {
     <mat-list-item>
        <mat-icon matListIcon>folder</mat-icon>
        <h4 matListItemTitle>{{folder.name}}</h4>
        <p matListItemLine class="demo-2"> {{folder.updated}} </p>
     </mat-list-item>
   }
   <mat-divider></mat-divider>
   <h3 matSubheader>Notes</h3>
   @for (note of notes; track note) {
     <mat-list-item>
        <mat-icon matListIcon>note</mat-icon>
        <h4 matListItemTitle>{{note.name}}</h4>
        <p matListItemLine class="demo-2"> {{note.updated}} </p>
     </mat-list-item>
   }
</mat-list>
```

### Accessibility

Angular Material offers multiple varieties of list so that you can choose the type that best applies
to your use-case.

#### Navigation

You should use `MatNavList` when every item in the list is an anchor that navigate to another URL.
The root `<mat-nav-list>` element sets `role="navigation"` and should contain only anchor elements
with the `mat-list-item` attribute. You should not nest any interactive elements inside these
anchors, including buttons and checkboxes.

Always provide an accessible label for the `<mat-nav-list>` element via `aria-label` or
`aria-labelledby`.

#### Selection

You should use `MatSelectionList` and `MatListOption` for lists that allow the user to select one
or more values. This list variant uses the `role="listbox"` interaction pattern, handling all
associated keyboard input and focus management. You should not nest any interactive elements inside
these options, including buttons and anchors.

Always provide an accessible label for the `<mat-selection-list>` element via `aria-label` or
`aria-labelledby` that describes the selection being made.

By default, `MatSelectionList` displays radio or checkmark indicators to identify selected items.
While you can hide the radio 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.

#### Custom scenarios

By default, the list assumes that it will be used in a purely decorative fashion and thus it sets no
roles, ARIA attributes, or keyboard shortcuts. This is equivalent to having a sequence of `<div>`
elements on the page. Any interactive content within the list should be given an appropriate
accessibility treatment based on the specific workflow of your application.

If the list is used to present a list of non-interactive content items, then the list element should
be given `role="list"` and each list item should be given `role="listitem"`.


/**
 * @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 {NgModule} from '@angular/core';
import {MatPseudoCheckboxModule, MatRippleModule, MatCommonModule} from '@angular/material/core';
import {MatDividerModule} from '@angular/material/divider';
import {MatActionList} from './action-list';
import {MatList, MatListItem} from './list';
import {MatListOption} from './list-option';
import {MatListSubheaderCssMatStyler} from './subheader';
import {
  MatListItemLine,
  MatListItemTitle,
  MatListItemMeta,
  MatListItemAvatar,
  MatListItemIcon,
} from './list-item-sections';
import {MatNavList} from './nav-list';
import {MatSelectionList} from './selection-list';
import {ObserversModule} from '@angular/cdk/observers';

@NgModule({
  imports: [
    ObserversModule,
    MatCommonModule,
    MatRippleModule,
    MatPseudoCheckboxModule,
    MatList,
    MatActionList,
    MatNavList,
    MatSelectionList,
    MatListItem,
    MatListOption,
    MatListSubheaderCssMatStyler,
    MatListItemAvatar,
    MatListItemIcon,
    MatListItemLine,
    MatListItemTitle,
    MatListItemMeta,
  ],
  exports: [
    MatList,
    MatActionList,
    MatNavList,
    MatSelectionList,
    MatListItem,
    MatListOption,
    MatListItemAvatar,
    MatListItemIcon,
    MatListSubheaderCssMatStyler,
    MatDividerModule,
    MatListItemLine,
    MatListItemTitle,
    MatListItemMeta,
  ],
})
export class MatListModule {}


it('should be able to deselect all options, even if they are disabled', () => {
      const list: MatSelectionList = selectionList.componentInstance;

      list.options.forEach(option => option.toggle());
      expect(list.options.toArray().every(option => option.selected)).toBe(true);

      list.options.forEach(option => (option.disabled = true));
      fixture.detectChanges();

      list.deselectAll();
      fixture.detectChanges();

      expect(list.options.toArray().every(option => option.selected)).toBe(false);
    });

    it('should update the list value when an item is selected programmatically', () => {
      const list: MatSelectionList = selectionList.componentInstance;

      expect(list.selectedOptions.isEmpty()).toBe(true);

      listOptions[0].componentInstance.selected = true;
      listOptions[2].componentInstance.selected = true;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();

      expect(list.selectedOptions.isEmpty()).toBe(false);
      expect(list.selectedOptions.isSelected(listOptions[0].componentInstance)).toBe(true);
      expect(list.selectedOptions.isSelected(listOptions[2].componentInstance)).toBe(true);
    });

    it('should update the item selected state when it is selected via the model', () => {
      const list: MatSelectionList = selectionList.componentInstance;
      const item: MatListOption = listOptions[0].componentInstance;

      expect(item.selected).toBe(false);

      list.selectedOptions.select(item);
      fixture.detectChanges();

      expect(item.selected).toBe(true);
    });

    it('should set aria-multiselectable to true on the selection list element', () => {
      expect(selectionList.nativeElement.getAttribute('aria-multiselectable')).toBe('true');
    });

    it('should be able to reach list options that are indirect descendants', () => {
      const descendatsFixture = TestBed.createComponent(SelectionListWithIndirectChildOptions);
      descendatsFixture.detectChanges();
      listOptions = descendatsFixture.debugElement.queryAll(By.directive(MatListOption));
      selectionList = descendatsFixture.debugElement.query(By.directive(MatSelectionList))!;
      const list: MatSelectionList = selectionList.componentInstance;

      expect(list.options.toArray().every(option => option.selected)).toBe(false);

      list.selectAll();
      descendatsFixture.detectChanges();

      expect(list.options.toArray().every(option => option.selected)).toBe(true);
    });

    it('should disable list item ripples when the ripples on the list have been disabled', fakeAsync(() => {
      const rippleTarget = fixture.nativeElement.querySelector(
        '.mat-mdc-list-option:not(.mdc-list-item--disabled)',
      );
      dispatchMouseEvent(rippleTarget, 'mousedown');
      dispatchMouseEvent(rippleTarget, 'mouseup');

      // Flush the ripple enter animation.
      dispatchFakeEvent(rippleTarget.querySelector('.mat-ripple-element')!, 'transitionend');

      expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
        .withContext('Expected ripples to be enabled by default.')
        .toBe(1);

      // Flush the ripple exit animation.
      dispatchFakeEvent(rippleTarget.querySelector('.mat-ripple-element')!, 'transitionend');

      expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
        .withContext('Expected ripples to go away.')
        .toBe(0);

      fixture.componentInstance.listRippleDisabled = true;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();

      dispatchMouseEvent(rippleTarget, 'mousedown');
      dispatchMouseEvent(rippleTarget, 'mouseup');

      expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
        .withContext('Expected no ripples after list ripples are disabled.')
        .toBe(0);
    }));

    it('can bind both selected and value at the same time', () => {
      const componentFixture = TestBed.createComponent(SelectionListWithSelectedOptionAndValue);
      componentFixture.detectChanges();
      const listItemEl = componentFixture.debugElement.query(By.directive(MatListOption))!;
      expect(listItemEl.componentInstance.selected).toBe(true);
      expect(listItemEl.componentInstance.value).toBe(componentFixture.componentInstance.itemValue);
    });

    it('should have a focus indicator', () => {
      const optionNativeElements = listOptions.map(option => option.nativeElement as HTMLElement);

      expect(
        optionNativeElements.every(
          element => element.querySelector('.mat-focus-indicator') !== null,
        ),
      ).toBe(true);
    });

    it('should hide the internal SVG', () => {
      listOptions.forEach(option => {
        const svg = option.nativeElement.querySelector('.mdc-checkbox svg');
        expect(svg.getAttribute('aria-hidden')).toBe('true');
      });
    });
  });

  describe('multiple-selection with list option selected', () => {
    let fixture: ComponentFixture<SelectionListWithSelectedOption>;
    let listOptionElements: DebugElement[];
    let selectionList: DebugElement;

    beforeEach(waitForAsync(() => {
      TestBed.configureTestingModule({
        imports: [MatListModule, SelectionListWithSelectedOption],
      });
    }));

    beforeEach(waitForAsync(() => {
      fixture = TestBed.createComponent(SelectionListWithSelectedOption);
      listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!;
      selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!;
      fixture.detectChanges();
    }));

    it('should set its initial selected state in the selectedOptions', () => {
      let options = listOptionElements.map(optionEl =>
        optionEl.injector.get<MatListOption>(MatListOption),
      );
      let selectedOptions = selectionList.componentInstance.selectedOptions;
      expect(selectedOptions.isSelected(options[0])).toBeFalse();
      expect(selectedOptions.isSelected(options[1])).toBeTrue();
      expect(selectedOptions.isSelected(options[2])).toBeTrue();
      expect(selectedOptions.isSelected(options[3])).toBeFalse();
    });

    it('should focus the first selected option on first focus if an item is pre-selected', fakeAsync(() => {
      // MDC manages the focus through setting a `tabindex` on the designated list item. We
      // assert that the proper tabindex is set on the pre-selected option at index 1, and
      // ensure that other options are not reachable through tab.
      expect(listOptionElements.map(el => el.nativeElement.tabIndex)).toEqual([-1, 0, -1, -1]);
    }));
  });

  describe('single-selection with list option selected', () => {
    let fixture: ComponentFixture<SingleSelectionListWithSelectedOption>;
    let listOptionElements: DebugElement[];

    beforeEach(waitForAsync(() => {
      TestBed.configureTestingModule({
        imports: [MatListModule, SingleSelectionListWithSelectedOption],
      });
    }));

    beforeEach(waitForAsync(() => {
      fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption);
      listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!;
      fixture.detectChanges();
    }));

    it('displays radio indicators by default', () => {
      expect(
        listOptionElements[0].nativeElement.querySelector('input[type="radio"]'),
      ).not.toBeNull();
      expect(
        listOptionElements[1].nativeElement.querySelector('input[type="radio"]'),
      ).not.toBeNull();

      expect(listOptionElements[0].nativeElement.classList).not.toContain(
        'mdc-list-item--selected',
      );
      expect(listOptionElements[1].nativeElement.classList).not.toContain(
        'mdc-list-item--selected',
      );
    });
  });

  describe('with token to hide radio indicators', () => {
    let fixture: ComponentFixture<SingleSelectionListWithSelectedOption>;
    let listOptionElements: DebugElement[];

    beforeEach(waitForAsync(() => {
      const matListConfig: MatListConfig = {hideSingleSelectionIndicator: true};

      TestBed.configureTestingModule({
        imports: [MatListModule, SingleSelectionListWithSelectedOption],
        providers: [{provide: MAT_LIST_CONFIG, useValue: matListConfig}],
      });
    }));

    beforeEach(waitForAsync(() => {
      fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption);
      listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!;
      fixture.detectChanges();
    }));

    it('does not display radio indicators', () => {
      expect(listOptionElements[0].nativeElement.querySelector('input[type="radio"]')).toBeNull();
      expect(listOptionElements[1].nativeElement.querySelector('input[type="radio"]')).toBeNull();

      expect(listOptionElements[0].nativeElement.classList).not.toContain(
        'mdc-list-item--selected',
      );

      expect(listOptionElements[1].nativeElement.getAttribute('aria-selected'))
        .withContext('Expected second option to be selected')
        .toBe('true');
      expect(listOptionElements[1].nativeElement.classList).toContain('mdc-list-item--selected');
    });
  });


describe('and formControl', () => {
    let fixture: ComponentFixture<SelectionListWithFormControl>;
    let listOptions: MatListOption[];
    let selectionList: MatSelectionList;

    beforeEach(() => {
      fixture = TestBed.createComponent(SelectionListWithFormControl);
      fixture.detectChanges();

      selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!.componentInstance;
      listOptions = fixture.debugElement
        .queryAll(By.directive(MatListOption))
        .map(optionDebugEl => optionDebugEl.componentInstance);
    });

    it('should be able to disable options from the control', () => {
      selectionList.focus();
      expect(selectionList.disabled)
        .withContext('Expected the selection list to be enabled.')
        .toBe(false);
      expect(listOptions.every(option => !option.disabled))
        .withContext('Expected every list option to be enabled.')
        .toBe(true);
      expect(
        listOptions.some(
          option => option._elementRef.nativeElement.getAttribute('tabindex') === '0',
        ),
      )
        .withContext('Expected one list item to be in the tab order')
        .toBe(true);

      fixture.componentInstance.formControl.disable();
      fixture.detectChanges();

      expect(selectionList.disabled)
        .withContext('Expected the selection list to be disabled.')
        .toBe(true);
      expect(listOptions.every(option => option.disabled))
        .withContext('Expected every list option to be disabled.')
        .toBe(true);
      expect(
        listOptions.every(
          option => option._elementRef.nativeElement.getAttribute('tabindex') === '-1',
        ),
      )
        .withContext('Expected every list option to be removed from the tab order')
        .toBe(true);
    });

    it('should be able to update the disabled property after form control disabling', () => {
      expect(listOptions.every(option => !option.disabled))
        .withContext('Expected every list option to be enabled.')
        .toBe(true);

      fixture.componentInstance.formControl.disable();
      fixture.detectChanges();

      expect(listOptions.every(option => option.disabled))
        .withContext('Expected every list option to be disabled.')
        .toBe(true);

      // Previously the selection list has been disabled through FormControl#disable. Now we
      // want to verify that we can still change the disabled state through updating the disabled
      // property. Calling FormControl#disable should not lock the disabled property.
      // See: https://github.com/angular/material2/issues/12107
      selectionList.disabled = false;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();

      expect(listOptions.every(option => !option.disabled))
        .withContext('Expected every list option to be enabled.')
        .toBe(true);
    });

    it('should be able to set the value through the form control', () => {
      expect(listOptions.every(option => !option.selected))
        .withContext('Expected every list option to be unselected.')
        .toBe(true);

      fixture.componentInstance.formControl.setValue(['opt2', 'opt3']);
      fixture.detectChanges();

      expect(listOptions[1].selected)
        .withContext('Expected second option to be selected.')
        .toBe(true);
      expect(listOptions[2].selected)
        .withContext('Expected third option to be selected.')
        .toBe(true);

      fixture.componentInstance.formControl.setValue(null);
      fixture.detectChanges();

      expect(listOptions.every(option => !option.selected))
        .withContext('Expected every list option to be unselected.')
        .toBe(true);
    });

    it('should deselect option whose value no longer matches', () => {
      const option = listOptions[1];

      fixture.componentInstance.formControl.setValue(['opt2']);
      fixture.detectChanges();

      expect(option.selected).withContext('Expected option to be selected.').toBe(true);

      option.value = 'something-different';
      fixture.detectChanges();

      expect(option.selected).withContext('Expected option not to be selected.').toBe(false);
      expect(fixture.componentInstance.formControl.value).toEqual([]);
    });

    it('should mark options as selected when the value is set before they are initialized', () => {
      fixture.destroy();
      fixture = TestBed.createComponent(SelectionListWithFormControl);

      fixture.componentInstance.formControl.setValue(['opt2', 'opt3']);
      fixture.detectChanges();

      listOptions = fixture.debugElement
        .queryAll(By.directive(MatListOption))
        .map(optionDebugEl => optionDebugEl.componentInstance);

      expect(listOptions[1].selected)
        .withContext('Expected second option to be selected.')
        .toBe(true);
      expect(listOptions[2].selected)
        .withContext('Expected third option to be selected.')
        .toBe(true);
    });

    it('should not clear the form control when the list is destroyed', fakeAsync(() => {
      const option = listOptions[1];

      option.selected = true;
      fixture.detectChanges();

      expect(fixture.componentInstance.formControl.value).toEqual(['opt2']);

      fixture.componentInstance.renderList = false;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();
      tick();
      fixture.detectChanges();

      expect(fixture.componentInstance.formControl.value).toEqual(['opt2']);
    }));

    it('should mark options added at a later point as selected', () => {
      fixture.componentInstance.formControl.setValue(['opt4']);
      fixture.detectChanges();

      fixture.componentInstance.renderExtraOption = true;
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();

      listOptions = fixture.debugElement
        .queryAll(By.directive(MatListOption))
        .map(optionDebugEl => optionDebugEl.componentInstance);

      expect(listOptions.length).toBe(4);
      expect(listOptions[3].selected).toBe(true);
    });
  });

  describe('preselected values', () => {
    it('should add preselected options to the model value', fakeAsync(() => {
      const fixture = TestBed.createComponent(SelectionListWithPreselectedOption);
      const listOptions = fixture.debugElement
        .queryAll(By.directive(MatListOption))
        .map(optionDebugEl => optionDebugEl.componentInstance);

      fixture.detectChanges();
      tick();

      expect(listOptions[1].selected).toBe(true);
      expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']);
    }));

    it('should handle preselected option both through the model and the view', fakeAsync(() => {
      const fixture = TestBed.createComponent(SelectionListWithPreselectedOptionAndModel);
      const listOptions = fixture.debugElement
        .queryAll(By.directive(MatListOption))
        .map(optionDebugEl => optionDebugEl.componentInstance);

      fixture.detectChanges();
      tick();

      expect(listOptions[0].selected).toBe(true);
      expect(listOptions[1].selected).toBe(true);
      expect(fixture.componentInstance.selectedOptions).toEqual(['opt1', 'opt2']);
    }));

    it('should show the item as selected when preselected inside OnPush parent', fakeAsync(() => {
      const fixture = TestBed.createComponent(SelectionListWithPreselectedFormControlOnPush);
      fixture.detectChanges();

      const option = fixture.debugElement.queryAll(By.directive(MatListOption))[1];
      const checkbox = option.nativeElement.querySelector(
        '.mdc-checkbox__native-control',
      ) as HTMLInputElement;

      fixture.detectChanges();
      flush();
      fixture.detectChanges();

      expect(option.componentInstance.selected).toBe(true);
      expect(checkbox.checked).toBe(true);
    }));
  });

  describe('with custom compare function', () => {
    it('should use a custom comparator to determine which options are selected', fakeAsync(() => {
      const fixture = TestBed.createComponent(SelectionListWithCustomComparator);
      const testComponent = fixture.componentInstance;

      testComponent.compareWith = jasmine
        .createSpy('comparator', (o1: any, o2: any) => {
          return o1 && o2 && o1.id === o2.id;
        })
        .and.callThrough();

      testComponent.selectedOptions = [{id: 2, label: 'Two'}];
      fixture.changeDetectorRef.markForCheck();
      fixture.detectChanges();
      tick();

      expect(testComponent.compareWith).toHaveBeenCalled();
      expect(testComponent.optionInstances.toArray()[1].selected).toBe(true);
    }));
  });
});

@Component({
  template: `
  <mat-selection-list
    id="selection-list-1"
    (selectionChange)="onSelectionChange($event)"
    [disableRipple]="listRippleDisabled"
    [color]="selectionListColor"
    [multiple]="multiple">
    <mat-list-option togglePosition="before" disabled="true" value="inbox"
                     [color]="firstOptionColor">
      Inbox (disabled selection-option)
    </mat-list-option>
    <mat-list-option id="testSelect" togglePosition="before" class="test-native-focus"
                    value="starred">
      Starred
    </mat-list-option>
    <mat-list-option togglePosition="before" value="sent-mail">
      Sent Mail
    </mat-list-option>
    <mat-list-option togglePosition="before" value="archive">
      Archive
    </mat-list-option>
    @if (showLastOption) {
      <mat-list-option togglePosition="before" value="drafts">
        Drafts
      </mat-list-option>
    }
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithListOptions {
  showLastOption = true;
  listRippleDisabled = false;
  multiple = true;
  selectionListColor: ThemePalette;
  firstOptionColor: ThemePalette;

  onSelectionChange(_change: MatSelectionListChange) {}
}


@Component({
  template: `
  <mat-selection-list id="selection-list-2">
    <mat-list-option togglePosition="after">
      Inbox (disabled selection-option)
    </mat-list-option>
    <mat-list-option id="testSelect" togglePosition="after">
      Starred
    </mat-list-option>
    <mat-list-option togglePosition="after">
      Sent Mail
    </mat-list-option>
    <mat-list-option togglePosition="after">
      Drafts
    </mat-list-option>
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithCheckboxPositionAfter {}

@Component({
  template: `
  <mat-selection-list id="selection-list-3" [disabled]="disabled">
    <mat-list-option togglePosition="after">
      Inbox (disabled selection-option)
    </mat-list-option>
    <mat-list-option id="testSelect" togglePosition="after">
      Starred
    </mat-list-option>
    <mat-list-option togglePosition="after">
      Sent Mail
    </mat-list-option>
    <mat-list-option togglePosition="after">
      Drafts
    </mat-list-option>
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithListDisabled {
  disabled: boolean = true;
}

@Component({
  template: `
  <mat-selection-list>
    <mat-list-option [disabled]="disableItem">Item</mat-list-option>
  </mat-selection-list>
  `,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithDisabledOption {
  disableItem: boolean = false;
}

@Component({
  template: `
  <mat-selection-list>
    <mat-list-option>Not selected - Item #1</mat-list-option>
    <mat-list-option [selected]="true">Pre-selected - Item #2</mat-list-option>
    <mat-list-option [selected]="true">Pre-selected - Item #3</mat-list-option>
    <mat-list-option>Not selected - Item #4</mat-list-option>
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithSelectedOption {}

@Component({
  template: `
  <mat-selection-list [multiple]="false">
    <mat-list-option>Not selected - Item #1</mat-list-option>
    <mat-list-option [selected]="true">Pre-selected - Item #2</mat-list-option>
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SingleSelectionListWithSelectedOption {}

@Component({
  template: `
  <mat-selection-list>
    <mat-list-option [selected]="true" [value]="itemValue">Item</mat-list-option>
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithSelectedOptionAndValue {
  itemValue = 'item1';
}

@Component({
  template: `
  <mat-selection-list id="selection-list-4">
    <mat-list-option togglePosition="after" class="test-focus" id="123">
      Inbox
    </mat-list-option>
  </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithOnlyOneOption {}

@Component({
  template: `
    <mat-selection-list
      [(ngModel)]="selectedOptions"
      (ngModelChange)="modelChangeSpy()"
      [multiple]="multiple">
      @for (option of options; track option) {
        <mat-list-option [value]="option">{{option}}</mat-list-option>
      }
    </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule, FormsModule, ReactiveFormsModule],
})
class SelectionListWithModel {
  modelChangeSpy = jasmine.createSpy('model change spy');
  selectedOptions: string[] = [];
  multiple = true;
  options = ['opt1', 'opt2', 'opt3'];
}

@Component({
  template: `
    @if (renderList) {
      <mat-selection-list [formControl]="formControl">
        <mat-list-option value="opt1">Option 1</mat-list-option>
        <mat-list-option value="opt2">Option 2</mat-list-option>
        <mat-list-option value="opt3">Option 3</mat-list-option>
        @if (renderExtraOption) {
          <mat-list-option value="opt4">Option 4</mat-list-option>
        }
      </mat-selection-list>
    }
  `,
  standalone: true,
  imports: [MatListModule, FormsModule, ReactiveFormsModule],
})
class SelectionListWithFormControl {
  formControl = new FormControl([] as string[]);
  renderList = true;
  renderExtraOption = false;
}

@Component({
  template: `
    <mat-selection-list [(ngModel)]="selectedOptions">
      <mat-list-option value="opt1">Option 1</mat-list-option>
      <mat-list-option value="opt2" selected>Option 2</mat-list-option>
    </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule, FormsModule, ReactiveFormsModule],
})
class SelectionListWithPreselectedOption {
  selectedOptions: string[];
}

@Component({
  template: `
    <mat-selection-list [(ngModel)]="selectedOptions">
      <mat-list-option value="opt1">Option 1</mat-list-option>
      <mat-list-option value="opt2" selected>Option 2</mat-list-option>
    </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule, FormsModule, ReactiveFormsModule],
})
class SelectionListWithPreselectedOptionAndModel {
  selectedOptions = ['opt1'];
}

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <mat-selection-list [formControl]="formControl">
      @for (opt of opts; track opt) {
        <mat-list-option [value]="opt">{{opt}}</mat-list-option>
      }
    </mat-selection-list>
  `,
  standalone: true,
  imports: [MatListModule, FormsModule, ReactiveFormsModule],
})
class SelectionListWithPreselectedFormControlOnPush {
  opts = ['opt1', 'opt2', 'opt3'];
  formControl = new FormControl(['opt2']);
}

@Component({
  template: `
    <mat-selection-list [(ngModel)]="selectedOptions" [compareWith]="compareWith">
      @for (option of options; track option) {
        <mat-list-option [value]="option">{{option.label}}</mat-list-option>
      }
    </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule, FormsModule, ReactiveFormsModule],
})
class SelectionListWithCustomComparator {
  @ViewChildren(MatListOption) optionInstances: QueryList<MatListOption>;
  selectedOptions: {id: number; label: string}[] = [];
  compareWith?: (o1: any, o2: any) => boolean;
  options = [
    {id: 1, label: 'One'},
    {id: 2, label: 'Two'},
    {id: 3, label: 'Three'},
  ];
}

@Component({
  template: `
    <mat-selection-list>
      <mat-list-option [togglePosition]="togglePosition">

I

        Inbox
      </mat-list-option>
    </mat-selection-list>
  `,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithAvatar {
  togglePosition: MatListOptionTogglePosition | undefined;
}

@Component({
  template: `
    <mat-selection-list>
      <mat-list-option [togglePosition]="togglePosition">

I

        Inbox
      </mat-list-option>
    </mat-selection-list>
  `,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithIcon {
  togglePosition: MatListOptionTogglePosition | undefined;
}

@Component({
  // Note the blank `@if` which we need in order to hit the bug that we're testing.
  template: `
    <mat-selection-list>
      @if (true) {
        <mat-list-option [value]="1">One</mat-list-option>
        <mat-list-option [value]="2">Two</mat-list-option>
      }
    </mat-selection-list>`,
  standalone: true,
  imports: [MatListModule],
})
class SelectionListWithIndirectChildOptions {
  @ViewChildren(MatListOption) optionInstances: QueryList<MatListOption>;
}

@Component({
  template: `
  <mat-selection-list>
    <mat-list-option [(selected)]="selected">Item</mat-list-option>
  </mat-selection-list>
`,
  standalone: true,
  imports: [MatListModule],
})
class ListOptionWithTwoWayBinding {
  selected = false;
}


`<mat-menu>` is a floating panel containing list of options.

By itself, the `<mat-menu>` element does not render anything. The menu is attached to and opened
via application of the `matMenuTriggerFor` directive:

### Toggling the menu programmatically
The menu exposes an API to open/close programmatically. Please note that in this case, an
`matMenuTriggerFor` directive is still necessary to attach the menu to a trigger element in the DOM.

```ts
class MyComponent {
  @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;

  someMethod() {
    this.trigger.openMenu();
  }
}
```

### Icons
Menus support displaying `mat-icon` elements before the menu item text.

### Customizing menu position

By default, the menu will display below (y-axis), after (x-axis), without overlapping
its trigger. The position can be changed using the `xPosition` (`before | after`) and `yPosition`
(`above | below`) attributes. The menu can be forced to overlap the trigger using the
`overlapTrigger` attribute.

### Nested menu

Material supports the ability for an `mat-menu-item` to open a sub-menu. To do so, you have to define
your root menu and sub-menus, in addition to setting the `[matMenuTriggerFor]` on the `mat-menu-item`
that should trigger the sub-menu:

### Lazy rendering
By default, the menu content will be initialized even when the panel is closed. To defer
initialization until the menu is open, the content can be provided as an `ng-template`
with the `matMenuContent` attribute:

```html
<mat-menu #appMenu="matMenu">
  <ng-template matMenuContent>
    <button mat-menu-item>Settings</button>
    <button mat-menu-item>Help</button>
  </ng-template>
</mat-menu>

<button mat-icon-button [matMenuTriggerFor]="appMenu">
  <mat-icon>more_vert</mat-icon>
</button>
```

### Passing in data to a menu
When using lazy rendering, additional context data can be passed to the menu panel via
the `matMenuTriggerData` input. This allows for a single menu instance to be rendered
with a different set of data, depending on the trigger that opened it:

```html
<mat-menu #appMenu="matMenu">
  <ng-template matMenuContent let-name="name">
    <button mat-menu-item>Settings</button>
    <button mat-menu-item>Log off {{name}}</button>
  </ng-template>
</mat-menu>

<button mat-icon-button [matMenuTriggerFor]="appMenu" [matMenuTriggerData]="{name: 'Sally'}">
  <mat-icon>more_vert</mat-icon>
</button>

<button mat-icon-button [matMenuTriggerFor]="appMenu" [matMenuTriggerData]="{name: 'Bob'}">
  <mat-icon>more_vert</mat-icon>
</button>
```

### Keyboard interaction
| Keyboard shortcut      | Action                                      |
|------------------------|---------------------------------------------|
| Down Arrow  | Focus the next menu item.                   |
| Up Arrow    | Focus the previous menu item.               |
| Left Arrow  | Close the current menu if it is a sub-menu. |
| Right Arrow | Opens the current menu item's sub-menu.     |
| Enter       | Activate the focused menu item.             |
| Escape      | Close all open menus.                       |

### Accessibility

Angular Material's menu component consists of two connected parts: the trigger and the pop-up menu.

The menu trigger is a standard button element augmented with `aria-haspopup`, `aria-expanded`, and
`aria-controls` to create the relationship to the pop-up panel.

The pop-up menu implements the `role="menu"` pattern, handling keyboard interaction and focus
management. Upon opening, the trigger will focus the first focusable menu item. Upon close, the menu
will return focus to its trigger. Avoid creating a menu in which all items are disabled, instead
hiding or disabling the menu trigger.

Angular Material does not support the `menuitemcheckbox` or `menuitemradio` roles.

Always provide an accessible label via `aria-label` or `aria-labelledby` for any menu
triggers or menu items without descriptive text content.

MatMenu should not contain any interactive controls aside from MatMenuItem.


@Component({
  selector: 'custom-menu',
  template: `
    <ng-template>
      Custom Menu header
      <ng-content></ng-content>
    </ng-template>
  `,
  exportAs: 'matCustomMenu',
  standalone: false,
})
class CustomMenuPanel implements MatMenuPanel {
  direction: Direction;
  xPosition: MenuPositionX = 'after';
  yPosition: MenuPositionY = 'below';
  overlapTrigger = true;
  parentMenu: MatMenuPanel;

  @ViewChild(TemplateRef) templateRef: TemplateRef<any>;
  @Output() readonly close = new EventEmitter<void | 'click' | 'keydown' | 'tab'>();
  focusFirstItem = () => {};
  resetActiveItem = () => {};
  setPositionClasses = () => {};
}

@Component({
  template: `
    <button [matMenuTriggerFor]="menu">Toggle menu</button>
    <custom-menu #menu="matCustomMenu">
      <button mat-menu-item> Custom Content </button>
    </custom-menu>
  `,
  standalone: false,
})
class CustomMenu {
  @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
}

@Component({
  template: `
    <button
      [matMenuTriggerFor]="root"
      #rootTrigger="matMenuTrigger"
      #rootTriggerEl>Toggle menu</button>

    <button
      [matMenuTriggerFor]="levelTwo"
      #alternateTrigger="matMenuTrigger">Toggle alternate menu</button>

    <mat-menu #root="matMenu" (closed)="rootCloseCallback($event)">
      <button mat-menu-item
        id="level-one-trigger"
        [matMenuTriggerFor]="levelOne"
        #levelOneTrigger="matMenuTrigger">One</button>
      <button mat-menu-item>Two</button>
      @if (showLazy) {
        <button mat-menu-item
          id="lazy-trigger"
          [matMenuTriggerFor]="lazy"
          #lazyTrigger="matMenuTrigger">Three</button>
      }
    </mat-menu>

    <mat-menu #levelOne="matMenu" (closed)="levelOneCloseCallback($event)">
      <button mat-menu-item>Four</button>
      <button mat-menu-item
        id="level-two-trigger"
        [matMenuTriggerFor]="levelTwo"
        #levelTwoTrigger="matMenuTrigger">Five</button>
      <button mat-menu-item>Six</button>
    </mat-menu>

    <mat-menu #levelTwo="matMenu" (closed)="levelTwoCloseCallback($event)">
      <button mat-menu-item>Seven</button>
      <button mat-menu-item>Eight</button>
      <button mat-menu-item>Nine</button>
    </mat-menu>

    <mat-menu #lazy="matMenu">
      <button mat-menu-item>Ten</button>
      <button mat-menu-item>Eleven</button>
      <button mat-menu-item>Twelve</button>
    </mat-menu>
  `,
  standalone: false,
})
class NestedMenu {
  @ViewChild('root') rootMenu: MatMenu;
  @ViewChild('rootTrigger') rootTrigger: MatMenuTrigger;
  @ViewChild('rootTriggerEl') rootTriggerEl: ElementRef<HTMLElement>;
  @ViewChild('alternateTrigger') alternateTrigger: MatMenuTrigger;
  readonly rootCloseCallback = jasmine.createSpy('root menu closed callback');

  @ViewChild('levelOne') levelOneMenu: MatMenu;
  @ViewChild('levelOneTrigger') levelOneTrigger: MatMenuTrigger;
  readonly levelOneCloseCallback = jasmine.createSpy('level one menu closed callback');

  @ViewChild('levelTwo') levelTwoMenu: MatMenu;
  @ViewChild('levelTwoTrigger') levelTwoTrigger: MatMenuTrigger;
  readonly levelTwoCloseCallback = jasmine.createSpy('level one menu closed callback');

  @ViewChild('lazy') lazyMenu: MatMenu;
  @ViewChild('lazyTrigger') lazyTrigger: MatMenuTrigger;
  showLazy = false;
}

@Component({
  template: `
    <button [matMenuTriggerFor]="root" #rootTrigger="matMenuTrigger">Toggle menu</button>

    <mat-menu #root="matMenu">
      <button mat-menu-item
        [matMenuTriggerFor]="levelOne"
        #levelOneTrigger="matMenuTrigger">One</button>
    </mat-menu>

    <mat-menu #levelOne="matMenu" class="mat-elevation-z24">
      <button mat-menu-item>Two</button>
    </mat-menu>
  `,
  standalone: false,
})
class NestedMenuCustomElevation {
  @ViewChild('rootTrigger') rootTrigger: MatMenuTrigger;
  @ViewChild('levelOneTrigger') levelOneTrigger: MatMenuTrigger;
}

@Component({
  template: `
    <button [matMenuTriggerFor]="root" #rootTriggerEl>Toggle menu</button>
    <mat-menu #root="matMenu">
      @for (item of items; track $index) {
        <button
          mat-menu-item
          class="level-one-trigger"
          [matMenuTriggerFor]="levelOne">{{item}}</button>
      }
    </mat-menu>

    <mat-menu #levelOne="matMenu">
      <button mat-menu-item>Four</button>
      <button mat-menu-item>Five</button>
    </mat-menu>
  `,
  standalone: false,
})
class NestedMenuRepeater {
  @ViewChild('rootTriggerEl') rootTriggerEl: ElementRef<HTMLElement>;
  @ViewChild('levelOneTrigger') levelOneTrigger: MatMenuTrigger;

  items = ['one', 'two', 'three'];
}

@Component({
  template: `
    <button [matMenuTriggerFor]="root" #rootTriggerEl>Toggle menu</button>

    <mat-menu #root="matMenu">
      <button mat-menu-item class="level-one-trigger" [matMenuTriggerFor]="levelOne">One</button>

      <mat-menu #levelOne="matMenu">
        <button mat-menu-item class="level-two-item">Two</button>
      </mat-menu>
    </mat-menu>
  `,
  standalone: false,
})
class SubmenuDeclaredInsideParentMenu {
  @ViewChild('rootTriggerEl') rootTriggerEl: ElementRef;
}

@Component({
  selector: 'mat-icon',
  template: '<ng-content></ng-content>',
  standalone: false,
})
class FakeIcon {}

@Component({
  template: `
    <button [matMenuTriggerFor]="menu" #triggerEl>Toggle menu</button>

    <mat-menu #menu="matMenu">
      <ng-template matMenuContent>
        <button mat-menu-item>Item</button>
        <button mat-menu-item>Another item</button>
      </ng-template>
    </mat-menu>
  `,
  standalone: false,
})
class SimpleLazyMenu {
  @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
  @ViewChild('triggerEl') triggerEl: ElementRef<HTMLElement>;
  @ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
}

@Component({
  template: `
    <button
      [matMenuTriggerFor]="menu"
      [matMenuTriggerData]="{label: 'one'}"
      #triggerOne="matMenuTrigger">One</button>

    <button
      [matMenuTriggerFor]="menu"
      [matMenuTriggerData]="{label: 'two'}"
      #triggerTwo="matMenuTrigger">Two</button>

    <mat-menu #menu="matMenu">
      <ng-template let-label="label" matMenuContent>
        <button mat-menu-item>{{label}}</button>
      </ng-template>
    </mat-menu>
  `,
  standalone: false,
})
class LazyMenuWithContext {
  @ViewChild('triggerOne') triggerOne: MatMenuTrigger;
  @ViewChild('triggerTwo') triggerTwo: MatMenuTrigger;
}

@Component({
  template: `
    <button [matMenuTriggerFor]="one">Toggle menu</button>
    <mat-menu #one="matMenu">
      <button mat-menu-item>One</button>
    </mat-menu>

    <mat-menu #two="matMenu">
      <button mat-menu-item>Two</button>
    </mat-menu>
  `,
  standalone: false,
})
class DynamicPanelMenu {
  @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
  @ViewChild('one') firstMenu: MatMenu;
  @ViewChild('two') secondMenu: MatMenu;
}

@Component({
  template: `
    <button [matMenuTriggerFor]="menu">Toggle menu</button>

    <mat-menu #menu="matMenu">
      <button mat-menu-item role="menuitemcheckbox" aria-checked="true">Checked</button>
      <button mat-menu-item role="menuitemcheckbox" aria-checked="false">Not checked</button>
    </mat-menu>
  `,
  standalone: false,
})
class MenuWithCheckboxItems {
  @ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
}