Skip to content

Commit b3d6d92

Browse files
authored
feat(cdk-experimental/menu): implement menu handling logic (#19701)
* CdkMenu elements should be wrapped with ng-template marked with a CdkMenuPanel directive * Implement logic to open/close attached menus and submenus when triggering the specified MenuItem to which the menu is attached to * In ViewEngine, if a directive is placed on an `ng-template` a child cannot reference it via DI. Therefore when running under VE a reference to the parent CdkMenuPanel must be explicitly passed to the child CdkMenu. Under Ivy it defaults to the injected value.
1 parent e211cab commit b3d6d92

16 files changed

+662
-134
lines changed

src/cdk-experimental/menu/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ ng_module(
1010
),
1111
module_name = "@angular/cdk-experimental/menu",
1212
deps = [
13+
"//src/cdk/bidi",
1314
"//src/cdk/coercion",
1415
"//src/cdk/collections",
16+
"//src/cdk/overlay",
1517
"@npm//@angular/core",
1618
"@npm//rxjs",
1719
],

src/cdk-experimental/menu/menu-bar.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {Directive, Input} from '@angular/core';
1010
import {CdkMenuGroup} from './menu-group';
11+
import {CDK_MENU, Menu} from './menu-interface';
1112

1213
/**
1314
* Directive applied to an element which configures it as a MenuBar by setting the appropriate
@@ -22,9 +23,12 @@ import {CdkMenuGroup} from './menu-group';
2223
'role': 'menubar',
2324
'[attr.aria-orientation]': 'orientation',
2425
},
25-
providers: [{provide: CdkMenuGroup, useExisting: CdkMenuBar}],
26+
providers: [
27+
{provide: CdkMenuGroup, useExisting: CdkMenuBar},
28+
{provide: CDK_MENU, useExisting: CdkMenuBar},
29+
],
2630
})
27-
export class CdkMenuBar extends CdkMenuGroup {
31+
export class CdkMenuBar extends CdkMenuGroup implements Menu {
2832
/**
2933
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
3034
* Does not affect styling/layout.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* Throws an exception when the CdkMenuPanel cannot be injected and the developer did not
11+
* explicitly provide a reference to the enclosing CdkMenuPanel.
12+
* @docs-private
13+
*/
14+
export function throwMissingMenuPanelError() {
15+
throw Error(
16+
'CdkMenu must be placed inside a CdkMenuPanel or a reference to CdkMenuPanel' +
17+
' must be explicitly provided if using ViewEngine'
18+
);
19+
}

src/cdk-experimental/menu/menu-group.spec.ts

Lines changed: 84 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -127,93 +127,102 @@ describe('MenuGroup', () => {
127127

128128
@Component({
129129
template: `
130-
<ul cdkMenu>
131-
<li role="none">
132-
<ul cdkMenuGroup>
133-
<li #first role="none">
134-
<button checked="true" cdkMenuItemCheckbox>
135-
one
136-
</button>
137-
</li>
138-
<li role="none">
139-
<button cdkMenuItemCheckbox>
140-
two
141-
</button>
142-
</li>
143-
</ul>
144-
</li>
145-
</ul>
130+
<ng-template #template cdkMenuPanel #panel="cdkMenuPanel">
131+
<ul cdkMenu [cdkMenuPanel]="panel">
132+
<li role="none">
133+
<ul cdkMenuGroup>
134+
<li #first role="none">
135+
<button checked="true" cdkMenuItemCheckbox>
136+
one
137+
</button>
138+
</li>
139+
<li role="none">
140+
<button cdkMenuItemCheckbox>
141+
two
142+
</button>
143+
</li>
144+
</ul>
145+
</li>
146+
</ul>
147+
</ng-template>
148+
<ng-container *ngTemplateOutlet="template"></ng-container>
146149
`,
147150
})
148151
class CheckboxMenu {}
149152

150153
@Component({
151154
template: `
152-
<ul cdkMenu>
153-
<li role="none">
154-
<ul cdkMenuGroup>
155-
<li role="none">
156-
<button checked="true" cdkMenuItemRadio>
157-
one
158-
</button>
159-
</li>
160-
<li role="none">
161-
<button cdkMenuItemRadio>
162-
two
163-
</button>
164-
</li>
165-
</ul>
166-
</li>
167-
<li role="none">
168-
<ul cdkMenuGroup>
169-
<li role="none">
170-
<button cdkMenuItemRadio>
171-
three
172-
</button>
173-
</li>
174-
<li role="none">
175-
<button cdkMenuItemRadio>
176-
four
177-
</button>
178-
</li>
179-
</ul>
180-
</li>
181-
</ul>
155+
<ng-template #template cdkMenuPanel #panel="cdkMenuPanel">
156+
<ul cdkMenu [cdkMenuPanel]="panel">
157+
<li role="none">
158+
<ul cdkMenuGroup>
159+
<li role="none">
160+
<button checked="true" cdkMenuItemRadio>
161+
one
162+
</button>
163+
</li>
164+
<li role="none">
165+
<button cdkMenuItemRadio>
166+
two
167+
</button>
168+
</li>
169+
</ul>
170+
</li>
171+
<li role="none">
172+
<ul cdkMenuGroup>
173+
<li role="none">
174+
<button cdkMenuItemRadio>
175+
three
176+
</button>
177+
</li>
178+
<li role="none">
179+
<button cdkMenuItemRadio>
180+
four
181+
</button>
182+
</li>
183+
</ul>
184+
</li>
185+
</ul>
186+
</ng-template>
187+
<ng-container *ngTemplateOutlet="template"></ng-container>
182188
`,
183189
})
184190
class MenuWithMultipleRadioGroups {}
185191

186192
@Component({
187193
template: `
188-
<ul cdkMenu>
189-
<li role="none">
190-
<ul cdkMenuGroup>
191-
<li role="none">
192-
<button cdkMenuItemRadio>
193-
one
194-
</button>
195-
</li>
196-
</ul>
197-
</li>
198-
<li role="none">
199-
<ul cdkMenuGroup>
200-
<li role="none">
201-
<button cdkMenuItemRadio>
202-
two
203-
</button>
204-
</li>
205-
</ul>
206-
</li>
207-
<li role="none">
208-
<ul cdkMenuGroup>
209-
<li role="none">
210-
<button cdkMenuItem>
211-
three
212-
</button>
213-
</li>
214-
</ul>
215-
</li>
216-
</ul>
194+
<ng-template #template cdkMenuPanel #panel="cdkMenuPanel">
195+
<ul cdkMenu [cdkMenuPanel]="panel">
196+
<li role="none">
197+
<ul cdkMenuGroup>
198+
<li role="none">
199+
<button cdkMenuItemRadio>
200+
one
201+
</button>
202+
</li>
203+
</ul>
204+
</li>
205+
<li role="none">
206+
<ul cdkMenuGroup>
207+
<li role="none">
208+
<button cdkMenuItemRadio>
209+
two
210+
</button>
211+
</li>
212+
</ul>
213+
</li>
214+
<li role="none">
215+
<ul cdkMenuGroup>
216+
<li role="none">
217+
<button cdkMenuItem>
218+
three
219+
</button>
220+
</li>
221+
</ul>
222+
</li>
223+
</ul>
224+
</ng-template>
225+
<ng-container *ngTemplateOutlet="template"></ng-container>
217226
`,
218227
})
219228
class MenuWithMenuItemsAndRadioGroups {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {InjectionToken} from '@angular/core';
10+
11+
/** Injection token used to return classes implementing the Menu interface */
12+
export const CDK_MENU = new InjectionToken<Menu>('cdk-menu');
13+
14+
/** Interface which specifies Menu operations and used to break circular dependency issues */
15+
export interface Menu {
16+
/** The orientation of the menu */
17+
orientation: 'horizontal' | 'vertical';
18+
}

src/cdk-experimental/menu/menu-item-checkbox.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('MenuItemCheckbox', () => {
4444
});
4545

4646
it('should not have a submenu', () => {
47-
expect(checkbox.hasSubmenu).toBeFalse();
47+
expect(checkbox.hasSubmenu()).toBeFalse();
4848
});
4949

5050
it('should toggle the aria checked attribute', () => {

src/cdk-experimental/menu/menu-item-radio.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('MenuItemRadio', () => {
5757
});
5858

5959
it('should not have a submenu', () => {
60-
expect(radioButton.hasSubmenu).toBeFalse();
60+
expect(radioButton.hasSubmenu()).toBeFalse();
6161
});
6262

6363
it('should not toggle checked state when disabled', () => {

0 commit comments

Comments
 (0)