Skip to content

Commit c7cadc3

Browse files
authored
feat(cdk-experimental/menu): Add menu skeleton and build scripts (#19583)
* feat(cdk-experimental/menu): Add menu skeleton and build scripts Configure bazel scripts for a cdk menu feature along with the general structure identifying the set of directives which make it up * feat(cdk-experimental/menu): Configure dev-app Configure dev-app for cdk-experimental/menu * build: Allow indirect circular dependency between menu and menu-item A CdkMenuItem opens a CdkMenuPanel and therefore must reference it. A CdkMenuPanel contains a CdkMenu and therefore must reference it. A CdkMenu contains CdkMenuItem's and therefore must reference it. * feat(cdk-experimental/menu): Quote all host keys * feat(cdk-experimental/menu): Add missing aria-attributes * feat(cdk-experimental/menu): Clear up CdkMenuItem comment * feat(cdk-experimental/menu): Remove unused build dep * feat(cdk-experimental/menu): Fix codeowners Move from material-experimental section to cdk-experimental * feat(cdk-experimental/menu): Grammer fix * feat(cdk-experimental/menu): explicitly specify the return when null and boolean * feat(cdk-experimental/menu): Clear up documentation/comments * feat(cdk-experimental/menu): Make cdkMenuOrientation comment more clear * feat(cdk-experimental/menu): Make orientation attribute public for consistency * feat(cdk-experimental/menu): Remove unnecessary comment for role binding * feat(cdk-experimental/menu): Fix orientation attribute on host binding typo * feat(cdk-experimental/menu): Refactor `orientation` property comment for clarity * feat(cdk-experimental/menu): Refactor event emitter types Emitters self complete when used with @output * feat(cdk-experimental/menu): Remove documentation to be added once feature is complete * feat(cdk-experimental/menu): Rename opensMenu method hasSubmenu specifies that it refers to the menu it opens and not its parent * feat(cdk-experimental/menu): Use getter function not property for aria-checked Getter property generates more code than a getter function - prefer to use a function. * feat(cdk-experimental/menu): refactor MenuGroup doc for clarity * feat(cdk-experimental/menu): add @jelbourn to CODEOWNERS for cdk-experimental/menu * feat(cdk-experimental/menu): coerce MenuItem checked state to boolean * feat(cdk-experimental/menu): nit: rename val to value
1 parent bde24ed commit c7cadc3

20 files changed

+344
-0
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
/src/cdk-experimental/* @jelbourn
126126
/src/cdk-experimental/column-resize/** @kseamon @andrewseguin
127127
/src/cdk-experimental/dialog/** @jelbourn @crisbeto
128+
/src/cdk-experimental/menu/** @jelbourn @andy9775
128129
/src/cdk-experimental/popover-edit/** @kseamon @andrewseguin
129130
/src/cdk-experimental/scrolling/** @mmalerba
130131

@@ -141,6 +142,7 @@
141142
/src/dev-app/button-toggle/** @jelbourn
142143
/src/dev-app/button/** @jelbourn
143144
/src/dev-app/card/** @jelbourn
145+
/src/dev-app/cdk-experimental-menu/** @jelbourn @andy9775
144146
/src/dev-app/checkbox/** @jelbourn @devversion
145147
/src/dev-app/chips/** @jelbourn
146148
/src/dev-app/clipboard/** @jelbourn @xkxx

goldens/ts-circular-deps.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
"src/cdk-experimental/dialog/dialog-config.ts",
44
"src/cdk-experimental/dialog/dialog-container.ts"
55
],
6+
[
7+
"src/cdk-experimental/menu/menu-item.ts",
8+
"src/cdk-experimental/menu/menu-panel.ts",
9+
"src/cdk-experimental/menu/menu.ts"
10+
],
611
[
712
"src/cdk-experimental/popover-edit/edit-event-dispatcher.ts",
813
"src/cdk-experimental/popover-edit/edit-ref.ts"

src/cdk-experimental/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
CDK_EXPERIMENTAL_ENTRYPOINTS = [
33
"column-resize",
44
"dialog",
5+
"menu",
56
"popover-edit",
67
"scrolling",
78
]

src/cdk-experimental/menu/BUILD.bazel

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load("//tools:defaults.bzl", "ng_module")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ng_module(
6+
name = "menu",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/cdk-experimental/menu",
12+
deps = [
13+
"//src/cdk/coercion",
14+
"@npm//@angular/core",
15+
"@npm//rxjs",
16+
],
17+
)

src/cdk-experimental/menu/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
export * from './public-api';

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 {Directive, Input} from '@angular/core';
10+
11+
/**
12+
* Directive applied to an element which configures it as a MenuBar by setting the appropriate
13+
* role, aria attributes, and accessable keyboard and mouse handling logic. The component that
14+
* this directive is applied to should contain components marked with CdkMenuItem.
15+
*
16+
*/
17+
@Directive({
18+
selector: '[cdkMenuBar]',
19+
exportAs: 'cdkMenuBar',
20+
host: {
21+
'role': 'menubar',
22+
'[attr.aria-orientation]': 'orientation',
23+
},
24+
})
25+
export class CdkMenuBar {
26+
/**
27+
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
28+
* Does not affect styling/layout.
29+
*/
30+
@Input('cdkMenuBarOrientation') orientation: 'horizontal' | 'vertical' = 'horizontal';
31+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 {Directive, Output, EventEmitter} from '@angular/core';
10+
import {CdkMenuItem} from './menu-item';
11+
12+
/**
13+
* Directive which acts as a grouping container for `CdkMenuItem` instances with
14+
* `role="menuitemradio"`, similar to a `role="radiogroup"` element.
15+
*/
16+
@Directive({
17+
selector: '[cdkMenuGroup]',
18+
exportAs: 'cdkMenuGroup',
19+
host: {
20+
'role': 'group',
21+
},
22+
})
23+
export class CdkMenuGroup {
24+
/** Emits the element when checkbox or radiobutton state changed */
25+
@Output() change: EventEmitter<CdkMenuItem> = new EventEmitter();
26+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 {Directive, Output, Input, EventEmitter} from '@angular/core';
10+
import {CdkMenuPanel} from './menu-panel';
11+
import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion';
12+
13+
/**
14+
* Directive which provides behavior for an element which when clicked:
15+
* If located in a CdkMenuBar:
16+
* - opens up an attached submenu
17+
*
18+
* If located in a CdkMenu/CdkMenuGroup, one of:
19+
* - executes the user defined click handler
20+
* - toggles its checkbox state
21+
* - toggles its radio button state (in relation to siblings)
22+
*
23+
* If it's in a CdkMenu and it triggers a sub-menu, hovering over the
24+
* CdkMenuItem will open the submenu.
25+
*
26+
*/
27+
@Directive({
28+
selector: '[cdkMenuItem], [cdkMenuTriggerFor]',
29+
exportAs: 'cdkMenuItem',
30+
host: {
31+
'type': 'button',
32+
'[attr.role]': 'role',
33+
'[attr.aria-checked]': '_getAriaChecked()',
34+
},
35+
})
36+
export class CdkMenuItem {
37+
/** Template reference variable to the menu this trigger opens */
38+
@Input('cdkMenuTriggerFor') _menuPanel: CdkMenuPanel;
39+
40+
/** ARIA role for the menu item. */
41+
@Input() role: 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem';
42+
43+
/** Whether the checkbox or radiobutton is checked */
44+
@Input()
45+
get checked() {
46+
return this._checked;
47+
}
48+
set checked(value: boolean) {
49+
this._checked = coerceBooleanProperty(value);
50+
}
51+
private _checked = false;
52+
53+
/** Emits when the attached submenu is opened */
54+
@Output() opened: EventEmitter<void> = new EventEmitter();
55+
56+
/** get the aria-checked value only if element is `menuitemradio` or `menuitemcheckbox` */
57+
_getAriaChecked(): boolean | null {
58+
if (this.role === 'menuitem') {
59+
return null;
60+
}
61+
return this.checked;
62+
}
63+
64+
/** Whether the menu item opens a menu */
65+
hasSubmenu() {
66+
return !!this._menuPanel;
67+
}
68+
69+
static ngAcceptInputType_checked: BooleanInput;
70+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 {NgModule} from '@angular/core';
10+
import {CdkMenu} from './menu';
11+
import {CdkMenuBar} from './menu-bar';
12+
import {CdkMenuPanel} from './menu-panel';
13+
import {CdkMenuItem} from './menu-item';
14+
import {CdkMenuGroup} from './menu-group';
15+
16+
const EXPORTED_DECLARATIONS = [CdkMenuBar, CdkMenu, CdkMenuPanel, CdkMenuItem, CdkMenuGroup];
17+
@NgModule({
18+
exports: EXPORTED_DECLARATIONS,
19+
declarations: EXPORTED_DECLARATIONS,
20+
})
21+
export class CdkMenuModule {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 {Directive} from '@angular/core';
10+
import {CdkMenu} from './menu';
11+
12+
/**
13+
* Directive applied to an ng-template which wraps a CdkMenu and provides a reference to the
14+
* child element it wraps which allows for opening of the CdkMenu in an overlay.
15+
*/
16+
@Directive({selector: 'ng-template[cdkMenuPanel]', exportAs: 'cdkMenuPanel'})
17+
export class CdkMenuPanel {
18+
/** Reference to the child menu component */
19+
_menu: CdkMenu;
20+
}

src/cdk-experimental/menu/menu.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 {Directive, Input, Output, EventEmitter} from '@angular/core';
10+
import {CdkMenuItem} from './menu-item';
11+
12+
/**
13+
* Directive which configures the element as a Menu which should contain child elements marked as
14+
* CdkMenuItem or CdkMenuGroup. Sets the appropriate role and aria-attributes for a menu and
15+
* contains accessable keyboard and mouse handling logic.
16+
*
17+
* It also acts as a RadioGroup for elements marked with role `menuitemradio`.
18+
*/
19+
@Directive({
20+
selector: '[cdkMenu]',
21+
exportAs: 'cdkMenu',
22+
host: {
23+
'role': 'menubar',
24+
'[attr.aria-orientation]': 'orientation',
25+
},
26+
})
27+
export class CdkMenu {
28+
/**
29+
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
30+
* Does not affect styling/layout.
31+
*/
32+
@Input('cdkMenuOrientation') orientation: 'horizontal' | 'vertical' = 'vertical';
33+
34+
/** Event emitted when the menu is closed. */
35+
@Output() readonly closed: EventEmitter<void | 'click' | 'tab' | 'escape'> = new EventEmitter();
36+
37+
/** Emits the activated element when checkbox or radiobutton state changed */
38+
@Output() change: EventEmitter<CdkMenuItem>;
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
export * from './menu-module';
10+
export * from './menu-bar';
11+
export * from './menu';
12+
export * from './menu-item';
13+
export * from './menu-panel';
14+
export * from './menu-group';

src/dev-app/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ng_module(
2323
"//src/dev-app/button",
2424
"//src/dev-app/button-toggle",
2525
"//src/dev-app/card",
26+
"//src/dev-app/cdk-experimental-menu",
2627
"//src/dev-app/checkbox",
2728
"//src/dev-app/chips",
2829
"//src/dev-app/clipboard",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("//tools:defaults.bzl", "ng_module")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ng_module(
6+
name = "cdk-experimental-menu",
7+
srcs = glob(["**/*.ts"]),
8+
assets = [
9+
"cdk-menu-demo.html",
10+
"cdk-menu-demo.css",
11+
],
12+
deps = [
13+
"//src/cdk-experimental/menu",
14+
"@npm//@angular/router",
15+
],
16+
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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 {NgModule} from '@angular/core';
10+
import {CommonModule} from '@angular/common';
11+
import {RouterModule} from '@angular/router';
12+
import {CdkMenuModule} from '@angular/cdk-experimental/menu';
13+
14+
import {CdkMenuDemo} from './cdk-menu-demo';
15+
16+
@NgModule({
17+
imports: [
18+
CdkMenuModule,
19+
CommonModule,
20+
RouterModule.forChild([{path: '', component: CdkMenuDemo}]),
21+
],
22+
declarations: [CdkMenuDemo],
23+
})
24+
export class CdkMenuDemoModule {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.example-menu-bar {
2+
display: flex;
3+
flex-direction: row;
4+
list-style: none;
5+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<ul cdkMenuBar class="example-menu-bar">
2+
<li role="none"><button id="file_button" [cdkMenuTriggerFor]="file">File</button></li>
3+
<li role="none"><button id="edit_button" [cdkMenuTriggerFor]="edit">Edit</button></li>
4+
</ul>
5+
6+
<ng-template cdkMenuPanel #file="cdkMenuPanel">
7+
<ul cdkMenu id="file_menu">
8+
<li role="none"><button id="share_button" cdkMenuItem>Share</button></li>
9+
<li role="none"><button id="open_button" cdkMenuItem>Open</button></li>
10+
<li role="none"><button id="rename_button" cdkMenuItem>Rename</button></li>
11+
<li role="none"><button id="print_button" cdkMenuItem>Print</button></li>
12+
</ul>
13+
</ng-template>
14+
15+
<ng-template cdkMenuPanel #edit="cdkMenuPanel">
16+
<ul cdkMenu id="edit_menu">
17+
<li role="none"><button id="undo_button" cdkMenuItem>Undo</button></li>
18+
<li role="none"><button id="redo_button" cdkMenuItem>Redo</button></li>
19+
<li role="none"><button id="cut_button" cdkMenuItem>Cut</button></li>
20+
<li role="none"><button id="copy_button" cdkMenuItem>Copy</button></li>
21+
<li role="none"><button id="paste_button" cdkMenuItem>Paste</button></li>
22+
</ul>
23+
</ng-template>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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 {Component} from '@angular/core';
10+
11+
@Component({
12+
templateUrl: 'cdk-menu-demo.html',
13+
styleUrls: ['cdk-menu-demo.css'],
14+
})
15+
export class CdkMenuDemo {}

src/dev-app/dev-app/dev-app-layout.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export class DevAppLayout {
2929
{name: 'Button Toggle', route: '/button-toggle'},
3030
{name: 'Button', route: '/button'},
3131
{name: 'Card', route: '/card'},
32+
{name: 'Cdk Experimental Menu', route: '/cdk-experimental-menu'},
3233
{name: 'Checkbox', route: '/checkbox'},
3334
{name: 'Chips', route: '/chips'},
3435
{name: 'Clipboard', route: '/clipboard'},

0 commit comments

Comments
 (0)