diff --git a/goldens/cdk/menu/index.api.md b/goldens/cdk/menu/index.api.md index 1146342cbced..43399c063e66 100644 --- a/goldens/cdk/menu/index.api.md +++ b/goldens/cdk/menu/index.api.md @@ -84,6 +84,7 @@ export class CdkMenuBar extends CdkMenuBase implements AfterContentInit { // @public export abstract class CdkMenuBase extends CdkMenuGroup implements Menu, AfterContentInit, OnDestroy { + protected _allItems: QueryList; protected closeOpenMenu(menu: MenuStackItem, options?: { focusParentTrigger?: boolean; }): void; @@ -110,7 +111,7 @@ export abstract class CdkMenuBase extends CdkMenuGroup implements Menu, AfterCon setActiveMenuItem(item: number | CdkMenuItem): void; protected triggerItem?: CdkMenuItem; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -146,6 +147,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler, // (undocumented) protected _ngZone: NgZone; _onKeydown(event: KeyboardEvent): void; + readonly _parentMenu: Menu | null; _resetTabIndex(): void; _setTabIndex(event?: MouseEvent): void; _tabindex: 0 | -1; diff --git a/src/cdk/menu/menu-base.ts b/src/cdk/menu/menu-base.ts index 3fbe591977cd..6c050c81868b 100644 --- a/src/cdk/menu/menu-base.ts +++ b/src/cdk/menu/menu-base.ts @@ -67,12 +67,15 @@ export abstract class CdkMenuBase /** The directionality (text direction) of the current page. */ protected readonly dir = inject(Directionality, {optional: true}); + /** All items inside the menu, including ones that belong to other menus. */ + @ContentChildren(CdkMenuItem, {descendants: true}) + protected _allItems: QueryList; + /** The id of the menu's host element. */ @Input() id: string = inject(_IdGenerator).getId('cdk-menu-'); - /** All child MenuItem elements nested in this Menu. */ - @ContentChildren(CdkMenuItem, {descendants: true}) - readonly items: QueryList; + /** All child MenuItem elements belonging to this Menu. */ + readonly items: QueryList = new QueryList(); /** The direction items in the menu flow. */ orientation: 'horizontal' | 'vertical' = 'vertical'; @@ -107,6 +110,7 @@ export abstract class CdkMenuBase if (!this.isInline) { this.menuStack.push(this); } + this._setItems(); this._setKeyManager(); this._handleFocus(); this._subscribeToMenuStackHasFocus(); @@ -178,6 +182,18 @@ export abstract class CdkMenuBase } } + /** Sets up the subscription that keeps the items list in sync. */ + private _setItems() { + // Since the items query has `descendants: true`, we need + // to filter out items belonging to a different menu. + this._allItems.changes + .pipe(startWith(this._allItems), takeUntil(this.destroyed)) + .subscribe((items: QueryList) => { + this.items.reset(items.filter(item => item._parentMenu === this)); + this.items.notifyOnChanges(); + }); + } + /** Setup the FocusKeyManager with the correct orientation for the menu. */ private _setKeyManager() { this.keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead().withHomeAndEnd(); diff --git a/src/cdk/menu/menu-item.ts b/src/cdk/menu/menu-item.ts index 3b81ffb3d740..58ed42e5b5d7 100644 --- a/src/cdk/menu/menu-item.ts +++ b/src/cdk/menu/menu-item.ts @@ -63,7 +63,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler, private readonly _menuStack = inject(MENU_STACK); /** The parent menu in which this menuitem resides. */ - private readonly _parentMenu = inject(CDK_MENU, {optional: true}); + readonly _parentMenu = inject(CDK_MENU, {optional: true}); /** Reference to the CdkMenuItemTrigger directive if one is added to the same element */ private readonly _menuTrigger = inject(CdkMenuTrigger, {optional: true, self: true}); diff --git a/src/cdk/menu/menu.spec.ts b/src/cdk/menu/menu.spec.ts index 27e27ba0daf9..faf38d7255c3 100644 --- a/src/cdk/menu/menu.spec.ts +++ b/src/cdk/menu/menu.spec.ts @@ -499,6 +499,16 @@ describe('Menu', () => { expect(document.activeElement).toEqual(nativeMenuItems[2]); }); }); + + it('should not pick up items from nested menu', () => { + const getItemsText = (menu: CdkMenu) => + menu.items.map(i => i._elementRef.nativeElement.textContent?.trim()); + const fixture = TestBed.createComponent(NestedMenuDefinition); + fixture.detectChanges(); + + expect(getItemsText(fixture.componentInstance.root)).toEqual(['One', 'Two']); + expect(getItemsText(fixture.componentInstance.inner)).toEqual(['Three', 'Four', 'Five']); + }); }); @Component({ @@ -667,3 +677,23 @@ class WithComplexNestedMenusOnBottom { class MenuWithActiveItem { @ViewChild(CdkMenu) menu: CdkMenu; } + +@Component({ + template: ` +
+ + + +
+ + + +
+
+ `, + imports: [CdkMenuModule], +}) +class NestedMenuDefinition { + @ViewChild('root', {read: CdkMenu}) root: CdkMenu; + @ViewChild('inner', {read: CdkMenu}) inner: CdkMenu; +}