From 77ca73d0772cbaa0113b31901404ddc9c3cfd976 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Fri, 12 Jul 2024 09:40:44 -0700 Subject: [PATCH] feat(chip): replace `always-focusable` attribute with new `soft-disabled` attribute PiperOrigin-RevId: 651801616 --- chips/demo/stories.ts | 20 ++++++++------------ chips/internal/_shared.scss | 4 ++-- chips/internal/assist-chip.ts | 7 ++++--- chips/internal/chip-set.ts | 8 ++++---- chips/internal/chip-set_test.ts | 5 ++--- chips/internal/chip.ts | 17 ++++++++--------- chips/internal/filter-chip.ts | 7 ++++--- chips/internal/input-chip.ts | 9 +++++---- chips/internal/multi-action-chip.ts | 3 +-- chips/internal/trailing-icons.ts | 2 +- testing/templates.ts | 1 + 11 files changed, 40 insertions(+), 43 deletions(-) diff --git a/chips/demo/stories.ts b/chips/demo/stories.ts index 98e0a68fb7..16b9eca78e 100644 --- a/chips/demo/stories.ts +++ b/chips/demo/stories.ts @@ -68,9 +68,8 @@ const assist: MaterialStoryInit = { >${GOOGLE_LOGO} `; @@ -100,9 +99,8 @@ const filters: MaterialStoryInit = { ?elevated=${elevated} removable> @@ -144,9 +142,8 @@ const inputs: MaterialStoryInit = { ?disabled=${disabled} remove-only> + label=${label || 'Soft-disabled input chip (focusable)'} + soft-disabled> `; }, @@ -177,9 +174,8 @@ const suggestions: MaterialStoryInit = { >${GOOGLE_LOGO} `; diff --git a/chips/internal/_shared.scss b/chips/internal/_shared.scss index fd5489f06a..4c73665721 100644 --- a/chips/internal/_shared.scss +++ b/chips/internal/_shared.scss @@ -33,7 +33,7 @@ ); } - :host([disabled]) { + :host(:is([disabled], [soft-disabled])) { pointer-events: none; } @@ -242,7 +242,7 @@ } a, - button:not(:disabled) { + button:not(:disabled, [aria-disabled='true']) { cursor: inherit; } } diff --git a/chips/internal/assist-chip.ts b/chips/internal/assist-chip.ts index d6dcf7ed8f..9502d22af2 100644 --- a/chips/internal/assist-chip.ts +++ b/chips/internal/assist-chip.ts @@ -27,14 +27,14 @@ export class AssistChip extends Chip { protected override get rippleDisabled() { // Link chips cannot be disabled - return !this.href && this.disabled; + return !this.href && (this.disabled || this.softDisabled); } protected override getContainerClasses() { return { ...super.getContainerClasses(), // Link chips cannot be disabled - disabled: !this.href && this.disabled, + disabled: !this.href && (this.disabled || this.softDisabled), elevated: this.elevated, link: !!this.href, }; @@ -60,7 +60,8 @@ export class AssistChip extends Chip { class="primary action" id="button" aria-label=${ariaLabel || nothing} - ?disabled=${this.disabled && !this.alwaysFocusable} + aria-disabled=${this.softDisabled || nothing} + ?disabled=${this.disabled} type="button" >${content} diff --git a/chips/internal/chip-set.ts b/chips/internal/chip-set.ts index 2a08199262..11fb401f7d 100644 --- a/chips/internal/chip-set.ts +++ b/chips/internal/chip-set.ts @@ -93,10 +93,10 @@ export class ChipSet extends LitElement { // Check if the next sibling is disabled. If so, // move the index and continue searching. // - // Some toolbar items may be focusable when disabled for increased - // visibility. + // Some toolbar items may be soft-disabled (disabled but focusable + // increased visibility. const nextChip = chips[nextIndex]; - if (nextChip.disabled && !nextChip.alwaysFocusable) { + if (nextChip.disabled) { if (forwards) { nextIndex++; } else { @@ -118,7 +118,7 @@ export class ChipSet extends LitElement { const {chips} = this; let chipToFocus: Chip | undefined; for (const chip of chips) { - const isChipFocusable = chip.alwaysFocusable || !chip.disabled; + const isChipFocusable = !chip.disabled; const chipIsFocused = chip.matches(':focus-within'); if (chipIsFocused && isChipFocusable) { // Found the first chip that is actively focused. This overrides the diff --git a/chips/internal/chip-set_test.ts b/chips/internal/chip-set_test.ts index 69379512a1..ae6a67695e 100644 --- a/chips/internal/chip-set_test.ts +++ b/chips/internal/chip-set_test.ts @@ -204,11 +204,10 @@ describe('Chip set', () => { }); }); - it('should NOT skip over disabled always focusable chips', async () => { + it('should NOT skip over soft-disabled chips', async () => { const first = new TestAssistChip(); const second = new TestAssistChip(); - second.disabled = true; - second.alwaysFocusable = true; + second.softDisabled = true; const third = new TestAssistChip(); const chipSet = await setupTest([first, second, third]); await testNavigation({ diff --git a/chips/internal/chip.ts b/chips/internal/chip.ts index a5ad833e1b..4975828632 100644 --- a/chips/internal/chip.ts +++ b/chips/internal/chip.ts @@ -30,20 +30,19 @@ export abstract class Chip extends chipBaseClass { /** * Whether or not the chip is disabled. - * - * Disabled chips are not focusable, unless `always-focusable` is set. */ @property({type: Boolean, reflect: true}) disabled = false; /** - * When true, allow disabled chips to be focused with arrow keys. + * Whether or not the chip is "soft-disabled" (disabled but still + * focusable). * - * Add this when a chip needs increased visibility when disabled. See + * Use this when a chip needs increased visibility when disabled. See * https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_disabled_controls * for more guidance on when this is needed. */ - @property({type: Boolean, attribute: 'always-focusable'}) - alwaysFocusable = false; + @property({type: Boolean, attribute: 'soft-disabled', reflect: true}) + softDisabled = false; /** * The label of the chip. @@ -70,11 +69,11 @@ export abstract class Chip extends chipBaseClass { * Some chip actions such as links cannot be disabled. */ protected get rippleDisabled() { - return this.disabled; + return this.disabled || this.softDisabled; } override focus(options?: FocusOptions) { - if (this.disabled && !this.alwaysFocusable) { + if (this.disabled) { return; } @@ -97,7 +96,7 @@ export abstract class Chip extends chipBaseClass { protected getContainerClasses(): ClassInfo { return { - 'disabled': this.disabled, + 'disabled': this.disabled || this.softDisabled, 'has-icon': this.hasIcon, }; } diff --git a/chips/internal/filter-chip.ts b/chips/internal/filter-chip.ts index bd0f07922d..d2df608118 100644 --- a/chips/internal/filter-chip.ts +++ b/chips/internal/filter-chip.ts @@ -61,7 +61,8 @@ export class FilterChip extends MultiActionChip { id="button" aria-label=${ariaLabel || nothing} aria-pressed=${this.selected} - ?disabled=${this.disabled && !this.alwaysFocusable} + aria-disabled=${this.softDisabled || nothing} + ?disabled=${this.disabled} @click=${this.handleClick} >${content} @@ -88,7 +89,7 @@ export class FilterChip extends MultiActionChip { return renderRemoveButton({ focusListener, ariaLabel: this.ariaLabelRemove, - disabled: this.disabled, + disabled: this.disabled || this.softDisabled, }); } @@ -104,7 +105,7 @@ export class FilterChip extends MultiActionChip { } private handleClick(event: MouseEvent) { - if (this.disabled) { + if (this.disabled || this.softDisabled) { return; } diff --git a/chips/internal/input-chip.ts b/chips/internal/input-chip.ts index 16616e9693..a65df5631b 100644 --- a/chips/internal/input-chip.ts +++ b/chips/internal/input-chip.ts @@ -38,7 +38,7 @@ export class InputChip extends MultiActionChip { protected override get rippleDisabled() { // Link chips cannot be disabled - return !this.href && this.disabled; + return !this.href && (this.disabled || this.softDisabled); } protected get primaryAction() { @@ -59,7 +59,7 @@ export class InputChip extends MultiActionChip { ...super.getContainerClasses(), avatar: this.avatar, // Link chips cannot be disabled - disabled: !this.href && this.disabled, + disabled: !this.href && (this.disabled || this.softDisabled), link: !!this.href, selected: this.selected, 'has-trailing': true, @@ -94,7 +94,8 @@ export class InputChip extends MultiActionChip { class="primary action" id="button" aria-label=${ariaLabel || nothing} - ?disabled=${this.disabled && !this.alwaysFocusable} + aria-disabled=${this.softDisabled || nothing} + ?disabled=${this.disabled} type="button" >${content} @@ -105,7 +106,7 @@ export class InputChip extends MultiActionChip { return renderRemoveButton({ focusListener, ariaLabel: this.ariaLabelRemove, - disabled: !this.href && this.disabled, + disabled: !this.href && (this.disabled || this.softDisabled), tabbable: this.removeOnly, }); } diff --git a/chips/internal/multi-action-chip.ts b/chips/internal/multi-action-chip.ts index 21310c82db..a1f1bb3917 100644 --- a/chips/internal/multi-action-chip.ts +++ b/chips/internal/multi-action-chip.ts @@ -51,8 +51,7 @@ export abstract class MultiActionChip extends Chip { } override focus(options?: FocusOptions & {trailing?: boolean}) { - const isFocusable = this.alwaysFocusable || !this.disabled; - if (isFocusable && options?.trailing && this.trailingAction) { + if (!this.disabled && options?.trailing && this.trailingAction) { this.trailingAction.focus(options); return; } diff --git a/chips/internal/trailing-icons.ts b/chips/internal/trailing-icons.ts index bd5d147301..76cd7a1f07 100644 --- a/chips/internal/trailing-icons.ts +++ b/chips/internal/trailing-icons.ts @@ -48,7 +48,7 @@ export function renderRemoveButton({ } function handleRemoveClick(this: Chip, event: Event) { - if (this.disabled) { + if (this.disabled || this.softDisabled) { return; } diff --git a/testing/templates.ts b/testing/templates.ts index 5daeb44458..769489ead4 100644 --- a/testing/templates.ts +++ b/testing/templates.ts @@ -24,6 +24,7 @@ export enum State { HOVER = 'Hover', PRESSED = 'Pressed', SELECTED = 'Selected', + SOFT_DISABLED = 'Soft disabled', } /**