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 cd532b2

Browse files
fix(cdk/menu): picking up items from child menu (#31684)
Fixes that the CDK menu was picking up items from nested menu instances which in turn were throwing off the keyboard navigation. Fixes #31678.
1 parent 6cbe4b2 commit cd532b2

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

‎goldens/cdk/menu/index.api.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export class CdkMenuBar extends CdkMenuBase implements AfterContentInit {
8484

8585
// @public
8686
export abstract class CdkMenuBase extends CdkMenuGroup implements Menu, AfterContentInit, OnDestroy {
87+
protected _allItems: QueryList<CdkMenuItem>;
8788
protected closeOpenMenu(menu: MenuStackItem, options?: {
8889
focusParentTrigger?: boolean;
8990
}): void;
@@ -110,7 +111,7 @@ export abstract class CdkMenuBase extends CdkMenuGroup implements Menu, AfterCon
110111
setActiveMenuItem(item: number | CdkMenuItem): void;
111112
protected triggerItem?: CdkMenuItem;
112113
// (undocumented)
113-
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkMenuBase, never, never, { "id": { "alias": "id"; "required": false; }; }, {}, ["items"], never, true, never>;
114+
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkMenuBase, never, never, { "id": { "alias": "id"; "required": false; }; }, {}, ["_allItems"], never, true, never>;
114115
// (undocumented)
115116
static ɵfac: i0.ɵɵFactoryDeclaration<CdkMenuBase, never>;
116117
}
@@ -146,6 +147,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
146147
// (undocumented)
147148
protected _ngZone: NgZone;
148149
_onKeydown(event: KeyboardEvent): void;
150+
readonly _parentMenu: Menu | null;
149151
_resetTabIndex(): void;
150152
_setTabIndex(event?: MouseEvent): void;
151153
_tabindex: 0 | -1;

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,15 @@ export abstract class CdkMenuBase
6767
/** The directionality (text direction) of the current page. */
6868
protected readonly dir = inject(Directionality, {optional: true});
6969

70+
/** All items inside the menu, including ones that belong to other menus. */
71+
@ContentChildren(CdkMenuItem, {descendants: true})
72+
protected _allItems: QueryList<CdkMenuItem>;
73+
7074
/** The id of the menu's host element. */
7175
@Input() id: string = inject(_IdGenerator).getId('cdk-menu-');
7276

73-
/** All child MenuItem elements nested in this Menu. */
74-
@ContentChildren(CdkMenuItem, {descendants: true})
75-
readonly items: QueryList<CdkMenuItem>;
77+
/** All child MenuItem elements belonging to this Menu. */
78+
readonly items: QueryList<CdkMenuItem> = new QueryList();
7679

7780
/** The direction items in the menu flow. */
7881
orientation: 'horizontal' | 'vertical' = 'vertical';
@@ -107,6 +110,7 @@ export abstract class CdkMenuBase
107110
if (!this.isInline) {
108111
this.menuStack.push(this);
109112
}
113+
this._setItems();
110114
this._setKeyManager();
111115
this._handleFocus();
112116
this._subscribeToMenuStackHasFocus();
@@ -178,6 +182,18 @@ export abstract class CdkMenuBase
178182
}
179183
}
180184

185+
/** Sets up the subscription that keeps the items list in sync. */
186+
private _setItems() {
187+
// Since the items query has `descendants: true`, we need
188+
// to filter out items belonging to a different menu.
189+
this._allItems.changes
190+
.pipe(startWith(this._allItems), takeUntil(this.destroyed))
191+
.subscribe((items: QueryList<CdkMenuItem>) => {
192+
this.items.reset(items.filter(item => item._parentMenu === this));
193+
this.items.notifyOnChanges();
194+
});
195+
}
196+
181197
/** Setup the FocusKeyManager with the correct orientation for the menu. */
182198
private _setKeyManager() {
183199
this.keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead().withHomeAndEnd();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
6363
private readonly _menuStack = inject(MENU_STACK);
6464

6565
/** The parent menu in which this menuitem resides. */
66-
privatereadonly _parentMenu = inject(CDK_MENU, {optional: true});
66+
readonly _parentMenu = inject(CDK_MENU, {optional: true});
6767

6868
/** Reference to the CdkMenuItemTrigger directive if one is added to the same element */
6969
private readonly _menuTrigger = inject(CdkMenuTrigger, {optional: true, self: true});

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,16 @@ describe('Menu', () => {
499499
expect(document.activeElement).toEqual(nativeMenuItems[2]);
500500
});
501501
});
502+
503+
it('should not pick up items from nested menu', () => {
504+
const getItemsText = (menu: CdkMenu) =>
505+
menu.items.map(i => i._elementRef.nativeElement.textContent?.trim());
506+
const fixture = TestBed.createComponent(NestedMenuDefinition);
507+
fixture.detectChanges();
508+
509+
expect(getItemsText(fixture.componentInstance.root)).toEqual(['One', 'Two']);
510+
expect(getItemsText(fixture.componentInstance.inner)).toEqual(['Three', 'Four', 'Five']);
511+
});
502512
});
503513

504514
@Component({
@@ -667,3 +677,23 @@ class WithComplexNestedMenusOnBottom {
667677
class MenuWithActiveItem {
668678
@ViewChild(CdkMenu) menu: CdkMenu;
669679
}
680+
681+
@Component({
682+
template: `
683+
<div cdkMenu #root>
684+
<button cdkMenuItem>One</button>
685+
<button cdkMenuItem>Two</button>
686+
687+
<div cdkMenu #inner>
688+
<button cdkMenuItem>Three</button>
689+
<button cdkMenuItem>Four</button>
690+
<button cdkMenuItem>Five</button>
691+
</div>
692+
</div>
693+
`,
694+
imports: [CdkMenuModule],
695+
})
696+
class NestedMenuDefinition {
697+
@ViewChild('root', {read: CdkMenu}) root: CdkMenu;
698+
@ViewChild('inner', {read: CdkMenu}) inner: CdkMenu;
699+
}

0 commit comments

Comments
(0)

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