From 3071ef69cd75d71869b2bcf8c8c99a36dced1b58 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 16 May 2026 09:36:11 +0200 Subject: [PATCH] fix(aria/menu): unable to set softDisabled Fixes the following issues in the Aria menu: 1. The menu didn't have a `softDisabled` input. 2. The menu bar had a `softDisabled` input, but it was being ignored. Fixes #33262. --- goldens/aria/menu/index.api.md | 3 ++- src/aria/menu/menu-bar.ts | 1 - src/aria/menu/menu.spec.ts | 29 ++++++++++++++++++++++++++++- src/aria/menu/menu.ts | 4 +++- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/goldens/aria/menu/index.api.md b/goldens/aria/menu/index.api.md index 71c20bf93c87..f07230c91513 100644 --- a/goldens/aria/menu/index.api.md +++ b/goldens/aria/menu/index.api.md @@ -25,13 +25,14 @@ export class Menu implements OnDestroy { ngOnDestroy(): void; readonly parent: _angular_core.WritableSignal | MenuItem | undefined>; readonly _pattern: MenuPattern; + readonly softDisabled: _angular_core.InputSignalWithTransform; readonly tabIndex: Signal<0 | -1>; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; readonly typeaheadDelay: _angular_core.InputSignal; readonly visible: Signal; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenu]", ["ngMenu"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "expansionDelay": { "alias": "expansionDelay"; "required": false; "isSignal": true; }; }, { "itemSelected": "itemSelected"; }, never, never, true, [{ directive: typeof DeferredContentAware; inputs: { "preserveContent": "preserveContent"; }; outputs: {}; }]>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenu]", ["ngMenu"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "expansionDelay": { "alias": "expansionDelay"; "required": false; "isSignal": true; }; }, { "itemSelected": "itemSelected"; }, never, never, true, [{ directive: typeof DeferredContentAware; inputs: { "preserveContent": "preserveContent"; }; outputs: {}; }]>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } diff --git a/src/aria/menu/menu-bar.ts b/src/aria/menu/menu-bar.ts index 267c9187982a..8c92f1d25996 100644 --- a/src/aria/menu/menu-bar.ts +++ b/src/aria/menu/menu-bar.ts @@ -113,7 +113,6 @@ export class MenuBar implements OnDestroy { ...this, items: this._itemPatterns, multi: () => false, - softDisabled: () => true, focusMode: () => 'roving', orientation: () => 'horizontal', selectionMode: () => 'explicit', diff --git a/src/aria/menu/menu.spec.ts b/src/aria/menu/menu.spec.ts index 5030ec322f6d..0e2bae8a97b4 100644 --- a/src/aria/menu/menu.spec.ts +++ b/src/aria/menu/menu.spec.ts @@ -498,6 +498,32 @@ describe('Standalone Menu Pattern', () => { expect(item?.getAttribute('aria-label')).toBe('Apple item label'); }); + describe('softDisabled', () => { + it('should skip disabled items during navigation when softDisabled is false', () => { + setupMenu(); + fixture.componentInstance.softDisabled.set(false); + fixture.detectChanges(); + + const apple = getItem('Apple')!; + const berries = getItem('Berries'); + + keydown(apple, 'ArrowUp'); + expect(document.activeElement).toBe(berries); + }); + + it('should focus disabled items during navigation when softDisabled is true', () => { + setupMenu(); + fixture.componentInstance.softDisabled.set(true); + fixture.detectChanges(); + + const apple = getItem('Apple')!; + const cherry = getItem('Cherry'); + + keydown(apple!, 'ArrowUp'); + expect(document.activeElement).toBe(cherry); + }); + }); + describe('structural validations', () => { let consoleSpy: jasmine.Spy; @@ -1077,7 +1103,7 @@ describe('Menu Bar Pattern', () => { @Component({ template: ` -
+
{ }) class StandaloneMenuExample { firstItemAriaLabel = signal(null); + softDisabled = signal(true); itemSelected(value: string) {} } diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index fb8d29b815d2..a4b79e76a307 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -114,6 +114,9 @@ export class Menu implements OnDestroy { /** A reference to the parent menu item or menu trigger. */ readonly parent = signal | MenuItem | undefined>(undefined); + /** Whether the menu is soft disabled. */ + readonly softDisabled = input(true, {transform: booleanAttribute}); + /** The menu ui pattern instance. */ readonly _pattern: MenuPattern; @@ -152,7 +155,6 @@ export class Menu implements OnDestroy { parent: computed(() => this.parent()?._pattern), items: this._itemPatterns, multi: () => false, - softDisabled: () => true, focusMode: () => 'roving', orientation: () => 'vertical', selectionMode: () => 'explicit',