Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b17ba55

Browse files
fix(cdk/menu): unable to close child menu with disabled items when using keyboard
Fixes the issue where the child menu remained open when all of its items were disabled.
1 parent 2e466be commit b17ba55

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

‎src/cdk/menu/menu-base.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ export abstract class CdkMenuBase
123123
focusFirstItem(focusOrigin: FocusOrigin = 'program') {
124124
this.keyManager.setFocusOrigin(focusOrigin);
125125
this.keyManager.setFirstItemActive();
126+
127+
// If there's no active item at this point, it means that all the items are disabled.
128+
// Move focus to the menuPanel panel so keyboard events like Escape and closing child menus using arrow keys still works.
129+
// Also, this will give _some_ feedback to screen readers.
130+
if (!this?.nativeElement.contains(document.activeElement)) {
131+
this.nativeElement.focus();
132+
}
126133
}
127134

128135
/**

‎src/cdk/menu/menu.spec.ts‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
waitForAsync,
88
} from '@angular/core/testing';
99
import {Component, ElementRef, QueryList, ViewChild, ViewChildren} from '@angular/core';
10-
import {TAB} from '@angular/cdk/keycodes';
10+
import {LEFT_ARROW,RIGHT_ARROW,TAB} from '@angular/cdk/keycodes';
1111
import {
1212
createMouseEvent,
1313
dispatchEvent,
@@ -135,6 +135,8 @@ describe('Menu', () => {
135135

136136
let nativeShareTrigger: HTMLElement | undefined;
137137

138+
let nativeDownloadTrigger: HTMLElement | undefined;
139+
138140
let nativeMenus: HTMLElement[];
139141

140142
beforeEach(waitForAsync(() => {
@@ -163,6 +165,8 @@ describe('Menu', () => {
163165

164166
nativeShareTrigger = fixture.componentInstance.nativeShareTrigger?.nativeElement;
165167

168+
nativeDownloadTrigger = fixture.componentInstance.nativeDownloadTrigger?.nativeElement;
169+
166170
nativeMenus = fixture.componentInstance.menus.map(m => m.nativeElement);
167171
}
168172

@@ -315,6 +319,20 @@ describe('Menu', () => {
315319
expect(numEnters).toBeGreaterThan(2);
316320
expect(nativeMenus.length).toBe(1);
317321
}));
322+
323+
it('should close the download menu when it is open and right arrow key is pressed', () => {
324+
openFileMenu();
325+
if (nativeDownloadTrigger) {
326+
dispatchKeyboardEvent(nativeDownloadTrigger, 'keydown', RIGHT_ARROW);
327+
detectChanges();
328+
329+
const downloadMenu = nativeMenus[1];
330+
dispatchKeyboardEvent(downloadMenu, 'keydown', LEFT_ARROW);
331+
detectChanges();
332+
333+
expect(nativeMenus.length).toBe(1);
334+
}
335+
});
318336
});
319337

320338
describe('with rtl layout and menu at bottom of page moving up and left', () => {
@@ -327,6 +345,8 @@ describe('Menu', () => {
327345

328346
let nativeShareTrigger: HTMLElement | undefined;
329347

348+
let nativeDownloadTrigger: HTMLElement | undefined;
349+
330350
let nativeMenus: HTMLElement[];
331351

332352
beforeEach(waitForAsync(() => {
@@ -363,6 +383,8 @@ describe('Menu', () => {
363383

364384
nativeShareTrigger = fixture.componentInstance.nativeShareTrigger?.nativeElement;
365385

386+
nativeDownloadTrigger = fixture.componentInstance.nativeDownloadTrigger?.nativeElement;
387+
366388
nativeMenus = fixture.componentInstance.menus.map(m => m.nativeElement);
367389
}
368390

@@ -504,6 +526,20 @@ describe('Menu', () => {
504526
expect(nativeMenus.length).toBe(2);
505527
expect(nativeMenus[1].id).toBe('edit_menu');
506528
}));
529+
530+
it('should close the download menu when it is open and right arrow key is pressed in rtl', () => {
531+
openFileMenu();
532+
if (nativeDownloadTrigger) {
533+
dispatchKeyboardEvent(nativeDownloadTrigger, 'keydown', LEFT_ARROW);
534+
detectChanges();
535+
536+
const downloadMenu = nativeMenus[1];
537+
dispatchKeyboardEvent(downloadMenu, 'keydown', RIGHT_ARROW);
538+
detectChanges();
539+
540+
expect(nativeMenus.length).toBe(1);
541+
}
542+
});
507543
});
508544
});
509545
});
@@ -554,6 +590,7 @@ class InlineMenu {}
554590
>
555591
<button #edit_trigger cdkMenuItem [cdkMenuTriggerFor]="edit">Edit</button>
556592
<button #share_trigger cdkMenuItem [cdkMenuTriggerFor]="share">Share</button>
593+
<button #download_trigger cdkMenuItem [cdkMenuTriggerFor]="download">Download</button>
557594
<button cdkMenuItem>Open</button>
558595
<button cdkMenuItem>Rename</button>
559596
<button cdkMenuItem>Print</button>
@@ -587,12 +624,26 @@ class InlineMenu {}
587624
<button cdkMenuItem>Twitter</button>
588625
</div>
589626
</ng-template>
627+
628+
<ng-template #download>
629+
<div
630+
id="download_menu"
631+
style="display: flex; flex-direction: column;"
632+
cdkMenu
633+
cdkTargetMenuAim
634+
>
635+
<button cdkMenuItem disabled>Via Server 1</button>
636+
<button cdkMenuItem disabled>Via Server 2</button>
637+
</div>
638+
</ng-template>
590639
`,
591640
})
592641
class WithComplexNestedMenus {
593642
@ViewChild('file_trigger', {read: ElementRef}) nativeFileTrigger: ElementRef<HTMLElement>;
594643
@ViewChild('edit_trigger', {read: ElementRef}) nativeEditTrigger?: ElementRef<HTMLElement>;
595644
@ViewChild('share_trigger', {read: ElementRef}) nativeShareTrigger?: ElementRef<HTMLElement>;
645+
@ViewChild('download_trigger', {read: ElementRef})
646+
nativeDownloadTrigger?: ElementRef<HTMLElement>;
596647

597648
@ViewChildren(CdkMenu) menus: QueryList<CdkMenu>;
598649
}
@@ -615,6 +666,7 @@ class WithComplexNestedMenus {
615666
<button cdkMenuItem>Open</button>
616667
<button #share_trigger cdkMenuItem [cdkMenuTriggerFor]="share">Share</button>
617668
<button #edit_trigger cdkMenuItem [cdkMenuTriggerFor]="edit">Edit</button>
669+
<button #download_trigger cdkMenuItem [cdkMenuTriggerFor]="download">Download</button>
618670
</div>
619671
</ng-template>
620672
@@ -646,12 +698,25 @@ class WithComplexNestedMenus {
646698
<button cdkMenuItem>Facebook</button>
647699
</div>
648700
</ng-template>
701+
<ng-template #download>
702+
<div
703+
id="download_menu"
704+
style="display: flex; flex-direction: column;"
705+
cdkMenu
706+
cdkTargetMenuAim
707+
>
708+
<button cdkMenuItem disabled>Via Server 1</button>
709+
<button cdkMenuItem disabled>Via Server 2</button>
710+
</div>
711+
</ng-template>
649712
`,
650713
})
651714
class WithComplexNestedMenusOnBottom {
652715
@ViewChild('file_trigger', {read: ElementRef}) nativeFileTrigger: ElementRef<HTMLElement>;
653716
@ViewChild('edit_trigger', {read: ElementRef}) nativeEditTrigger?: ElementRef<HTMLElement>;
654717
@ViewChild('share_trigger', {read: ElementRef}) nativeShareTrigger?: ElementRef<HTMLElement>;
718+
@ViewChild('download_trigger', {read: ElementRef})
719+
nativeDownloadTrigger?: ElementRef<HTMLElement>;
655720

656721
@ViewChildren(CdkMenu) menus: QueryList<CdkMenu>;
657722
}

‎src/cdk/menu/menu.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,38 @@ export class CdkMenu extends CdkMenuBase implements AfterContentInit, OnDestroy
7474
const keyManager = this.keyManager;
7575
switch (event.keyCode) {
7676
case LEFT_ARROW:
77+
if (!hasModifierKey(event)) {
78+
event.preventDefault();
79+
80+
if (this._parentTrigger && this.nativeElement === document.activeElement) {
81+
if (this.dir?.value !== 'rtl') {
82+
this.menuStack.close(this, {
83+
focusNextOnEmpty: FocusNext.currentItem,
84+
focusParentTrigger: true,
85+
});
86+
}
87+
return;
88+
}
89+
90+
keyManager.setFocusOrigin('keyboard');
91+
keyManager.onKeydown(event);
92+
}
93+
break;
94+
7795
case RIGHT_ARROW:
7896
if (!hasModifierKey(event)) {
7997
event.preventDefault();
98+
99+
if (this._parentTrigger && this.nativeElement === document.activeElement) {
100+
if (this.dir?.value === 'rtl') {
101+
this.menuStack.close(this, {
102+
focusNextOnEmpty: FocusNext.currentItem,
103+
focusParentTrigger: true,
104+
});
105+
}
106+
return;
107+
}
108+
80109
keyManager.setFocusOrigin('keyboard');
81110
keyManager.onKeydown(event);
82111
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /