@use '@angular/material' as mat;

:host {
  display: block;
  max-width: 1000px;
}

h1 {
  font: var(--mat-sys-title-large);
  font-size: 28px;
  padding-top: 32px;
}

h2 {
  font: var(--mat-sys-title-large);
}

a {
  color: var(--mat-sys-primary);
}

.demo-warn {
  background: var(--mat-sys-error-container);
  color: var(--mat-sys-on-error-container);
  border: 1px solid var(--mat-sys-outline-variant);
  border-radius: var(--mat-sys-corner-extra-small);
  padding: 8px;
}

.demo-group {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 24px;
  margin-top: 24px;
}

@media (max-width: 1000px) {
  .demo-group {  grid-template-columns: auto;}
}

.demo-color-container {
  border-radius: var(--mat-sys-corner-small);
  display: inline-block;
  font: var(--mat-sys-body-medium);
  vertical-align: top;
}

.demo-heading {
  color: var(--mat-sys-on-primary);
  background: var(--mat-sys-primary);
  border: 1px solid var(--mat-sys-outline);
  border-top-right-radius: var(--mat-sys-corner-small);
  border-top-left-radius: var(--mat-sys-corner-small);
  border-bottom: none;
  padding: 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.demo-name {
  font: var(--mat-sys-title-medium);
}

.demo-variable {
  font: var(--mat-sys-title-small);
  font-family: monospace;
  text-align: right;
}

.demo-description {
  border: 1px solid var(--mat-sys-outline);
  border-bottom-right-radius: var(--mat-sys-corner-small);
  border-bottom-left-radius: var(--mat-sys-corner-small);
  padding: 0 16px;
}

.demo-code {
  font-family: monospace;
}

.demo-surface-variable {
  display: inline-block;
  font-family: monospace;
  background: var(--mat-sys-primary-container);
  color: var(--mat-sys-on-primary-container);
  padding: 2px 6px;
  margin: 0 2px;
  border-radius: 4px;
}

mat-expansion-panel {
  margin-top: 24px;
  overflow: visible;
  @include mat.expansion-overrides((
    'container-text-font': var(--mat-sys-body-medium-font),
    'container-text-size': var(--mat-sys-body-medium-size),
    'container-text-weight': var(--mat-sys-body-medium-weight),
    'container-text-line-height': var(--mat-sys-body-medium-line-height),
    'container-text-tracking': var(--mat-sys-body-medium-tracking),
  ));
}

.demo-compact-color-container {
  border-radius: var(--mat-sys-corner-small);
  border: 1px solid var(--mat-sys-outline);
  overflow: hidden; // Hide child heading background color
  margin-top: 24px;

  .demo-heading {
    border: none;
    border-radius: 0;

    &:not(:nth-child(1)) {
      border-top: 1px solid var(--mat-sys-outline);
    }
  }

  .demo-variables {
    text-align: end;
  }
}

.demo-typography-group {
  border: 1px solid var(--mat-sys-outline);
  border-radius: var(--mat-sys-corner-small);
  margin-top: 40px;
  overflow: hidden;
}

.demo-typography-title {
  text-transform: capitalize;
  font: var(--mat-sys-title-medium);
  padding: 16px;
  border-bottom: 1px solid var(--mat-sys-outline);
  background: var(--mat-sys-primary-container);
  color: var(--mat-sys-on-primary-container);
}

.demo-typography-variable {
  min-width: 240px;
}

.demo-typography-example {
  padding: 16px;
  display: flex;
  align-items: baseline;
  border-top: 1px solid var(--mat-sys-outline-variant);

  &:nth-child(1) {
    border: none;
  }
  .demo-surface-variable {
    margin-right: 16px;
  }
}

.demo-typography-text {
  display: inline-block;
}

.demo-elevation {
  height: 40px;
  width: 300px;
  margin: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--mat-sys-surface-container);
  color: var(--mat-sys-on-surface);
  border-radius: var(--mat-sys-corner-extra-small);
}

.demo-code-block {
  background: var(--mat-sys-surface-container-low);
  padding: 16px;
  border-radius: var(--mat-sys-corner-small);
  border: 1px solid var(--mat-sys-outline);
}

.demo-overrides {
  background-color: var(--mat-sys-primary);
  color: var(--mat-sys-on-primary);
  font: var(--mat-sys-body-medium);
  border-radius: var(--mat-sys-corner-large);
  box-shadow: var(--mat-sys-level3);
  padding: 16px;

  @include mat.theme-overrides((
    primary: #ebdcff,
    on-primary: #230f46,
    body-medium: 600 1.5rem / 2.25rem Arial,
    corner-large: 32px,
    level3: 0 4px 6px 1px var(--mat-sys-surface-dim),
  ));
}


  This page uses an experimental prototype version of a
  material.theme API.
  To enable it, click the <mat-icon>public</mat-icon> icon in the header.

  Angular Material components depend on CSS variables defined by the
  material.theme
  Sass mixin. This page provides guidance and documentation for using these variables to
  customize components.

Colors

  Material Design uses color to create accessible, personal color schemes
  that communicate your product's hierarchy, state, and brand. See Material
  Design's Color System
  page to learn more about its use and purpose.

  The following colors are the most often used in Angular Material components. Use these
  colors and follow their uses to add theme colors to your application's custom components.

  Note that variables starting with "--mat-sys-on-*" are designed to be used for text or icons
  placed on top of its paired parent color. For example, --mat-sys-on-primary
  is used for text and icons in elements filled with the --mat-sys-primary color.

 Primary

 --mat-sys-primary

        The most common color used by Angular Material components to
        participate in the application theme.

        Examples include the background color
        of filled buttons, the icon color of selected radio buttons, and the
        outline color of form fields.

        Use the color --mat-sys-on-primary for
        icons, text, and other visual elements placed on a primary background. This
        color is calculated to be optimal for accessibility and legibility.

 Surface

 --mat-sys-surface

        A low-emphasis background color that provides a clear contrast for
        both light and dark themes and their varied theme colors.

        Examples include the background color of the application and most
        components such as the dialog, card, table, and more.

        Use the color --mat-sys-on-surface for
        icons, text, and other visual elements placed on a surface background. This
        color is calculated to be optimal for accessibility and legibility.

 Error

 --mat-sys-error

        High-contrast color meant to alert the user to attract immediate attention.

        Examples include the background color of the badge and the text color of invalid
        form fields inputs.

        Use the color --mat-sys-on-error for
        icons, text, and other visual elements placed on an error background. This
        color is calculated to be optimal for accessibility and legibility.

 Outline

 --mat-sys-outline

        Used for borders and dividers to help provide visual separation between
        and around elements.

        Examples include the color of the divider and border color of an outlined
        form field.

        Use the color --mat-sys-outline-variant for a less
        prominent outline.

<mat-expansion-panel>
  <mat-expansion-panel-header>Other available colors</mat-expansion-panel-header>

    These colors are less commonly used in Angular Material components but
    are available for adding color variety and creating additional emphasis
    to components.

    Colors may be paired with a --mat-sys-on- variable
    that should be used for text and icons placed within a filled container.

Alternative Theme Colors

  <theme-demo-colors [colors]="alternativeThemeColors"></theme-demo-colors>

Surface Colors

    The following colors should be used for backgrounds and large,
    low-emphasis areas of the screen.

    Containers filled with a surface color should apply the
    --mat-sys-on-surface color to text
    and icons placed within.

  <theme-demo-colors [colors]="surfaceColors"></theme-demo-colors>

Fixed Colors

    These colors are the same for both light and dark themes. They are unused
    by any Angular Material components.

  <theme-demo-colors [colors]="fixedColors"></theme-demo-colors>

</mat-expansion-panel>

Typography

  There are five categories of font types defined by Material Design: body, display, headline,
  label, and title. Each category has three sizes: small, medium, and large.

  Learn more about how these categories and their sizes should be used in your application by
  visiting Material Design's
  Typography documentation.

@for (category of ['body', 'display', 'headline', 'label', 'title']; track $index) {

{{category}}

    @for (size of ['small', 'medium', 'large']; track $index) {

--mat-sys-{{category}}-{{size}}

Lorem ipsum dolor

    }

}

  Each variable can be applied to the `font` CSS style. Additionally, the parts of the variable definition
  can be accessed individually by appending the keywords "font", "line-height", "size", "tracking", and "weight".

  For example, the values for medium body text may be defined as follows:

--mat-sys-body-medium: 400 0.875rem / 1.25rem Roboto, sans-serif;
--mat-sys-body-medium-font: Roboto, sans-serif;
--mat-sys-body-medium-line-height: 1.25rem;
--mat-sys-body-medium-size: 0.875rem;
--mat-sys-body-medium-tracking: 0.016rem;
--mat-sys-body-medium-weight: 400;

Elevation


/**
 * @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 {
  afterNextRender,
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  inject,
  Injector,
  ViewChild,
} from '@angular/core';
import {FormsModule} from '@angular/forms';

import {MatButtonModule} from '@angular/material/button';
import {MatDividerModule} from '@angular/material/divider';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
import {MatSelectModule} from '@angular/material/select';
import {MatTableDataSource, MatTableModule} from '@angular/material/table';

@Component({
  selector: 'performance-demo',
  templateUrl: 'performance-demo.html',
  styleUrl: 'performance-demo.css',
  imports: [
    FormsModule,
    MatButtonModule,
    MatDividerModule,
    MatFormFieldModule,
    MatIconModule,
    MatInputModule,
    MatPaginatorModule,
    MatSelectModule,
    MatTableModule,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PerformanceDemo implements AfterViewInit {
  /** Controls the rendering of components. */
  show = false;

  /** The number of times metrics will be gathered. */
  sampleSize = 100;

  /** The number of components being rendered. */
  componentCount = 100;

  /** A flat array of every sample recorded. */
  allSamples: number[] = [];

  /** Used to disable benchmark controls while a benchmark is being run. */
  isRunningBenchmark = false;

  /** The columns in the metrics table. */
  displayedColumns: string[] = ['index', 'time'];

  /** Basically the same thing as allSamples but organized as a mat-table data source. */
  dataSource = new MatTableDataSource<{index: number; time: string}>();

  /** The average plus/minus the stdev. */
  computedResults = '';

  /** Used in an `@for` to render the desired number of comonents. */
  componentArray = [].constructor(this.componentCount);

  private _injector = inject(Injector);

  readonly cdr = inject(ChangeDetectorRef);

  /** The standard deviation of the recorded samples. */
  get stdev(): number | undefined {
    if (!this.allSamples.length) {
      return undefined;
    }
    return Math.sqrt(
      this.allSamples.map(x => Math.pow(x - this.mean!, 2)).reduce((a, b) => a + b) /
        this.allSamples.length,
    );
  }

  /** The average value of the recorded samples. */
  get mean(): number | undefined {
    if (!this.allSamples.length) {
      return undefined;
    }
    return this.allSamples.reduce((a, b) => a + b) / this.allSamples.length;
  }

  @ViewChild(MatPaginator) paginator?: MatPaginator;

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

  getTotalRenderTime(): string {
    return this.allSamples.length ? `${this.format(this.mean!)} ± ${this.format(this.stdev!)}` : '';
  }

  format(num: number): string {
    const roundedNum = Math.round(num * 100) / 100;
    return roundedNum >= 10 ? roundedNum.toFixed(2) : '0' + roundedNum.toFixed(2);
  }

  async runBenchmark(): Promise<void> {
    this.isRunningBenchmark = true;
    const samples = [];
    for (let i = 0; i < this.sampleSize; i++) {
      samples.push(await this.recordSample());
    }
    this.dataSource.data = this.dataSource.data.concat(
      samples.map((sample, i) => ({
        index: this.allSamples.length + i,
        time: this.format(sample),
      })),
    );
    this.allSamples.push(...samples);
    this.isRunningBenchmark = false;
    this.computedResults = this.getTotalRenderTime();
    this.cdr.markForCheck();
  }

  clearMetrics() {
    this.allSamples = [];
    this.dataSource.data = [];
    this.computedResults = this.getTotalRenderTime();
  }

  recordSample(): Promise<number> {
    return new Promise(res => {
      setTimeout(() => {
        this.show = true;
        this.cdr.markForCheck();
        const start = performance.now();
        afterNextRender(
          () => {
            const end = performance.now();
            this.show = false;
            this.cdr.markForCheck();
            res(end - start);
          },
          {injector: this._injector},
        );
      });
    });
  }
}


/**
 * @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 {Directionality} from '@angular/cdk/bidi';
import {
  ChangeDetectionStrategy,
  Component,
  TemplateRef,
  ViewChild,
  ViewEncapsulation,
  inject,
} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {
  MatSnackBar,
  MatSnackBarConfig,
  MatSnackBarHorizontalPosition,
  MatSnackBarVerticalPosition,
} from '@angular/material/snack-bar';

@Component({
  selector: 'snack-bar-demo',
  templateUrl: 'snack-bar-demo.html',
  styleUrl: 'snack-bar-demo.css',
  encapsulation: ViewEncapsulation.None,
  imports: [FormsModule, MatButtonModule, MatCheckboxModule, MatInputModule, MatSelectModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SnackBarDemo {
  snackBar = inject(MatSnackBar);
  private _dir = inject(Directionality);

  @ViewChild('template') template: TemplateRef<any>;
  message = 'Snack Bar opened.';
  actionButtonLabel = 'Retry';
  action = false;
  setAutoHide = true;
  autoHide = 10000;
  addExtraClass = false;
  horizontalPosition: MatSnackBarHorizontalPosition = 'center';
  verticalPosition: MatSnackBarVerticalPosition = 'bottom';

  open() {
    const config = this._createConfig();
    this.snackBar.open(this.message, this.action ? this.actionButtonLabel : undefined, config);
  }

  openTemplate() {
    const config = this._createConfig();
    this.snackBar.openFromTemplate(this.template, config);
  }

  private _createConfig() {
    const config = new MatSnackBarConfig();
    config.verticalPosition = this.verticalPosition;
    config.horizontalPosition = this.horizontalPosition;
    config.duration = this.setAutoHide ? this.autoHide : 0;
    config.panelClass = this.addExtraClass ? ['demo-party'] : undefined;
    config.direction = this._dir.value;
    return config;
  }
}


import {bootstrapApplication, provideClientHydration} from '@angular/platform-browser';
import {provideAnimations} from '@angular/platform-browser/animations';
import {AUTOMATED_KITCHEN_SINK, KitchenSink} from './kitchen-sink/kitchen-sink';

bootstrapApplication(KitchenSink, {
  providers: [
    provideAnimations(),
    provideClientHydration(),
    {
      provide: AUTOMATED_KITCHEN_SINK,
      useValue: false,
    },
  ],
});


<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Angular Material Universal Kitchen Sink Test</title>
  <link href="styles.css" rel="stylesheet">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
  <base href="/">
</head>
<body class="mat-app-background mat-typography">
  <kitchen-sink>Loading...</kitchen-sink>

  <script src="zone.js/bundles/zone.umd.min.js"></script>
  <script src="https://maps.googleapis.com/maps/api/js?libraries=visualization"></script>
  <script src="https://unpkg.com/@googlemaps/markerclustererplus/dist/index.min.js"></script>
  <script src="client_bundle/main.js" type="module"></script>
</body>
</html>


@Component({
  selector: 'mat-option',
  exportAs: 'matOption',
  host: {
    'role': 'option',
    '[class.mdc-list-item--selected]': 'selected',
    '[class.mat-mdc-option-multiple]': 'multiple',
    '[class.mat-mdc-option-active]': 'active',
    '[class.mdc-list-item--disabled]': 'disabled',
    '[id]': 'id',
    // Set aria-selected to false for non-selected items and true for selected items. Conform to
    // [WAI ARIA Listbox authoring practices guide](
    //  https://www.w3.org/WAI/ARIA/apg/patterns/listbox/), "If any options are selected, each
    // selected option has either aria-selected or aria-checked  set to true. All options that are
    // selectable but not selected have either aria-selected or aria-checked set to false." Align
    // aria-selected implementation of Chips and List components.
    //
    // Set `aria-selected="false"` on not-selected listbox options to fix VoiceOver announcing
    // every option as "selected" (#21491).
    '[attr.aria-selected]': 'selected',
    '[attr.aria-disabled]': 'disabled.toString()',
    '(click)': '_selectViaInteraction()',
    '(keydown)': '_handleKeydown($event)',
    'class': 'mat-mdc-option mdc-list-item',
  },
  styleUrl: 'option.css',
  templateUrl: 'option.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [MatPseudoCheckbox, MatRipple],
})
export class MatOption<T = any> implements FocusableOption, AfterViewChecked, OnDestroy {
  private _element = inject<ElementRef<HTMLElement>>(ElementRef);
  _changeDetectorRef = inject(ChangeDetectorRef);
  private _parent = inject<MatOptionParentComponent>(MAT_OPTION_PARENT_COMPONENT, {optional: true});
  group = inject<MatOptgroup>(MAT_OPTGROUP, {optional: true});

  private _signalDisableRipple = false;
  private _selected = false;
  private _active = false;
  private _disabled = false;
  private _mostRecentViewValue = '';

  /** Whether the wrapping component is in multiple selection mode. */
  get multiple() {
    return this._parent && this._parent.multiple;
  }

  /** Whether or not the option is currently selected. */
  get selected(): boolean {
    return this._selected;
  }

  /** The form value of the option. */
  @Input() value: T;

  /** The unique ID of the option. */
  @Input() id: string = `mat-option-${_uniqueIdCounter++}`;

  /** Whether the option is disabled. */
  @Input({transform: booleanAttribute})
  get disabled(): boolean {
    return (this.group && this.group.disabled) || this._disabled;
  }
  set disabled(value: boolean) {
    this._disabled = value;
  }

  /** Whether ripples for the option are disabled. */
  get disableRipple(): boolean {
    return this._signalDisableRipple
      ? (this._parent!.disableRipple as Signal<boolean>)()
      : !!this._parent?.disableRipple;
  }

  /** Whether to display checkmark for single-selection. */
  get hideSingleSelectionIndicator(): boolean {
    return !!(this._parent && this._parent.hideSingleSelectionIndicator);
  }

  /** Event emitted when the option is selected or deselected. */
  // tslint:disable-next-line:no-output-on-prefix
  @Output() readonly onSelectionChange = new EventEmitter<MatOptionSelectionChange<T>>();

  /** Element containing the option's text. */
  @ViewChild('text', {static: true}) _text: ElementRef<HTMLElement> | undefined;

  /** Emits when the state of the option changes and any parents have to be notified. */
  readonly _stateChanges = new Subject<void>();

  constructor(...args: unknown[]);
  constructor() {
    inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);
    inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader);
    this._signalDisableRipple = !!this._parent && isSignal(this._parent.disableRipple);
  }

  /**
   * Whether or not the option is currently active and ready to be selected.
   * An active option displays styles as if it is focused, but the
   * focus is actually retained somewhere else. This comes in handy
   * for components like autocomplete where focus must remain on the input.
   */
  get active(): boolean {
    return this._active;
  }

  /**
   * The displayed value of the option. It is necessary to show the selected option in the
   * select's trigger.
   */
  get viewValue(): string {
    // TODO(kara): Add input property alternative for node envs.
    return (this._text?.nativeElement.textContent || '').trim();
  }

  /** Selects the option. */
  select(emitEvent = true): void {
    if (!this._selected) {
      this._selected = true;
      this._changeDetectorRef.markForCheck();

      if (emitEvent) {
        this._emitSelectionChangeEvent();
      }
    }
  }

  /** Deselects the option. */
  deselect(emitEvent = true): void {
    if (this._selected) {
      this._selected = false;
      this._changeDetectorRef.markForCheck();

      if (emitEvent) {
        this._emitSelectionChangeEvent();
      }
    }
  }

  /** Sets focus onto this option. */
  focus(_origin?: FocusOrigin, options?: FocusOptions): void {
    // Note that we aren't using `_origin`, but we need to keep it because some internal consumers
    // use `MatOption` in a `FocusKeyManager` and we need it to match `FocusableOption`.
    const element = this._getHostElement();

    if (typeof element.focus === 'function') {
      element.focus(options);
    }
  }

  /**
   * This method sets display styles on the option to make it appear
   * active. This is used by the ActiveDescendantKeyManager so key
   * events will display the proper options as active on arrow key events.
   */
  setActiveStyles(): void {
    if (!this._active) {
      this._active = true;
      this._changeDetectorRef.markForCheck();
    }
  }

  /**
   * This method removes display styles on the option that made it appear
   * active. This is used by the ActiveDescendantKeyManager so key
   * events will display the proper options as active on arrow key events.
   */
  setInactiveStyles(): void {
    if (this._active) {
      this._active = false;
      this._changeDetectorRef.markForCheck();
    }
  }

  /** Gets the label to be used when determining whether the option should be focused. */
  getLabel(): string {
    return this.viewValue;
  }

  /** Ensures the option is selected when activated from the keyboard. */
  _handleKeydown(event: KeyboardEvent): void {
    if ((event.keyCode === ENTER || event.keyCode === SPACE) && !hasModifierKey(event)) {
      this._selectViaInteraction();

      // Prevent the page from scrolling down and form submits.
      event.preventDefault();
    }
  }

  /**
   * `Selects the option while indicating the selection came from the user. Used to
   * determine if the select's view -> model callback should be invoked.`
   */
  _selectViaInteraction(): void {
    if (!this.disabled) {
      this._selected = this.multiple ? !this._selected : true;
      this._changeDetectorRef.markForCheck();
      this._emitSelectionChangeEvent(true);
    }
  }

  /** Returns the correct tabindex for the option depending on disabled state. */
  // This method is only used by `MatLegacyOption`. Keeping it here to avoid breaking the types.
  // That's because `MatLegacyOption` use `MatOption` type in a few places such as
  // `MatOptionSelectionChange`. It is safe to delete this when `MatLegacyOption` is deleted.
  _getTabIndex(): string {
    return this.disabled ? '-1' : '0';
  }

  /** Gets the host DOM element. */
  _getHostElement(): HTMLElement {
    return this._element.nativeElement;
  }

  ngAfterViewChecked() {
    // Since parent components could be using the option's label to display the selected values
    // (e.g. `mat-select`) and they don't have a way of knowing if the option's label has changed
    // we have to check for changes in the DOM ourselves and dispatch an event. These checks are
    // relatively cheap, however we still limit them only to selected options in order to avoid
    // hitting the DOM too often.
    if (this._selected) {
      const viewValue = this.viewValue;

      if (viewValue !== this._mostRecentViewValue) {
        if (this._mostRecentViewValue) {
          this._stateChanges.next();
        }

        this._mostRecentViewValue = viewValue;
      }
    }
  }

  ngOnDestroy() {
    this._stateChanges.complete();
  }

  /** Emits the selection change event. */
  private _emitSelectionChangeEvent(isUserInput = false): void {
    this.onSelectionChange.emit(new MatOptionSelectionChange<T>(this, isUserInput));
  }
}


(
    0: #000000,
    10: #1a1c18,
    20: #2f312c,
    25: #3a3c37,
    30: #464742,
    35: #52534e,
    40: #5e5f5a,
    50: #767872,
    60: #90918b,
    70: #abaca5,
    80: #c7c7c0,
    90: #e3e3dc,
    95: #f1f1ea,
    98: #fafaf2,
    99: #fdfdf5,
    100: #ffffff,
    4: #0c0f0b,
    6: #121410,
    12: #1e201c,
    17: #282b26,
    22: #333531,
    24: #383a35,
    87: #dadbd3,
    92: #e8e9e1,
    94: #eeeee7,
    96: #f3f4ed,
  ),
  neutral-variant: (
    0: #000000,
    10: #181d14,
    20: #2d3228,
    25: #383d33,
    30: #44483e,
    35: #4f5449,
    40: #5b6055,
    50: #74796d,
    60: #8e9286,
    70: #a8ada0,
    80: #c4c8bb,
    90: #e0e4d6,
    95: #eef2e4,
    98: #f7fbec,
    99: #fafeef,
    100: #ffffff,
  ),
));

/// Spring Green color palette to be used as primary or tertiary palette.
$spring-green-palette: _patch-error-palette((
  0: #000000,
  10: #00210b,
  20: #003917,
  25: #00461e,
  30: #005225,
  35: #00602c,
  40: #006d33,
  50: #008942,
  60: #00a751,
  70: #00c561,
  80: #00e472,
  90: #63ff94,
  95: #c4ffcb,
  98: #eaffe9,
  99: #f5fff2,
  100: #ffffff,
  secondary: (
    0: #000000,
    10: #0e1f12,
    20: #233425,
    25: #2e4030,
    30: #394b3b,
    35: #445746,
    40: #506352,
    50: #697c6a,
    60: #829682,
    70: #9cb19c,
    80: #b7ccb7,
    90: #d3e8d2,
    95: #e1f6e0,
    98: #eaffe9,
    99: #f5fff2,
    100: #ffffff,
  ),
  neutral: (
    0: #000000,
    10: #191c19,
    20: #2e312e,
    25: #393c39,
    30: #454744,
    35: #51534f,
    40: #5d5f5b,
    50: #757873,
    60: #8f918d,
    70: #aaaca7,
    80: #c5c7c2,
    90: #e2e3de,
    95: #f0f1ec,
    98: #f9faf4,
    99: #fcfdf7,
    100: #ffffff,
    4: #0c0f0c,
    6: #111411,
    12: #1d201d,
    17: #282b27,
    22: #323632,
    24: #373a36,
    87: #d9dbd5,
    92: #e7e9e3,
    94: #edefe8,
    96: #f2f4ee,
  ),
  neutral-variant: (
    0: #000000,
    10: #161d17,
    20: #2b322b,
    25: #363d36,
    30: #414941,
    35: #4d544c,
    40: #596058,
    50: #717970,
    60: #8b9389,
    70: #a6ada4,
    80: #c1c9be,
    90: #dde5da,
    95: #ebf3e8,
    98: #f4fcf0,
    99: #f7fef3,
    100: #ffffff,
  ),
));

/// Azure color palette to be used as primary or tertiary palette.
$azure-palette: _patch-error-palette((
  0: #000000,
  10: #001b3f,
  20: #002f65,
  25: #003a7a,
  30: #00458f,
  35: #0050a5,
  40: #005cbb,
  50: #0074e9,
  60: #438fff,
  70: #7cabff,
  80: #abc7ff,
  90: #d7e3ff,
  95: #ecf0ff,
  98: #f9f9ff,
  99: #fdfbff,
  100: #ffffff,
  secondary: (
    0: #000000,
    10: #131c2b,
    20: #283041,
    25: #333c4d,
    30: #3e4759,
    35: #4a5365,
    40: #565e71,
    50: #6f778b,
    60: #8891a5,
    70: #a3abc0,
    80: #bec6dc,
    90: #dae2f9,
    95: #ecf0ff,
    98: #f9f9ff,
    99: #fdfbff,
    100: #ffffff,
  ),
  neutral: (
    0: #000000,
    10: #1a1b1f,
    20: #2f3033,
    25: #3b3b3f,
    30: #46464a,
    35: #525256,
    40: #5e5e62,
    50: #77777a,
    60: #919094,
    70: #ababaf,
    80: #c7c6ca,
    90: #e3e2e6,
    95: #f2f0f4,
    98: #faf9fd,
    99: #fdfbff,
    100: #ffffff,
    4: #0d0e11,
    6: #121316,
    12: #1f2022,
    17: #292a2c,
    22: #343537,
    24: #38393c,
    87: #dbd9dd,
    92: #e9e7eb,
    94: #efedf0,
    96: #f4f3f6,
  ),
  neutral-variant: (
    0: #000000,
    10: #181c22,
    20: #2d3038,
    25: #383b43,
    30: #44474e,
    35: #4f525a,
    40: #5b5e66,
    50: #74777f,
    60: #8e9099,
    70: #a9abb4,
    80: #c4c6d0,
    90: #e0e2ec,
    95: #eff0fa,
    98: #f9f9ff,
    99: #fdfbff,
    100: #ffffff,
  ),
));

/// Violet color palette to be used as primary or tertiary palette.
$violet-palette: _patch-error-palette((
  0: #000000,
  10: #270057,
  20: #42008a,
  25: #5000a4,
  30:


#5f00c0,
  35: #6e00dc,
  40: #7d00fa,
  50: #944aff,
  60: #a974ff,
  70: #bf98ff,
  80: #d5baff,
  90: #ecdcff,
  95: #f7edff,
  98: #fef7ff,
  99: #fffbff,
  100: #ffffff,
  secondary: (
    0: #000000,
    10: #1f182a,
    20: #352d40,
    25: #40384c,
    30: #4b4357,
    35: #574f63,
    40: #645b70,
    50: #7d7389,
    60: #978ca4,
    70: #b2a7bf,
    80: #cec2db,
    90: #eadef7,
    95: #f7edff,
    98: #fef7ff,
    99: #fffbff,
    100: #ffffff,
  ),
  neutral: (
    0: #000000,
    10: #1d1b1e,
    20: #323033,
    25: #3d3a3e,
    30: #49464a,
    35: #545155,
    40: #605d61,
    50: #7a767a,
    60: #948f94,
    70: #aeaaae,
    80: #cac5ca,
    90: #e6e1e6,
    95: #f5eff4,
    98: #fef8fc,
    99: #fffbff,
    100: #ffffff,
    4: #0f0d11,
    6: #151316,
    12: #211f22,
    17: #2b292d,
    22: #363437,
    24: #3b383c,
    87: #ded8dd,
    92: #ede6eb,
    94: #f2ecf1,
    96: #f8f2f6,
  ),
  neutral-variant: (
    0: #000000,
    10: #1d1a22,
    20: #332f37,
    25: #3e3a42,
    30: #49454e,
    35: #55515a,
    40: #615c66,
    50: #7b757f,
    60: #958e99,
    70: #b0a9b3,
    80: #cbc4cf,
    90: #e8e0eb,
    95: #f6eef9,
    98: #fef7ff,
    99: #fffbff,
    100: #ffffff,
  ),
));

/// Rose color palette to be used as primary or tertiary palette.
$rose-palette: _patch-error-palette((
  0: #000000,
  10: #3f001b,
  20: #65002f,
  25: #7a003a,
  30: #8f0045,
  35: #a40050,
  40: #ba005c,
  50: #e80074,
  60: #ff4a8e,
  70: #ff84a9,
  80: #ffb1c5,
  90: #ffd9e1,
  95: #ffecef,
  98: #fff8f8,
  99: #fffbff,
  100: #ffffff,
  secondary: (
    0: #000000,
    10: #2b151b,
    20: #422930,
    25: #4f343b,
    30: #5b3f46,
    35: #684b52,
    40: #74565d,
    50: #8f6f76,
    60: #aa888f,
    70: #c6a2aa,
    80: #e3bdc5,
    90: #ffd9e1,
    95: #ffecef,
    98: #fff8f8,
    99: #fffbff,
    100: #ffffff,
  ),
  neutral: (
    0: #000000,
    10: #201a1b,
    20: #352f30,
    25: #413a3b,
    30: #4c4546,
    35: #585052,
    40: #655c5e,
    50: #7e7576,
    60: #988e90,
    70: #b3a9aa,
    80: #cfc4c5,
    90: #ece0e1,
    95: #faeeef,
    98: #fff8f8,
    99: #fffbff,
    100: #ffffff,
    4: #120d0e,
    6: #171213,
    12: #241e1f,
    17: #2f2829,
    22: #3a3334,
    24: #3e3738,
    87: #e3d7d8,
    92: #f1e5e6,
    94: #f7ebec,
    96: #fdf1f2,
  ),
  neutral-variant: (
    0: #000000,
    10: #24191b,
    20: #3a2d30,
    25: #45383b,
    30: #514346,
    35: #5d4f52,
    40: #6a5a5e,
    50: #847376,
    60: #9e8c90,
    70: #baa7aa,
    80: #d6c2c5,
    90: #f3dde1,
    95: #ffecef,
    98: #fff8f8,
    99: #fffbff,
    100: #ffffff,
  ),
));


// This file contains functions used to define Angular Material theme objects.

@use 'sass:map';
@use '../style/sass-utils';
@use './palettes';
@use '../tokens/m3-tokens';
@use './config-validation';

// Prefix used for component token fallback variables, e.g.
// `color: var(--mdc-text-button-label-text-color, var(--mat-sys-primary));`
$system-fallback-prefix: mat-sys;

// Default system level prefix to use when directly calling the `system-level-*` mixins.
// Prefix used for component token fallback variables, e.g.
// `color: var(--mdc-text-button-label-text-color, var(--mat-sys-primary));`
// TODO: Remove this variable after internal clients are migrated from "sys"
$system-level-prefix: mat-sys;

/// Map key used to store internals of theme config.
$internals: _mat-theming-internals-do-not-access;
/// The theme version of generated themes.
$theme-version: 1;

/// Defines an Angular Material theme object with color, typography, and density settings.
/// @param {Map} $config The theme configuration
/// @return {Map} A theme object
@function define-theme($config: ()) {
  $err: config-validation.validate-theme-config($config);
  @if $err {
    @error $err;
  }

  @return sass-utils.deep-merge-all(
      define-colors(map.get($config, color) or ()),
      define-typography(map.get($config, typography) or ()),
      define-density(map.get($config, density) or ()),
      ($internals: (base-tokens: m3-tokens.generate-base-tokens())),
  );
}

/// Defines an Angular Material theme object with color settings.
/// @param {Map} $config The color configuration
/// @return {Map} A theme object
@function define-colors($config: ()) {
  $err: config-validation.validate-color-config($config);
  @if $err {
    @error $err;
  }

  $type: map.get($config, theme-type) or light;
  $primary: map.get($config, primary) or palettes.$violet-palette;
  $tertiary: map.get($config, tertiary) or $primary;
  $system-variables-prefix: map.get($config, system-variables-prefix) or $system-level-prefix;
  sass-utils.$use-system-color-variables: map.get($config, use-system-variables) or false;

  @return (
    $internals: (
      theme-version: $theme-version,
      theme-type: $type,
      palettes: (
        primary: map.remove($primary, neutral, neutral-variant, secondary),
        secondary: map.get($primary, secondary),
        tertiary: map.remove($tertiary, neutral, neutral-variant, secondary),
        neutral: map.get($primary, neutral),
        neutral-variant: map.get($primary, neutral-variant),
        error: map.get($primary, error),
      ),
      color-system-variables-prefix: $system-variables-prefix,
      color-tokens: m3-tokens.generate-color-tokens(
          $type, $primary, $tertiary, map.get($primary, error), $system-variables-prefix)
    )
  );
}

/// Defines an Angular Material theme object with typography settings.
/// @param {Map} $config The typography configuration
/// @return {Map} A theme object
@function define-typography($config: ()) {
  $err: config-validation.validate-typography-config($config);
  @if $err {
    @error $err;
  }

  $plain: map.get($config, plain-family) or (Roboto, sans-serif);
  $brand: map.get($config, brand-family) or $plain;
  $bold: map.get($config, bold-weight) or 700;
  $medium: map.get($config, medium-weight) or 500;
  $regular: map.get($config, regular-weight) or 400;
  $system-variables-prefix: map.get($config, system-variables-prefix) or $system-level-prefix;
  sass-utils.$use-system-typography-variables: map.get($config, use-system-variables) or false;

  @return (
    $internals: (
      theme-version: $theme-version,
      font-definition: (
        plain: $plain,
        brand: $brand,
        bold: $bold,
        medium: $medium,
        regular: $regular,
      ),
      typography-system-variables-prefix: $system-variables-prefix,
      typography-tokens: m3-tokens.generate-typography-tokens(
          $brand, $plain, $bold, $medium, $regular, $system-variables-prefix)
    )
  );
}

/// Defines an Angular Material theme object with density settings.
/// @param {Map} $config The density configuration
/// @return {Map} A theme object
@function define-density($config: ()) {
  $err: config-validation.validate-density-config($config);
  @if $err {
    @error $err;
  }

  $density-scale: map.get($config, scale) or 0;

  @return (
    $internals: (
      theme-version: $theme-version,
      density-scale: $density-scale,
      density-tokens: m3-tokens.generate-density-tokens($density-scale)
    )
  );
}


@use 'sass:list';
@use 'sass:map';
@use '../style/validation';
@use './m2-inspection';

$_internals: _mat-theming-internals-do-not-access;

$_m3-typescales: (
  display-large,
  display-medium,
  display-small,
  headline-large,
  headline-medium,
  headline-small,
  title-large,
  title-medium,
  title-small,
  label-large,
  label-medium,
  label-small,
  body-large,
  body-medium,
  body-small,
);

$_typography-properties: (font, font-family, line-height, font-size, letter-spacing, font-weight);

/// Validates that the given value is a versioned theme object.
/// @param {Any} $theme The theme object to validate.
/// @return {Boolean|Null} true if the theme has errors, else null.
@function _validate-theme-object($theme) {
  $err: validation.validate-type($theme, 'map') or
        map.get($theme, $_internals, theme-version) == null;
  @return if($err, true, null);
}

/// Gets the version number of a theme object. A theme that is not a valid versioned theme object is
/// considered to be version 0.
/// @param {Map} $theme The theme to check the version of
/// @return {Number} The version number of the theme (0 if unknown).
@function get-theme-version($theme) {
  $err: _validate-theme-object($theme);
  @return if($err, 0, map.get($theme, $_internals, theme-version) or 0);
}

/// Gets the type of theme represented by a theme object (light or dark).
/// @param {Map} $theme The theme
/// @return {String} The type of theme (either `light` or `dark`).
@function get-theme-type($theme) {
  $version: get-theme-version($theme);
  @if $version == 0 {
    @return m2-inspection.get-theme-type($theme);
  }
  @else if $version == 1 {
    @if not theme-has($theme, color) {
      @error 'Color information is not available on this theme.';
    }
    @return map.get($theme, $_internals, theme-type) or light;
  }
  @else {
    @error #{'Unrecognized theme version:'} $version;
  }
}

/// Gets a color from a theme object. This function can take 2 or 3 arguments. If 2 arguments are
/// passed, the second argument is treated as the name of a color role. If 3 arguments are passed,
/// the second argument is treated as the name of a color palette (primary, secondary, etc.) and the
/// third is treated as the palette hue (10, 50, etc.)
/// @param {Map} $theme The theme
/// @param {String} $color-role-or-palette-name The name of the color role to get, or the name of a
///   color palette.
/// @param {Number} $hue The palette hue to get (passing this argument means the second argument is
///   interpreted as a palette name).
/// @return {Color} The requested theme color.
@function get-theme-color($theme, $args...) {
  $version: get-theme-version($theme);
  $args-count: list.length($args);
  @if $args-count != 1 and $args-count != 2 and $args-count != 3 {
    @error #{'Expected between 2 and 4 arguments. Got:'} $args-count + 1;
  }


@if $version == 0 {
    @return m2-inspection.get-theme-color($theme, $args...);
  }
  @else if $version == 1 {
    @if $args-count == 1 {
      @return _get-theme-role-color($theme, $args...);
    }
    @else if $args-count == 2 {
      @return _get-theme-palette-color($theme, $args...);
    }
  }
  @else {
    @error #{'Unrecognized theme version:'} $version;
  }
}

/// Gets a role color from a theme object.
/// @param {Map} $theme The theme
/// @param {String} $color-role-name The name of the color role to get.
/// @return {Color} The requested role color.
@function _get-theme-role-color($theme, $color-role-name) {
  $err: _validate-theme-object($theme);
  @if $err {
    // TODO(mmalerba): implement for old style theme objects.
    @error #{'get-theme-color does not support legacy theme objects.'};
  }
  @if not theme-has($theme, color) {
    @error 'Color information is not available on this theme.';
  }
  $color-roles: map.get($theme, $_internals, color-tokens, (mdc, theme));
  $result: map.get($color-roles, $color-role-name);
  @if not $result {
    @error #{'Valid color roles are: #{map.keys($color-roles)}. Got:'} $color-role-name;
  }
  @return $result;
}

/// Gets a palette color from a theme object.
/// @param {Map} $theme The theme
/// @param {String} $palette-name The name of the palette to get the color from.
/// @param {Number} $hue The hue to read from the palette.
/// @return {Color} The requested palette color.
@function _get-theme-palette-color($theme, $palette-name, $hue) {
  $err: _validate-theme-object($theme);
  @if $err {
    // TODO(mmalerba): implement for old style theme objects.
    @error #{'get-theme-color does not support legacy theme objects.'};
  }
  @if not theme-has($theme, color) {
    @error 'Color information is not available on this theme.';
  }
  $palettes: map.get($theme, $_internals, palettes);
  $palette: map.get($palettes, $palette-name);
  @if not $palette {
    $supported-palettes: map.keys($palettes);
    @error #{'Valid palettes are: #{$supported-palettes}. Got:'} $palette-name;
  }
  $result: map.get($palette, $hue);
  @if not $result {
    $supported-hues: map.keys($palette);
    @error #{'Valid hues for'} $palette-name #{'are: #{$supported-hues}. Got:'} $hue;
  }
  @return $result;
}

/// Gets a typography value from a theme object.
/// @param {Map} $theme The theme
/// @param {String} $typescale The typescale name.
/// @param {String} $property The CSS font property to get
///   (font, font-family, font-size, font-weight, line-height, or letter-spacing).
/// @return {*} The value of the requested font property.
@function get-theme-typography($theme, $typescale, $property: font) {
  $version: get-theme-version($theme);
  @if $version == 0 {
    @return m2-inspection.get-theme-typography($theme, $typescale, $property);
  }
  @else if $version == 1 {
    @if not theme-has($theme, typography) {
      @error 'Typography information is not available on this theme.';
    }
    @if not list.index($_m3-typescales, $typescale) {
      @error #{'Valid typescales are: #{$_m3-typescales}. Got:'} $typescale;
    }
    @if not list.index($_typography-properties, $property) {
      @error #{'Valid typography properties are: #{$_typography-properties}. Got:'} $property;
    }
    $property-key: map.get((
      font: '',
      font-family: '-font',
      line-height: '-line-height',
      font-size: '-size',
      letter-spacing: '-tracking',
      font-weight: '-weight'
    ), $property);
    $token-name: '#{$typescale}#{$property-key}';
    @return map.get($theme, $_internals, typography-tokens, (mdc, typography), $token-name);
  }
  @else {
    @error #{'Unrecognized theme version:'} $version;
  }
}

/// Gets the density scale from a theme object.
/// @param {Map} $theme The theme
/// @return {Number} The density scale.
@function get-theme-density($theme) {
  $version: get-theme-version($theme);
  @if $version == 0 {
    @return m2-inspection.get-theme-density($theme);
  }
  @else if $version == 1 {
    @if not theme-has($theme, density) {
      @error 'Density information is not available on this theme.';
    }
    @return map.get($theme, $_internals, density-scale);
  }
  @else {
    @error #{'Unrecognized theme version:'} $version;
  }
}

/// Checks whether the theme has information about given theming system.
/// @param {Map} $theme The theme
/// @param {String} $system The system to check
/// @param {Boolean} Whether the theme has information about the system.
@function theme-has($theme, $system) {
  $version: get-theme-version($theme);
  @if $version == 0 {
    @return m2-inspection.theme-has($theme, $system);
  }
  @else if $version == 1 {
    @if $system == base {
      @return map.get($theme, $_internals, base-tokens) != null;
    }
    @if $system == color {
      @return map.get($theme, $_internals, color-tokens) != null and
        map.get($theme, $_internals, theme-type) != null and
        map.get($theme, $_internals, palettes) != null;
    }
    @if $system == typography {
      @return map.get($theme, $_internals, typography-tokens) != null;
    }
    @if $system == density {
      @return map.get($theme, $_internals, density-scale) != null;
    }
    @error 'Valid systems are: base, color, typography, density. Got:' $system;
  }
  @else {
    @error #{'Unrecognized theme version:'} $version;
  }
}

/// Removes the information about the given theming system(s) from the given theme.
/// @param {Map} $theme The theme to remove information from
/// @param {String...} $systems The systems to remove
/// @return {Map} A version of the theme without the removed theming systems.
@function theme-remove($theme, $systems...) {
  $err: validation.validate-allowed-values($systems, color, typography, density, base);
  @if $err {
    @error #{'Expected $systems to contain valid system names (color, typography, density, or'}
    #{'base). Got invalid system names:'} $err;
  }
  $version: get-theme-version($theme);
  @if $version == 0 {
    @return m2-inspection.theme-remove($theme, $systems...);
  }
  @else if $version == 1 {
    @each $system in $systems {
      @if $system == base {
        $theme: map.deep-remove($theme, $_internals, base-tokens);
      }
      @else if $system == color {
        $theme: map.deep-remove($theme, $_internals, color-tokens);
        $theme: map.deep-remove($theme, $_internals, theme-type);
        $theme: map.deep-remove($theme, $_internals, palettes);
      }
      @else if $system == typography {
        $theme: map.deep-remove($theme, $_internals, typography-tokens);
      }
      @else if $system == density {
        $theme: map.deep-remove($theme, $_internals, density-scale);
        $theme: map.deep-remove($theme, $_internals, density-tokens);
      }
    }
    @return $theme;
  }
}

/// Gets the set of tokens from the given theme, limited to those affected by the requested theming
/// systems.
/// @param {Map} $theme The theme to get tokens from.
/// @param {String...} $systems The theming systems to get tokens for. Valid values are: color,
///   typography, density, base. If no systems are passed, all tokens will be returned.
/// @return {Map} The requested tokens for the theme.


describe('theming definition api', () => {
  describe('define-theme', () => {
    it('should fill in defaults', () => {
      const css = transpile(`
        $theme: mat.define-theme();
        $data: map.get($theme, $internals);
        :root {
          --keys: #{map.keys($data)};
          --version: #{map.get($data, theme-version)};
          --type: #{map.get($data, theme-type)};
          --palettes: #{map.keys(map.get($data, palettes))};
          --density: #{map.get($data, density-scale)};
          --base-tokens: #{list.length(map.get($data, base-tokens)) > 0};
          --color-tokens: #{list.length(map.get($data, color-tokens)) > 0};
          --typography-tokens: #{list.length(map.get($data, typography-tokens)) > 0};
          --density-tokens: #{list.length(map.get($data, density-tokens)) > 0};
          --color-system-variables-prefix: #{map.get($data, color-system-variables-prefix)};
          --typography-system-variables-prefix: #{map.get($data, typography-system-variables-prefix)};
        }
      `);
      const vars = getRootVars(css);
      expect(vars['keys'].split(', ')).toEqual([
        'theme-version',
        'theme-type',
        'palettes',
        'color-system-variables-prefix',
        'color-tokens',
        'font-definition',
        'typography-system-variables-prefix',
        'typography-tokens',
        'density-scale',
        'density-tokens',
        'base-tokens',
      ]);
      expect(vars['version']).toBe('1');
      expect(vars['type']).toBe('light');
      expect(vars['palettes'].split(', ')).toEqual([
        'primary',
        'secondary',
        'tertiary',
        'neutral',
        'neutral-variant',
        'error',
      ]);
      expect(vars['density']).toBe('0');
      expect(vars['base-tokens']).toBe('true');
      expect(vars['color-tokens']).toBe('true');
      expect(vars['typography-tokens']).toBe('true');
      expect(vars['density-tokens']).toBe('true');
      expect(vars['typography-system-variables-prefix']).toBe('mat-sys');
      expect(vars['color-system-variables-prefix']).toBe('mat-sys');
    });

    it('should customize colors', () => {
      const css = transpile(`
        $theme: mat.define-theme((
          color: (
            theme-type: dark,
            primary: mat.$yellow-palette,
            tertiary: mat.$red-palette,
          )
        ));
        $data: map.get($theme, $internals);
        :root {
          --token-surface: #{map.get($data, color-tokens, (mdc, theme), surface)};
          --token-primary: #{map.get($data, color-tokens, (mdc, theme), primary)};
          --token-secondary: #{map.get($data, color-tokens, (mdc, theme), secondary)};
          --token-tertiary: #{map.get($data, color-tokens, (mdc, theme), tertiary)};
          --palette-primary: #{map.get($data, palettes, primary, 50)};
          --palette-secondary: #{map.get($data, palettes, secondary, 50)};
          --palette-tertiary: #{map.get($data, palettes, tertiary, 50)};
          --type: #{map.get($data, theme-type)};
        }
      `);
      const vars = getRootVars(css);
      expect(vars['token-surface']).toBe('#14140f');
      expect(vars['token-primary']).toBe('#cdcd00');
      expect(vars['token-secondary']).toBe('#cac8a5');
      expect(vars['token-tertiary']).toBe('#ffb4a8');
      expect(vars['palette-primary']).toBe('#7b7b00');
      expect(vars['palette-secondary']).toBe('#7a795a');
      expect(vars['palette-tertiary']).toBe('#ef0000');
      expect(vars['type']).toBe('dark');
    });

    it('should customize typography', () => {
      const css = transpile(`
        $theme: mat.define-theme((
          typography: (
            brand-family: Comic Sans,
            plain-family: Wingdings,
            bold-weight: 300,
            medium-weight: 200,
            regular-weight: 100,
          )
        ));
        $data: map.get($theme, $internals);
        :root {
          --display-font:
            #{map.get($data, typography-tokens, (mdc, typography), display-large-font)};
          --display-weight:
            #{map.get($data, typography-tokens, (mdc, typography), display-large-weight)};
          --title-font:
            #{map.get($data, typography-tokens, (mdc, typography), title-small-font)};
          --title-weight:
            #{map.get($data, typography-tokens, (mdc, typography), title-small-weight)};
        }
      `);
      const vars = getRootVars(css);
      expect(vars['display-font']).toBe('Comic Sans');
      expect(vars['display-weight']).toBe('100');
      expect(vars['title-font']).toBe('Wingdings');
      expect(vars['title-weight']).toBe('200');
    });

    it('should customize density', () => {
      const css = transpile(`
        $theme: mat.define-theme((
          density: (
            scale: -2
          )
        ));
        $data: map.get($theme, $internals);
        :root {
          --size: #{map.get($data, density-tokens, (mdc, checkbox), state-layer-size)};
        }
      `);
      const vars = getRootVars(css);
      expect(vars['size']).toBe('32px');
    });

    it('should throw for invalid system config', () => {
      expect(() => transpile(`$theme: mat.define-theme(5)`)).toThrowError(
        /\$config should be a configuration object\. Got: 5/,
      );
    });

    it('should throw for invalid color config', () => {
      expect(() => transpile(`$theme: mat.define-theme((color: 5))`)).toThrowError(
        /\$config\.color should be a color configuration object\. Got: 5/,
      );
    });

    it('should throw for invalid typography config', () => {
      expect(() => transpile(`$theme: mat.define-theme((typography: 5))`)).toThrowError(
        /\$config\.typography should be a typography configuration object\. Got: 5/,
      );
    });

    it('should throw for invalid density config', () => {
      expect(() => transpile(`$theme: mat.define-theme((density: 5))`)).toThrowError(
        /\$config\.density should be a density configuration object\. Got: 5/,
      );
    });

    it('should throw for invalid config property', () => {
      expect(() => transpile(`$theme: mat.define-theme((fake: 5))`)).toThrowError(
        /\$config has unexpected properties.*Found: fake/,
      );
    });

    it('should throw for invalid color property', () => {
      expect(() => transpile(`$theme: mat.define-theme((color: (fake: 5)))`)).toThrowError(
        /\$config\.color has unexpected properties.*Found: fake/,
      );
    });

    it('should throw for invalid typography property', () => {
      expect(() => transpile(`$theme: mat.define-theme((typography: (fake: 5)))`)).toThrowError(
        /\$config\.typography has unexpected properties.*Found: fake/,
      );
    });

    it('should throw for invalid density property', () => {
      expect(() => transpile(`$theme: mat.define-theme((density: (fake: 5)))`)).toThrowError(
        /\$config\.density has unexpected properties.*Found: fake/,
      );
    });

    it('should throw for invalid theme type', () => {
      expect(() =>
        transpile(`$theme: mat.define-theme((color: (theme-type: black)))`),
      ).toThrowError(/Expected \$config\.color.theme-type to be one of:.*Got: black/);
    });

    it('should throw for invalid palette', () => {
      expect(() =>
        transpile(`$theme: mat.define-theme((color: (tertiary: mat.$m2-red-palette)))`),
      ).toThrowError(/Expected \$config\.color\.tertiary to be a valid M3 palette\. Got:/);
    });

    it('should throw for invalid density scale', () => {
      expect(() => transpile(`$theme: mat.define-theme((density: (scale: 10)))`)).toThrowError(
        /Expected \$config\.density\.scale to be one of:.*Got: 10/,
      );
    });
  });


import {compileString} from 'sass';
import {runfiles} from '@bazel/runfiles';
import * as path from 'path';

import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer';

// Note: For Windows compatibility, we need to resolve the directory paths through runfiles
// which are guaranteed to reside in the source tree.
const testDir = path.join(runfiles.resolvePackageRelative('../_all-theme.scss'), '../tests');
const packagesDir = path.join(runfiles.resolveWorkspaceRelative('src/cdk/_index.scss'), '../..');

const localPackageSassImporter = createLocalAngularPackageImporter(packagesDir);

/** Transpiles given Sass content into CSS. */
function transpile(content: string) {
  return compileString(
    `
        @use 'sass:map';
        @use '../../../index' as mat;

        ${content}
      `,
    {
      loadPaths: [testDir],
      importers: [localPackageSassImporter],
    },
  ).css.toString();
}

describe('theming inspection api', () => {
  describe('for m2 theme', () => {
    it('should get theme version', () => {
      expect(
        transpile(`
          $theme: mat.m2-define-light-theme((
            color: (
              primary: mat.m2-define-palette(mat.$m2-red-palette),
              accent: mat.m2-define-palette(mat.$m2-red-palette),
              warn: mat.m2-define-palette(mat.$m2-red-palette),
            ),
            typography: mat.m2-define-typography-config(),
            density: 0,
          ));
          div {
            --theme-version: #{mat.get-theme-version($theme)};
          }
        `),
      ).toMatch('--theme-version: 0;');
    });

    it('should get theme type', () => {
      expect(
        transpile(`
          $theme: mat.m2-define-dark-theme((
            color: (
              primary: mat.m2-define-palette(mat.$m2-red-palette),
              accent: mat.m2-define-palette(mat.$m2-red-palette),
            ),
          ));
          div {
            --theme-type: #{mat.get-theme-type($theme)};
          }
        `),
      ).toMatch('--theme-type: dark;');
    });

    it('should get role color', () => {
      expect(
        transpile(`
          $theme: mat.m2-define-light-theme((
            color: (
              primary: mat.m2-define-palette(mat.$m2-red-palette),
              accent: mat.m2-define-palette(mat.$m2-green-palette)
            )
          ));
          div {
            color: mat.get-theme-color($theme, accent);
          }
        `),
      ).toMatch('color: #4caf50;');
    });

    it('should get palette color', () => {
      expect(
        transpile(`
          $theme: mat.m2-define-light-theme((
            color: (
              primary: mat.m2-define-palette(mat.$m2-red-palette),
              accent: mat.m2-define-palette(mat.$m2-green-palette)
            )
          ));
          div {
            color: mat.get-theme-color($theme, accent, A200);
          }
        `),
      ).toMatch('color: #69f0ae;');
    });

    it('should get typography properties from theme', () => {
      const css = transpile(`
        $theme: mat.m2-define-light-theme((
          typography: mat.m2-define-typography-config()
        ));
        div {
          font: mat.get-theme-typography($theme, headline-1);
          font-family: mat.get-theme-typography($theme, headline-1, font-family);
          font-size: mat.get-theme-typography($theme, headline-1, font-size);
          font-weight: mat.get-theme-typography($theme, headline-1, font-weight);
          line-height: mat.get-theme-typography($theme, headline-1, line-height);
          letter-spacing: mat.get-theme-typography($theme, headline-1, letter-spacing);
        }
      `);
      expect(css).toMatch('font: 300 96px / 96px Roboto, sans-serif;');
      expect(css).toMatch('font-family: Roboto, sans-serif;');
      expect(css).toMatch('font-size: 96px;');
      expect(css).toMatch('font-weight: 300;');
      expect(css).toMatch('line-height: 96px;');
      expect(css).toMatch('letter-spacing: -0.015625em;');
    });

    it('should get density scale', () => {
      expect(
        transpile(`
          $theme: mat.m2-define-light-theme((
            density: -1
          ));
          div {
            --density-scale: #{mat.get-theme-density($theme)};
          }
        `),
      ).toMatch('--density-scale: -1;');
    });

    it('should check what information the theme has', () => {
      const css = transpile(`
        $theme: mat.m2-define-light-theme((
          color: (
            primary: mat.m2-define-palette(mat.$m2-red-palette),
            accent: mat.m2-define-palette(mat.$m2-red-palette),
          ),
          typography: mat.m2-define-typography-config(),
          density: -1,
        ));
        $color-only: mat.m2-define-light-theme((
          color: (
            primary: mat.m2-define-palette(mat.$m2-red-palette),
            accent: mat.m2-define-palette(mat.$m2-red-palette),
          ),
          typography: null,
          density: null,
        ));
        $typography-only: mat.m2-define-light-theme((
          color: null,
          typography: mat.m2-define-typography-config(),
          density: null,
        ));
        $density-only: mat.m2-define-light-theme((
          color: null,
          typography: null,
          density: -1,
        ));
        div {
          --base: #{(
            mat.theme-has($theme, base),
            mat.theme-has($color-only, base),
            mat.theme-has($typography-only, base),
            mat.theme-has($density-only, base),
          )};
          --color: #{(
            mat.theme-has($theme, color),
            mat.theme-has($color-only, color),
            mat.theme-has($typography-only, color),
            mat.theme-has($density-only, color),
          )};
          --typography: #{(
            mat.theme-has($theme, typography),
            mat.theme-has($color-only, typography),
            mat.theme-has($typography-only, typography),
            mat.theme-has($density-only, typography),
          )};
          --density: #{(
            mat.theme-has($theme, density),
            mat.theme-has($color-only, density),
            mat.theme-has($typography-only, density),
            mat.theme-has($density-only, density),
          )};
        }
      `);
      expect(css).toMatch(/--base: true, true, true, true;/);
      expect(css).toMatch(/--color: true, true, false, false;/);
      expect(css).toMatch(/--typography: true, false, true, false;/);
      expect(css).toMatch(/--density: true, false, false, true;/);
    });

    it('should work with compatibility disabled', () => {
      expect(
        transpile(`
          mat.$theme-legacy-inspection-api-compatibility: false;
          $theme: mat.m2-define-dark-theme((
            color: (
              primary: mat.m2-define-palette(mat.$m2-red-palette),
              accent: mat.m2-define-palette(mat.$m2-red-palette),
            )
          ));
          div {
            --theme-type: #{mat.get-theme-type($theme)};
          }
        `),
      ).toMatch('--theme-type: dark;');
    });

    it('should not allow access via legacy APIs with compatibility disabled', () => {
      expect(() =>
        transpile(`
          mat.$theme-legacy-inspection-api-compatibility: false;
          $theme: mat.m2-define-dark-theme((
            color: (
              primary: mat.m2-define-palette(mat.$red-palette),
              accent: mat.m2-define-palette(mat.$red-palette),
            )
          ));
          $color-config: mat.get-color-config($theme);
          $primary: map.get($color-config, primary);
          div {
            color: #{mat.m2-get-color-from-palette($primary)};
          }
        `),
      ).toThrow();
    });
  });


describe('for m3 theme', () => {
    it('should get theme version', () => {
      expect(
        transpile(`
          $theme: mat.define-theme();
          div {
            --theme-version: #{mat.get-theme-version($theme)};
          }
        `),
      ).toMatch('--theme-version: 1;');
    });

    it('should get theme type', () => {
      expect(
        transpile(`
          $theme: mat.define-theme();
          div {
            --theme-type: #{mat.get-theme-type($theme)};
          }
        `),
      ).toMatch('--theme-type: light;');
    });

    it('should get role color', () => {
      expect(
        transpile(`
          $theme: mat.define-theme();
          div {
            color: mat.get-theme-color($theme, primary-container);
          }
        `),
      ).toMatch('color: #ecdcff;');
    });

    it('should error on invalid color role', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-theme();
          div {
            color: mat.get-theme-color($theme, fake-role);
          }
        `),
      ).toThrowError(/Valid color roles are.*Got: fake-role/);
    });

    it('should get palette color', () => {
      expect(
        transpile(`
          $theme: mat.define-theme();
          div {
            color: mat.get-theme-color($theme, tertiary, 20);
          }
        `),
      ).toMatch('color: #42008a;');
    });

    it('should error on invalid color palette', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-theme();
          div {
            color: mat.get-theme-color($theme, fake-palette, 20);
          }
        `),
      ).toThrowError(/Valid palettes are.*Got: fake-palette/);
    });

    it('should error on invalid color hue', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-theme();
          div {
            color: mat.get-theme-color($theme, neutral, 11);
          }
        `),
      ).toThrowError(/Valid hues for neutral are.*Got: 11/);
    });

    it('should error on wrong number of get-color-theme args', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-theme();
          div {
            color: mat.get-theme-color($theme);
          }
        `),
      ).toThrowError(/Expected between 2 and 4 arguments\. Got: 1/);
    });

    it('should get typography properties from theme', () => {
      const css = transpile(`
        $theme: mat.define-theme();
        div {
          font: mat.get-theme-typography($theme, headline-large);
          font-family: mat.get-theme-typography($theme, headline-large, font-family);
          font-size: mat.get-theme-typography($theme, headline-large, font-size);
          font-weight: mat.get-theme-typography($theme, headline-large, font-weight);
          line-height: mat.get-theme-typography($theme, headline-large, line-height);
          letter-spacing: mat.get-theme-typography($theme, headline-large, letter-spacing);
        }
      `);
      expect(css).toMatch('font: 400 2rem / 2.5rem Roboto, sans-serif;');
      expect(css).toMatch('font-family: Roboto, sans-serif;');
      expect(css).toMatch('font-size: 2rem;');
      expect(css).toMatch('font-weight: 400;');
      expect(css).toMatch('line-height: 2.5rem;');
      expect(css).toMatch('letter-spacing: 0;');
    });

    it('should error on invalid typescale', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-theme();
          div {
            font: mat.get-theme-typography($theme, subtitle-large);
          }
        `),
      ).toThrowError(/Valid typescales are:.*Got: subtitle-large/);
    });

    it('should error on invalid typography property', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-theme();
          div {
            text-transform: mat.get-theme-typography($theme, body-small, text-transform);
          }
        `),
      ).toThrowError(/Valid typography properties are:.*Got: text-transform/);
    });

    it('should get density scale', () => {
      expect(
        transpile(`
          $theme: mat.define-theme();
          div {
            --density-scale: #{mat.get-theme-density($theme)};
          }
        `),
      ).toMatch('--density-scale: 0;');
    });

    it('should check what information the theme has', () => {
      const css = transpile(`
        $theme: mat.define-theme();
        $color-only: mat.define-colors();
        $typography-only: mat.define-typography();
        $density-only: mat.define-density();
        div {
          --base: #{(
            mat.theme-has($theme, base),
            mat.theme-has($color-only, base),
            mat.theme-has($typography-only, base),
            mat.theme-has($density-only, base),
          )};
          --color: #{(
            mat.theme-has($theme, color),
            mat.theme-has($color-only, color),
            mat.theme-has($typography-only, color),
            mat.theme-has($density-only, color),
          )};
          --typography: #{(
            mat.theme-has($theme, typography),
            mat.theme-has($color-only, typography),
            mat.theme-has($typography-only, typography),
            mat.theme-has($density-only, typography),
          )};
          --density: #{(
            mat.theme-has($theme, density),
            mat.theme-has($color-only, density),
            mat.theme-has($typography-only, density),
            mat.theme-has($density-only, density),
          )};
        }
      `);
      expect(css).toMatch(/--base: true, false, false, false;/);
      expect(css).toMatch(/--color: true, true, false, false;/);
      expect(css).toMatch(/--typography: true, false, true, false;/);
      expect(css).toMatch(/--density: true, false, false, true;/);
    });

    it('should error when reading theme type from a theme with no color information', () => {
      expect(() =>
        transpile(`
        $theme: mat.define-density();
        div {
          color: mat.get-theme-type($theme);
        }
      `),
      ).toThrowError(/Color information is not available on this theme/);
    });

    it('should error when reading color from a theme with no color information', () => {
      expect(() =>
        transpile(`
        $theme: mat.define-density();
        div {
          color: mat.get-theme-color($theme, primary);
        }
      `),
      ).toThrowError(/Color information is not available on this theme/);
    });

    it('should error when reading typography from a theme with no typography information', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-density();
          div {
            font: mat.get-theme-typography($theme, body-small);
          }
        `),
      ).toThrowError(/Typography information is not available on this theme/);
    });

    it('should error when reading density from a theme with no density information', () => {
      expect(() =>
        transpile(`
          $theme: mat.define-colors();
          div {
            --density: #{mat.get-theme-density($theme)};
          }
        `),
      ).toThrowError(/Density information is not available on this theme/);
    });

    it('should not emit styles for removed theme dimensions', () => {
      const css = transpile(`
        $theme: mat.theme-remove(mat.define-theme(), base, color, typography, density);
        div {
          @include mat.all-component-themes($theme);
        }`);
      expect(css.trim()).toBe('');
    });
  });
});


@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
@use '../theming/theming';
@use './palette';

/// Extracts a color from a palette or throws an error if it doesn't exist.
/// @param {Map} $palette The palette from which to extract a color.
/// @param {String | Number} $hue The hue for which to get the color.
@function _get-color-from-palette($palette, $hue) {
  @if map.has-key($palette, $hue) {
    @return map.get($palette, $hue);
  }

  @error 'Hue "' + $hue + '" does not exist in palette. Available hues are: ' + map.keys($palette);
}

/// For a given hue in a palette, return the contrast color from the map of contrast palettes.
/// @param {Map} $palette The palette from which to extract a color.
/// @param {String | Number} $hue The hue for which to get a contrast color.
/// @returns {Color} The contrast color for the given palette and hue.
@function get-contrast-color-from-palette($palette, $hue) {
  @return map.get(map.get($palette, contrast), $hue);
}

/// Creates a map of hues to colors for a theme. This is used to define a theme palette in terms
/// of the Material Design hues.
/// @param {Map} $base-palette Map of hue keys to color values for the basis for this palette.
/// @param {String | Number} $default Default hue for this palette.
/// @param {String | Number} $lighter "lighter" hue for this palette.
/// @param {String | Number} $darker "darker" hue for this palette.
/// @param {String | Number} $text "text" hue for this palette.
/// @returns {Map} A complete Angular Material theming palette.
@function define-palette($base-palette, $default: 500, $lighter: 100, $darker: 700,
  $text: $default) {
  $result: map.merge($base-palette, (
    default: _get-color-from-palette($base-palette, $default),
    lighter: _get-color-from-palette($base-palette, $lighter),
    darker: _get-color-from-palette($base-palette, $darker),
    text: _get-color-from-palette($base-palette, $text),
    default-contrast: get-contrast-color-from-palette($base-palette, $default),
    lighter-contrast: get-contrast-color-from-palette($base-palette, $lighter),
    darker-contrast: get-contrast-color-from-palette($base-palette, $darker)
  ));

  // For each hue in the palette, add a "-contrast" color to the map.
  @each $hue, $color in $base-palette {
    $result: map.merge($result, (
      '#{$hue}-contrast': get-contrast-color-from-palette($base-palette, $hue)
    ));
  }

  @return $result;
}

/// Gets a color from a theme palette (the output of mat-palette).
/// The hue can be one of the standard values (500, A400, etc.), one of the three preconfigured
/// hues (default, lighter, darker), or any of the aforementioned suffixed with "-contrast".
///
/// @param {Map} $palette The palette from which to extract a color.
/// @param {String | Number} $hue The hue from the palette to use. If this is a value between 0
//     and 1, it will be treated as opacity.
/// @param {Number} $opacity The alpha channel value for the color.
/// @returns {Color} The color for the given palette, hue, and opacity.
@function get-color-from-palette($palette, $hue: default, $opacity: null) {
  // If hueKey is a number between zero and one, then it actually contains an
  // opacity value, so recall this function with the default hue and that given opacity.
  @if meta.type-of($hue) == number and $hue >= 0 and $hue <= 1 {
    @return get-color-from-palette($palette, default, $hue);
  }

  // We cast the $hue to a string, because some hues starting with a number, like `700-contrast`,
  // might be inferred as numbers by Sass. Casting them to string fixes the map lookup.
  $color: if(map.has-key($palette, $hue), map.get($palette, $hue), map.get($palette, $hue + ''));

  @if (meta.type-of($color) != color) {
    // If the $color resolved to something different from a color (e.g. a CSS variable),
    // we can't apply the opacity anyway so we return the value as is, otherwise Sass can
    // throw an error or output something invalid.
    @return $color;
  }

  @return rgba($color, if($opacity == null, opacity($color), $opacity));
}

// Validates the specified theme by ensuring that the optional color config defines
// a primary, accent and warn palette. Returns the theme if no failures were found.
@function _mat-validate-theme($theme) {
  @if map.get($theme, color) {
    $color: map.get($theme, color);
    @if not map.get($color, primary) {
      @error 'Theme does not define a valid "primary" palette.';
    }
    @else if not map.get($color, accent) {
      @error 'Theme does not define a valid "accent" palette.';
    }
    @else if not map.get($color, warn) {
      @error 'Theme does not define a valid "warn" palette.';
    }
  }
  @return $theme;
}

// Creates a light-themed color configuration from the specified
// primary, accent and warn palettes.
@function _mat-create-light-color-config($primary, $accent, $warn: null) {
  @return (
    primary: $primary,
    accent: $accent,
    warn: if($warn != null, $warn, define-palette(palette.$red-palette)),
    is-dark: false,
    foreground: palette.$light-theme-foreground-palette,
    background: palette.$light-theme-background-palette,
  );
}

// Creates a dark-themed color configuration from the specified
// primary, accent and warn palettes.
@function _mat-create-dark-color-config($primary, $accent, $warn: null) {
  @return (
    primary: $primary,
    accent: $accent,
    warn: if($warn != null, $warn, define-palette(palette.$red-palette)),
    is-dark: true,
    foreground: palette.$dark-theme-foreground-palette,
    background: palette.$dark-theme-background-palette,
  );
}

// TODO: Remove legacy API and rename `$primary` below to `$config`. Currently it cannot be renamed
// as it would break existing apps that set the parameter by name.

/// Creates a container object for a light theme to be given to individual component theme mixins.
/// @param {Map} $primary The theme configuration object.
/// @returns {Map} A complete Angular Material theme map.


@use 'sass:list';
@use 'sass:map';
@use 'sass:string';
@use 'typography-utils';
@use '../theming/inspection';

// Definition and versioning functions live in their own files to avoid circular dependencies, but
// we re-export them here so that historical imports from this file continue to work without needing
// to be updated.
@forward './versioning';

@mixin typography-hierarchy($theme, $selector: '.mat-typography', $back-compat: false) {
  @if inspection.get-theme-version($theme) == 1 {
    @include _m3-typography-hierarchy($theme, $selector, $back-compat);
  }
  @else {
    @include _m2-typography-hierarchy($theme, $selector);
  }
}

@function _get-selector($selectors, $prefix) {
  $result: ();
  @each $selector in $selectors {
    // Don't add "naked" tag selectors, and don't nest prefix selector.
    @if string.index($selector, '.') == 1 {
      $result: list.append($result, $selector, $separator: comma);
    }
    // Don't nest the prefix selector in itself.
    @if $selector != $prefix {
      $result: list.append($result, '#{$prefix} #{$selector}', $separator: comma);
    }
  }
  @return $result;
}

@mixin _m3-typography-level($theme, $selector-prefix, $level, $selectors, $margin: null) {
  #{_get-selector($selectors, $selector-prefix)} {
    // TODO(mmalerba): When we expose system tokens as CSS vars, we should change this to emit token
    //  slots.
    font: inspection.get-theme-typography($theme, $level, font);
    letter-spacing: inspection.get-theme-typography($theme, $level, letter-spacing);
    @if $margin != null {
      margin: 0 0 $margin;
    }
  }
}

@mixin _m3-typography-hierarchy($theme, $selector-prefix, $add-m2-selectors) {
 $levels: (
   display-large: (
     selectors: ('.mat-display-large', 'h1'),
     m2-selectors: ('.mat-h1', '.mat-headline-1'),
     margin: 0.5em
   ),
   display-medium: (
     selectors: ('.mat-display-medium', 'h2'),
     m2-selectors: ('.mat-h2', '.mat-headline-2'),
     margin: 0.5em
   ),
   display-small: (
     selectors: ('.mat-display-small', 'h3'),
     m2-selectors: ('.mat-h3', '.mat-headline-3'),
     margin: 0.5em
   ),
   headline-large: (
     selectors: ('.mat-headline-large', 'h4'),
     m2-selectors: ('.mat-h4', '.mat-headline-4'),
     margin: 0.5em
   ),
   headline-medium: (
     selectors: ('.mat-headline-medium', 'h5'),
     m2-selectors: ('.mat-h5', '.mat-headline-5'),
     margin: 0.5em
   ),
   headline-small: (
     selectors: ('.mat-headline-small', 'h6'),
     m2-selectors: ('.mat-h6', '.mat-headline-6'),
     margin: 0.5em
   ),
   title-large: (
     selectors: ('.mat-title-large'),
     m2-selectors: ('.mat-subtitle-1'),
   ),
   title-medium: (
     selectors: ('.mat-title-medium'),
     m2-selectors: ('.mat-subtitle-2'),
   ),
   title-small: (
     selectors: ('.mat-title-small')
   ),
   body-large: (
     selectors: ('.mat-body-large', $selector-prefix),
     m2-selectors: ('.mat-body', '.mat-body-strong', '.mat-body-2'),
   ),
   body-medium: (
     selectors: ('.mat-body-medium')
   ),
   body-small: (
     selectors: ('.mat-body-small')
   ),
   label-large: (
     selectors: ('.mat-label-large')
   ),
   label-medium: (
     selectors: ('.mat-label-medium')
   ),
   label-small: (
     selectors: ('.mat-label-small'),
     m2-selectors: ('.mat-small', '.mat-caption')
   ),
 );

  @each $level, $options in $levels {
    @if $add-m2-selectors {
      $options: map.set($options, selectors,
          list.join(map.get($options, selectors), map.get($options, m2-selectors) or ()));
    }
    $options: map.remove($options, m2-selectors);

    // Apply styles for the level.
    @include _m3-typography-level($theme, $selector-prefix, $level, $options...);

    // Also style <p> inside body-large.
    @if $level == body-large {
      #{_get-selector(map.get($options, selectors), $selector-prefix)} {
        p {
          margin: 0 0 0.75em;
        }
      }
    }
  }
}

/// Emits baseline typographic styles based on a given config.
/// @param {Map} $config-or-theme A typography config for an entire theme.
/// @param {String} $selector Ancestor selector under which native elements, such as h1, will
///     be styled.
@mixin _m2-typography-hierarchy($theme, $selector) {
  // Note that it seems redundant to prefix the class rules with the `$selector`, however it's
  // necessary if we want to allow people to overwrite the tag selectors. This is due to
  // selectors like `#{$selector} h1` being more specific than ones like `.mat-title`.
  .mat-h1,
  .mat-headline-5,
  #{$selector} .mat-h1,
  #{$selector} .mat-headline-5,
  #{$selector} h1 {
    font: inspection.get-theme-typography($theme, headline-5, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-5, letter-spacing);
    margin: 0 0 16px;
  }

  .mat-h2,
  .mat-headline-6,
  #{$selector} .mat-h2,
  #{$selector} .mat-headline-6,
  #{$selector} h2 {
    font: inspection.get-theme-typography($theme, headline-6, font);
    letter-spacing: inspection.get-theme-typography($theme, headline-6, letter-spacing);
    margin: 0 0 16px;
  }

  .mat-h3,
  .mat-subtitle-1,
  #{$selector} .mat-h3,
  #{$selector} .mat-subtitle-1,
  #{$selector} h3 {
    font: inspection.get-theme-typography($theme, subtitle-1, font);
    letter-spacing: inspection.get-theme-typography($theme, subtitle-1, letter-spacing);
    margin: 0 0 16px;
  }

  .mat-h4,
  .mat-body-1,
  #{$selector} .mat-h4,
  #{$selector} .mat-body-1,
  #{$selector} h4 {
    font: inspection.get-theme-typography($theme, body-1, font);
    letter-spacing: inspection.get-theme-typography($theme, body-1, letter-spacing);
    margin: 0 0 16px;
  }

  // Note: the spec doesn't have anything that would correspond to h5 and h6, but we add these for
  // consistency. The font sizes come from the Chrome user agent styles which have h5 at 0.83em
  // and h6 at 0.67em.
  .mat-h5,
  #{$selector} .mat-h5,
  #{$selector} h5 {
    @include typography-utils


@function _get-sys-typeface($ref, $prefix) {
  @if (sass-utils.$use-system-typography-variables) {
    $keys: (
            'body-large',
            'body-large-font',
            'body-large-line-height',
            'body-large-size',
            'body-large-tracking',
            'body-large-weight',
            'body-medium',
            'body-medium-font',
            'body-medium-line-height',
            'body-medium-size',
            'body-medium-tracking',
            'body-medium-weight',
            'body-small',
            'body-small-font',
            'body-small-line-height',
            'body-small-size',
            'body-small-tracking',
            'body-small-weight',
            'display-large',
            'display-large-font',
            'display-large-line-height',
            'display-large-size',
            'display-large-tracking',
            'display-large-weight',
            'display-medium',
            'display-medium-font',
            'display-medium-line-height',
            'display-medium-size',
            'display-medium-tracking',
            'display-medium-weight',
            'display-small',
            'display-small-font',
            'display-small-line-height',
            'display-small-size',
            'display-small-tracking',
            'display-small-weight',
            'headline-large',
            'headline-large-font',
            'headline-large-line-height',
            'headline-large-size',
            'headline-large-tracking',
            'headline-large-weight',
            'headline-medium',
            'headline-medium-font',
            'headline-medium-line-height',
            'headline-medium-size',
            'headline-medium-tracking',
            'headline-medium-weight',
            'headline-small',
            'headline-small-font',
            'headline-small-line-height',
            'headline-small-size',
            'headline-small-tracking',
            'headline-small-weight',
            'label-large',
            'label-large-font',
            'label-large-line-height',
            'label-large-size',
            'label-large-tracking',
            'label-large-weight',
            'label-large-weight-prominent',
            'label-medium',
            'label-medium-font',
            'label-medium-line-height',
            'label-medium-size',
            'label-medium-tracking',
            'label-medium-weight',
            'label-medium-weight-prominent',
            'label-small',
            'label-small-font',
            'label-small-line-height',
            'label-small-size',
            'label-small-tracking',
            'label-small-weight',
            'title-large',
            'title-large-font',
            'title-large-line-height',
            'title-large-size',
            'title-large-tracking',
            'title-large-weight',
            'title-medium',
            'title-medium-font',
            'title-medium-line-height',
            'title-medium-size',
            'title-medium-tracking',
            'title-medium-weight',
            'title-small',
            'title-small-font',
            'title-small-line-height',
            'title-small-size',
            'title-small-tracking',
            'title-small-weight'
    );

    @return create-map($keys, $prefix);
  }
  @return m3-token-definitions.md-sys-typescale-values($ref);
}

/// Generates a set of namespaced color tokens for all components.
/// @param {String} $type The type of theme system (light or dark)
/// @param {Map} $primary The primary palette
/// @param {Map} $tertiary The tertiary palette
/// @param {Map} $error The error palette
/// @param {String} $system-variables-prefix The prefix of system tokens
/// @return {Map} A map of namespaced color tokens
@function generate-color-tokens($type, $primary, $tertiary, $error, $system-variables-prefix) {
  $ref: (
    md-ref-palette: generate-ref-palette-tokens($primary, $tertiary, $error)
  );

  $sys-color: _get-sys-color($type, $ref, $system-variables-prefix);

  @return generate-tokens(map.merge($ref, (
    md-sys-color: $sys-color,
    // Because the elevation values are always combined with color values to create the box shadow,
    // elevation needs to be part of the color dimension.
    md-sys-elevation: m3-token-definitions.md-sys-elevation-values(),
    // Because the state values are sometimes combined with color values to create rgba colors,
    // state needs to be part of color dimension.
    // TODO(mmalerba): If at some point we remove the need for these combined values, we can move
    //  state to the base dimension.
    md-sys-state: m3-token-definitions.md-sys-state-values(),
  )));
}

/// Generates a set of namespaced color tokens for all components.
/// @param {String|List} $brand The brand font-family
/// @param {String|List} $plain The plain fort-family
/// @param {String|Number} $bold The bold font-weight
/// @param {String|Number} $medium The medium font-weight
/// @param {String|Number} $regular The regular font-weight
/// @param {String} $system-variables-prefix The prefix of system tokens
/// @return {Map} A map of namespaced typography tokens
@function generate-typography-tokens($brand, $plain, $bold, $medium, $regular,
$system-variables-prefix) {
  $ref: (
    md-ref-typeface: generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular)
  );
  $sys-typeface: _get-sys-typeface($ref, $system-variables-prefix);
  @return generate-tokens((
    md-sys-typescale: $sys-typeface
  ));
}

/// Generates a set of namespaced density tokens for all components.
/// @param {String|Number} $scale The regular font-weight
/// @return {Map} A map of namespaced density tokens
@function generate-density-tokens($scale) {
  @return density.get-tokens-for-scale($scale);
}

/// Generates a set of namespaced tokens not related to color, typography, or density for all
/// components.
/// @return {Map} A map of namespaced tokens not related to color, typography, or density
@function generate-base-tokens() {
  // TODO(mmalerba): Exclude density tokens once implemented.
  @return generate-tokens((
    md-sys-motion: m3-token-definitions.md-sys-motion-values(),
    md-sys-shape: m3-token-definitions.md-sys-shape-values(),
  ), $include-non-systemized: true);
}


@mixin system-level-typography($theme, $overrides: (), $prefix: null) {
  $font-definition: map.get($theme, _mat-theming-internals-do-not-access, font-definition);
  $brand: map.get($font-definition, brand);
  $plain: map.get($font-definition, plain);
  $bold: map.get($font-definition, bold);
  $medium: map.get($font-definition, medium);
  $regular: map.get($font-definition, regular);
  $ref: (md-ref-typeface:
      m3-tokens.generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular)
  );

  @if (not $prefix) {
    $prefix: map.get($theme, _mat-theming-internals-do-not-access,
        typography-system-variables-prefix) or definition.$system-level-prefix;
  }

  & {
    @each $name, $value in definitions.md-sys-typescale-values($ref) {
      --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
    }
  }
}

@mixin system-level-elevation($theme, $overrides: (), $prefix: definition.$system-level-prefix) {
  $shadow-color: map.get(
      $theme, _mat-theming-internals-do-not-access, color-tokens, (mdc, theme), shadow);

  @each $name, $value in definitions.md-sys-elevation-values() {
    $level: map.get($overrides, $name) or $value;
    $value: elevation.get-box-shadow($level, $shadow-color);
    & {
      --#{$prefix}-#{$name}: #{$value};
    }
  }
}

@mixin system-level-shape($theme: (), $overrides: (), $prefix: definition.$system-level-prefix) {
  & {
    @each $name, $value in definitions.md-sys-shape-values() {
      --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
    }
  }
}

@mixin system-level-state($theme: (), $overrides: (), $prefix: definition.$system-level-prefix) {
  & {
    @each $name, $value in definitions.md-sys-state-values() {
      --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
    }
  }
}

@mixin system-level-motion($theme: (), $overrides: (), $prefix: definition.$system-level-prefix) {
  & {
    @each $name, $value in definitions.md-sys-motion-values() {
      --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value};
    }
  }
}

// Return a new map where the values are the same as the provided map's
// keys, prefixed with "--mat-sys-". For example:
// (key1: '', key2: '') --> (key1: --mat-sys-key1, key2: --mat-sys-key2)
@function _create-system-app-vars-map($map) {
  $new-map: ();
  @each $key, $value in $map {
    $new-map: map.set($new-map, $key, --#{definition.$system-fallback-prefix}-#{$key});
  }
  @return $new-map;
}

// Create a components tokens map where values are based on
// system fallback variables referencing Material's system keys.
// Includes density token fallbacks where density is 0.
@function create-system-fallbacks() {
  $app-vars: (
    'md-sys-color':
        _create-system-app-vars-map(definitions.md-sys-color-values-light()),
    'md-sys-typescale':
        _create-system-app-vars-map(definitions.md-sys-typescale-values()),
    'md-sys-elevation':
        _create-system-app-vars-map(definitions.md-sys-elevation-values()),
    'md-sys-state':
        _create-system-app-vars-map(definitions.md-sys-state-values()),
    'md-sys-shape':
        _create-system-app-vars-map(definitions.md-sys-shape-values()),
    // Add a subset of palette-specific colors used by components instead of system values
    'md-ref-palette':
        _create-system-app-vars-map(
          (
            neutral10: '', // Form field native select option text color
            neutral-variant20: '', // Sidenav scrim (container background shadow when opened),
          )
        ),
  );

  @return sass-utils.deep-merge-all(
      m3-tokens.generate-tokens($app-vars, true, true),
      m3-tokens.generate-density-tokens(0)
  );
}

/// Creates a single merged map from the provided variable-length map arguments
@function map-merge-all($maps...) {
  $result: ();
  @each $map in $maps {
    $result: map.merge($result, $map);
  }
  @return $result;
}


#{'/'} if(
            $exclude-hardcoded-values,
            null,
            1.5rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'body-large-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'body-large-line-height': if($exclude-hardcoded-values, null, 1.5rem),
    'body-large-size': if($exclude-hardcoded-values, null, 1rem),
    'body-large-tracking': if($exclude-hardcoded-values, null, 0.031rem),
    'body-large-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),

    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.body-medium.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'body-medium':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 0.875rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1.25rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'body-medium-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'body-medium-line-height': if($exclude-hardcoded-values, null, 1.25rem),
    'body-medium-size': if($exclude-hardcoded-values, null, 0.875rem),
    'body-medium-tracking': if($exclude-hardcoded-values, null, 0.016rem),
    'body-medium-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.body-small.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'body-small':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 0.75rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            1rem
          ) map.get($deps, 'md-ref-typeface', 'plain')
      ),
    'body-small-font': map.get($deps, 'md-ref-typeface', 'plain'),
    'body-small-line-height': if($exclude-hardcoded-values, null, 1rem),
    'body-small-size': if($exclude-hardcoded-values, null, 0.75rem),
    'body-small-tracking': if($exclude-hardcoded-values, null, 0.025rem),
    'body-small-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.display-large.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'display-large':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 3.562rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            4rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      ),
    'display-large-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'display-large-line-height': if($exclude-hardcoded-values, null, 4rem),
    'display-large-size': if($exclude-hardcoded-values, null, 3.562rem),
    'display-large-tracking': if($exclude-hardcoded-values, null, -0.016rem),
    'display-large-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.display-medium.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'display-medium':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 2.812rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            3.25rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      ),
    'display-medium-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'display-medium-line-height': if($exclude-hardcoded-values, null, 3.25rem),
    'display-medium-size': if($exclude-hardcoded-values, null, 2.812rem),
    'display-medium-tracking': if($exclude-hardcoded-values, null, 0),
    'display-medium-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.display-small.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'display-small':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 2.25rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            2.75rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      ),
    'display-small-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'display-small-line-height': if($exclude-hardcoded-values, null, 2.75rem),
    'display-small-size': if($exclude-hardcoded-values, null, 2.25rem),
    'display-small-tracking': if($exclude-hardcoded-values, null, 0),
    'display-small-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.headline-large.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'headline-large':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 2rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            2.5rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      ),
    'headline-large-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'headline-large-line-height': if($exclude-hardcoded-values, null, 2.5rem),
    'headline-large-size': if($exclude-hardcoded-values, null, 2rem),
    'headline-large-tracking': if($exclude-hardcoded-values, null, 0),
    'headline-large-weight': map.get($deps, 'md-ref-typeface', 'weight-regular'),
    // Warning: risk of reduced fidelity from using this composite typography token.
    // Tokens md.sys.typescale.headline-medium.tracking cannot be represented in the "font"
    // property shorthand. Consider using the discrete properties instead.
    'headline-medium':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 1.75rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            2.25rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      ),
    'headline-medium-font': map.get($deps, 'md-ref-typeface', 'brand'),
    'headline-medium-line-height': if($exclude-hardcoded-values, null, 2.25rem),
    'headline-medium-size': if($exclude-hardcoded-values, null, 1.75rem),
    'headline-medium-tracking': if($exclude-hardcoded-values, null, 0),
    'headline-medium-weight':
      map.get($deps, 'md-ref-typeface', 'weight-regular'),
      // Warning: risk of reduced fidelity from using this composite typography token.
      // Tokens md.sys.typescale.headline-small.tracking cannot be represented in the "font"
      // property shorthand. Consider using the discrete properties instead.
    'headline-small':
      if(
        $exclude-hardcoded-values,
        null,
          map.get($deps, 'md-ref-typeface', 'weight-regular')
          if($exclude-hardcoded-values, null, 1.5rem) #{'/'} if(
            $exclude-hardcoded-values,
            null,
            2rem
          ) map.get($deps, 'md-ref-typeface', 'brand')
      )