From 4926cc57eeb74f26c658d97ff0e7078f32535bca Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Thu, 13 Oct 2022 15:09:01 -0700 Subject: [PATCH] fix(material/chips): allow focusing disabled listbox options (#25771) Allow user to focus to disabled listbox options. Unlike other chips, disabled `MatChipOption` remains in the tab order, but it cannot be clicked. This aligns with WAI ARIA documented best practice to allow focusing disabled listbox options. Fix issue where screen reader does not announce disabled item in single selection list. Summary of API and behavior changes: - when disabled, `MatChipOption` sets `aria-disabled="true"` and omits `disabled` attribute. - Add private `@Input _alowFocusWithDisabled` to `MatChipAction` to support focusing the action when it is disabled. - `MatChipSet` defines `_skipPredicate` as an instance member which dervied classes may override. `_alowFocusWithDisabled` and `_skipPredicate` are internal API to the chips. (cherry picked from commit 3f68996722eab1cb4845070fd26d06b1300a616a) --- src/dev-app/chips/chips-demo.html | 2 +- src/material/chips/chip-action.ts | 28 ++++++++++++++++++++++-- src/material/chips/chip-listbox.ts | 18 +++++++++++++++ src/material/chips/chip-option.html | 2 +- src/material/chips/chip-option.ts | 6 +++-- src/material/chips/chip-set.ts | 13 +++++++++-- tools/public_api_guard/material/chips.md | 2 ++ 7 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/dev-app/chips/chips-demo.html b/src/dev-app/chips/chips-demo.html index 5352c6b16c84..48c650a2840f 100644 --- a/src/dev-app/chips/chips-demo.html +++ b/src/dev-app/chips/chips-demo.html @@ -108,7 +108,7 @@

Single selection

Extra Small Small - Medium + Medium Large diff --git a/src/material/chips/chip-action.ts b/src/material/chips/chip-action.ts index 9df34ee8153d..81f194c8e231 100644 --- a/src/material/chips/chip-action.ts +++ b/src/material/chips/chip-action.ts @@ -32,8 +32,8 @@ const _MatChipActionMixinBase = mixinTabIndex(_MatChipActionBase, -1); // in order to avoid some super-specific `:hover` styles from MDC. '[class.mdc-evolution-chip__action--presentational]': '_isPrimary', '[class.mdc-evolution-chip__action--trailing]': '!_isPrimary', - '[attr.tabindex]': '(disabled || !isInteractive) ? null : tabIndex', - '[attr.disabled]': "disabled ? '' : null", + '[attr.tabindex]': '_getTabindex()', + '[attr.disabled]': '_getDisabledAttribute()', '[attr.aria-disabled]': 'disabled', '(click)': '_handleClick($event)', '(keydown)': '_handleKeydown($event)', @@ -56,6 +56,30 @@ export class MatChipAction extends _MatChipActionMixinBase implements HasTabInde } private _disabled = false; + /** + * Private API to allow focusing this chip when it is disabled. + */ + @Input() + private _allowFocusWhenDisabled = false; + + /** + * Determine the value of the disabled attribute for this chip action. + */ + protected _getDisabledAttribute(): string | null { + // When this chip action is disabled and focusing disabled chips is not permitted, return empty + // string to indicate that disabled attribute should be included. + return this.disabled && !this._allowFocusWhenDisabled ? '' : null; + } + + /** + * Determine the value of the tabindex attribute for this chip action. + */ + protected _getTabindex(): string | null { + return (this.disabled && !this._allowFocusWhenDisabled) || !this.isInteractive + ? null + : this.tabIndex.toString(); + } + constructor( public _elementRef: ElementRef, @Inject(MAT_CHIP) diff --git a/src/material/chips/chip-listbox.ts b/src/material/chips/chip-listbox.ts index 2f2effcee1f4..c8135b59b648 100644 --- a/src/material/chips/chip-listbox.ts +++ b/src/material/chips/chip-listbox.ts @@ -7,6 +7,7 @@ */ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; +import {MatChipAction} from './chip-action'; import {TAB} from '@angular/cdk/keycodes'; import { AfterContentInit, @@ -364,4 +365,21 @@ export class MatChipListbox return this.selected; } } + + /** + * Determines if key manager should avoid putting a given chip action in the tab index. Skip + * non-interactive actions since the user can't do anything with them. + */ + protected override _skipPredicate(action: MatChipAction): boolean { + // Override the skip predicate in the base class to avoid skipping disabled chips. Allow + // disabled chip options to receive focus to align with WAI ARIA recommendation. Normally WAI + // ARIA's instructions are to exclude disabled items from the tab order, but it makes a few + // exceptions for compound widgets. + // + // From [Developing a Keyboard Interface]( + // https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/): + // "For the following composite widget elements, keep them focusable when disabled: Options in a + // Listbox..." + return !action.isInteractive; + } } diff --git a/src/material/chips/chip-option.html b/src/material/chips/chip-option.html index 4b04272ce88c..3ea7c065b369 100644 --- a/src/material/chips/chip-option.html +++ b/src/material/chips/chip-option.html @@ -8,7 +8,7 @@