/** * @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 { Component, ViewEncapsulation, ChangeDetectionStrategy, Input, InjectionToken, booleanAttribute, inject, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {MatOptionParentComponent, MAT_OPTION_PARENT_COMPONENT} from './option-parent'; // Notes on the accessibility pattern used for `mat-optgroup`. // The option group has two different "modes": regular and inert. The regular mode uses the // recommended a11y pattern which has `role="group"` on the group element with `aria-labelledby` // pointing to the label. This works for `mat-select`, but it seems to hit a bug for autocomplete // under VoiceOver where the group doesn't get read out at all. The bug appears to be that if // there's __any__ a11y-related attribute on the group (e.g. `role` or `aria-labelledby`), // VoiceOver on Safari won't read it out. // We've introduced the `inert` mode as a workaround. Under this mode, all a11y attributes are // removed from the group, and we get the screen reader to read out the group label by mirroring it // inside an invisible element in the option. This is sub-optimal, because the screen reader will // repeat the group label on each navigation, whereas the default pattern only reads the group when // the user enters a new group. The following alternate approaches were considered: // 1. Reading out the group label using the `LiveAnnouncer` solves the problem, but we can't control // when the text will be read out so sometimes it comes in too late or never if the user // navigates quickly. // 2. `('MatOptgroup'); /** * Component that is used to group instances of `mat-option`. */ @Component({ selector: 'mat-optgroup', exportAs: 'matOptgroup', templateUrl: 'optgroup.html', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styleUrl: 'optgroup.css', host: { 'class': 'mat-mdc-optgroup', '[attr.role]': '_inert ? null : "group"', '[attr.aria-disabled]': '_inert ? null : disabled.toString()', '[attr.aria-labelledby]': '_inert ? null : _labelId', }, providers: [{provide: MAT_OPTGROUP, useExisting: MatOptgroup}], }) export class MatOptgroup { /** Label for the option group. */ @Input() label: string; /** whether the option group is disabled. */ @Input({transform: booleanAttribute}) disabled: boolean = false; /** Unique id for the underlying label. */ _labelId: string = inject(_IdGenerator).getId('mat-optgroup-label-'); /** Whether the group is in inert a11y mode. */ _inert: boolean; constructor(...args: unknown[]); constructor() { const parent = inject(MAT_OPTION_PARENT_COMPONENT, {optional: true}); this._inert = parent?.inertGroups ?? false; } }