Skip to content

feat(cdk-experimental/menu): Add menu skeleton and build scripts #19583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b8e838b
feat(cdk-experimental/menu): Add menu skeleton and build scripts
andy9775 Jun 9, 2020
187bc6e
feat(cdk-experimental/menu): Configure dev-app
andy9775 Jun 9, 2020
7b010a9
build: Allow indirect circular dependency between menu and menu-item
andy9775 Jun 10, 2020
5fbb865
feat(cdk-experimental/menu): Quote all host keys
andy9775 Jun 10, 2020
9118b14
feat(cdk-experimental/menu): Add missing aria-attributes
andy9775 Jun 10, 2020
c039f66
feat(cdk-experimental/menu): Clear up CdkMenuItem comment
andy9775 Jun 10, 2020
235da65
feat(cdk-experimental/menu): Remove unused build dep
andy9775 Jun 10, 2020
645f0d7
feat(cdk-experimental/menu): Fix codeowners
andy9775 Jun 10, 2020
2ac2cf3
feat(cdk-experimental/menu): Grammer fix
andy9775 Jun 10, 2020
e4e8c7e
feat(cdk-experimental/menu): explicitly specify the return when null …
andy9775 Jun 10, 2020
0fed70f
feat(cdk-experimental/menu): Clear up documentation/comments
andy9775 Jun 10, 2020
ba75cf5
feat(cdk-experimental/menu): Make cdkMenuOrientation comment more clear
andy9775 Jun 10, 2020
20ea413
feat(cdk-experimental/menu): Make orientation attribute public for co…
andy9775 Jun 10, 2020
dce973c
feat(cdk-experimental/menu): Remove unnecessary comment for role binding
andy9775 Jun 10, 2020
933edf9
feat(cdk-experimental/menu): Fix orientation attribute on host bindin…
andy9775 Jun 11, 2020
a07fe11
feat(cdk-experimental/menu): Refactor `orientation` property comment …
andy9775 Jun 11, 2020
a6dde62
feat(cdk-experimental/menu): Refactor event emitter types
andy9775 Jun 11, 2020
36b4b5e
feat(cdk-experimental/menu): Remove documentation to be added once fe…
andy9775 Jun 11, 2020
5d22b6b
feat(cdk-experimental/menu): Rename opensMenu method
andy9775 Jun 11, 2020
740217d
feat(cdk-experimental/menu): Use getter function not property for
andy9775 Jun 11, 2020
c2c7146
feat(cdk-experimental/menu): refactor MenuGroup doc for clarity
andy9775 Jun 11, 2020
633b573
feat(cdk-experimental/menu): add @jelbourn to CODEOWNERS for cdk-expe…
andy9775 Jun 11, 2020
559adf6
feat(cdk-experimental/menu): coerce MenuItem checked state to boolean
andy9775 Jun 11, 2020
2c9b997
feat(cdk-experimental/menu): nit: rename val to value
andy9775 Jun 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
/src/cdk-experimental/* @jelbourn
/src/cdk-experimental/column-resize/** @kseamon @andrewseguin
/src/cdk-experimental/dialog/** @jelbourn @crisbeto
/src/cdk-experimental/menu/** @jelbourn @andy9775
/src/cdk-experimental/popover-edit/** @kseamon @andrewseguin
/src/cdk-experimental/scrolling/** @mmalerba

Expand All @@ -141,6 +142,7 @@
/src/dev-app/button-toggle/** @jelbourn
/src/dev-app/button/** @jelbourn
/src/dev-app/card/** @jelbourn
/src/dev-app/cdk-experimental-menu/** @jelbourn @andy9775
/src/dev-app/checkbox/** @jelbourn @devversion
/src/dev-app/chips/** @jelbourn
/src/dev-app/clipboard/** @jelbourn @xkxx
Expand Down
5 changes: 5 additions & 0 deletions goldens/ts-circular-deps.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"src/cdk-experimental/dialog/dialog-config.ts",
"src/cdk-experimental/dialog/dialog-container.ts"
],
[
"src/cdk-experimental/menu/menu-item.ts",
"src/cdk-experimental/menu/menu-panel.ts",
"src/cdk-experimental/menu/menu.ts"
],
[
"src/cdk-experimental/popover-edit/edit-event-dispatcher.ts",
"src/cdk-experimental/popover-edit/edit-ref.ts"
Expand Down
1 change: 1 addition & 0 deletions src/cdk-experimental/config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
CDK_EXPERIMENTAL_ENTRYPOINTS = [
"column-resize",
"dialog",
"menu",
"popover-edit",
"scrolling",
]
Expand Down
17 changes: 17 additions & 0 deletions src/cdk-experimental/menu/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("//tools:defaults.bzl", "ng_module")

package(default_visibility = ["//visibility:public"])

ng_module(
name = "menu",
srcs = glob(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
module_name = "@angular/cdk-experimental/menu",
deps = [
"//src/cdk/coercion",
"@npm//@angular/core",
"@npm//rxjs",
],
)
9 changes: 9 additions & 0 deletions src/cdk-experimental/menu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export * from './public-api';
31 changes: 31 additions & 0 deletions src/cdk-experimental/menu/menu-bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, Input} from '@angular/core';

/**
* Directive applied to an element which configures it as a MenuBar by setting the appropriate
* role, aria attributes, and accessable keyboard and mouse handling logic. The component that
* this directive is applied to should contain components marked with CdkMenuItem.
*
*/
@Directive({
selector: '[cdkMenuBar]',
exportAs: 'cdkMenuBar',
host: {
'role': 'menubar',
'[attr.aria-orientation]': 'orientation',
},
})
export class CdkMenuBar {
/**
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
* Does not affect styling/layout.
*/
@Input('cdkMenuBarOrientation') orientation: 'horizontal' | 'vertical' = 'horizontal';
}
26 changes: 26 additions & 0 deletions src/cdk-experimental/menu/menu-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, Output, EventEmitter} from '@angular/core';
import {CdkMenuItem} from './menu-item';

/**
* Directive which acts as a grouping container for `CdkMenuItem` instances with
* `role="menuitemradio"`, similar to a `role="radiogroup"` element.
*/
@Directive({
selector: '[cdkMenuGroup]',
exportAs: 'cdkMenuGroup',
host: {
'role': 'group',
},
})
export class CdkMenuGroup {
/** Emits the element when checkbox or radiobutton state changed */
@Output() change: EventEmitter<CdkMenuItem> = new EventEmitter();
}
70 changes: 70 additions & 0 deletions src/cdk-experimental/menu/menu-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, Output, Input, EventEmitter} from '@angular/core';
import {CdkMenuPanel} from './menu-panel';
import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion';

/**
* Directive which provides behavior for an element which when clicked:
* If located in a CdkMenuBar:
* - opens up an attached submenu
*
* If located in a CdkMenu/CdkMenuGroup, one of:
* - executes the user defined click handler
* - toggles its checkbox state
* - toggles its radio button state (in relation to siblings)
*
* If it's in a CdkMenu and it triggers a sub-menu, hovering over the
* CdkMenuItem will open the submenu.
*
*/
@Directive({
selector: '[cdkMenuItem], [cdkMenuTriggerFor]',
exportAs: 'cdkMenuItem',
host: {
'type': 'button',
'[attr.role]': 'role',
'[attr.aria-checked]': '_getAriaChecked()',
},
})
export class CdkMenuItem {
/** Template reference variable to the menu this trigger opens */
@Input('cdkMenuTriggerFor') _menuPanel: CdkMenuPanel;

/** ARIA role for the menu item. */
@Input() role: 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this in code, I would probably lean towards having separate classes for CdkMenuItem, CdkMenuItemCheckbox, and CdkMenuItemRadio. My thinking:

  • It would be more concise for the user (not having to manually set the role)
  • Adding a role property might be confusing since people are accustomed to using attr.role, which wouldn't work here
  • It would simplify the aria-checked bit for each directive


/** Whether the checkbox or radiobutton is checked */
@Input()
get checked() {
return this._checked;
}
set checked(value: boolean) {
this._checked = coerceBooleanProperty(value);
}
private _checked = false;

/** Emits when the attached submenu is opened */
@Output() opened: EventEmitter<void> = new EventEmitter();

/** get the aria-checked value only if element is `menuitemradio` or `menuitemcheckbox` */
_getAriaChecked(): boolean | null {
if (this.role === 'menuitem') {
return null;
}
return this.checked;
}

/** Whether the menu item opens a menu */
hasSubmenu() {
return !!this._menuPanel;
}

static ngAcceptInputType_checked: BooleanInput;
}
21 changes: 21 additions & 0 deletions src/cdk-experimental/menu/menu-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NgModule} from '@angular/core';
import {CdkMenu} from './menu';
import {CdkMenuBar} from './menu-bar';
import {CdkMenuPanel} from './menu-panel';
import {CdkMenuItem} from './menu-item';
import {CdkMenuGroup} from './menu-group';

const EXPORTED_DECLARATIONS = [CdkMenuBar, CdkMenu, CdkMenuPanel, CdkMenuItem, CdkMenuGroup];
@NgModule({
exports: EXPORTED_DECLARATIONS,
declarations: EXPORTED_DECLARATIONS,
})
export class CdkMenuModule {}
20 changes: 20 additions & 0 deletions src/cdk-experimental/menu/menu-panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Directive} from '@angular/core';
import {CdkMenu} from './menu';

/**
* Directive applied to an ng-template which wraps a CdkMenu and provides a reference to the
* child element it wraps which allows for opening of the CdkMenu in an overlay.
*/
@Directive({selector: 'ng-template[cdkMenuPanel]', exportAs: 'cdkMenuPanel'})
export class CdkMenuPanel {
/** Reference to the child menu component */
_menu: CdkMenu;
}
39 changes: 39 additions & 0 deletions src/cdk-experimental/menu/menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, Input, Output, EventEmitter} from '@angular/core';
import {CdkMenuItem} from './menu-item';

/**
* Directive which configures the element as a Menu which should contain child elements marked as
* CdkMenuItem or CdkMenuGroup. Sets the appropriate role and aria-attributes for a menu and
* contains accessable keyboard and mouse handling logic.
*
* It also acts as a RadioGroup for elements marked with role `menuitemradio`.
*/
@Directive({
selector: '[cdkMenu]',
exportAs: 'cdkMenu',
host: {
'role': 'menubar',
'[attr.aria-orientation]': 'orientation',
},
})
export class CdkMenu {
/**
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
* Does not affect styling/layout.
*/
@Input('cdkMenuOrientation') orientation: 'horizontal' | 'vertical' = 'vertical';

/** Event emitted when the menu is closed. */
@Output() readonly closed: EventEmitter<void | 'click' | 'tab' | 'escape'> = new EventEmitter();

/** Emits the activated element when checkbox or radiobutton state changed */
@Output() change: EventEmitter<CdkMenuItem>;
}
14 changes: 14 additions & 0 deletions src/cdk-experimental/menu/public-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export * from './menu-module';
export * from './menu-bar';
export * from './menu';
export * from './menu-item';
export * from './menu-panel';
export * from './menu-group';
1 change: 1 addition & 0 deletions src/dev-app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ng_module(
"//src/dev-app/button",
"//src/dev-app/button-toggle",
"//src/dev-app/card",
"//src/dev-app/cdk-experimental-menu",
"//src/dev-app/checkbox",
"//src/dev-app/chips",
"//src/dev-app/clipboard",
Expand Down
16 changes: 16 additions & 0 deletions src/dev-app/cdk-experimental-menu/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("//tools:defaults.bzl", "ng_module")

package(default_visibility = ["//visibility:public"])

ng_module(
name = "cdk-experimental-menu",
srcs = glob(["**/*.ts"]),
assets = [
"cdk-menu-demo.html",
"cdk-menu-demo.css",
],
deps = [
"//src/cdk-experimental/menu",
"@npm//@angular/router",
],
)
24 changes: 24 additions & 0 deletions src/dev-app/cdk-experimental-menu/cdk-menu-demo-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router';
import {CdkMenuModule} from '@angular/cdk-experimental/menu';

import {CdkMenuDemo} from './cdk-menu-demo';

@NgModule({
imports: [
CdkMenuModule,
CommonModule,
RouterModule.forChild([{path: '', component: CdkMenuDemo}]),
],
declarations: [CdkMenuDemo],
})
export class CdkMenuDemoModule {}
5 changes: 5 additions & 0 deletions src/dev-app/cdk-experimental-menu/cdk-menu-demo.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.example-menu-bar {
display: flex;
flex-direction: row;
list-style: none;
}
23 changes: 23 additions & 0 deletions src/dev-app/cdk-experimental-menu/cdk-menu-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<ul cdkMenuBar class="example-menu-bar">
<li role="none"><button id="file_button" [cdkMenuTriggerFor]="file">File</button></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this PR, but at some point I want to sit down and think about the way we're using lists with the menu and see if we could do something simpler

<li role="none"><button id="edit_button" [cdkMenuTriggerFor]="edit">Edit</button></li>
</ul>

<ng-template cdkMenuPanel #file="cdkMenuPanel">
<ul cdkMenu id="file_menu">
<li role="none"><button id="share_button" cdkMenuItem>Share</button></li>
<li role="none"><button id="open_button" cdkMenuItem>Open</button></li>
<li role="none"><button id="rename_button" cdkMenuItem>Rename</button></li>
<li role="none"><button id="print_button" cdkMenuItem>Print</button></li>
</ul>
</ng-template>

<ng-template cdkMenuPanel #edit="cdkMenuPanel">
<ul cdkMenu id="edit_menu">
<li role="none"><button id="undo_button" cdkMenuItem>Undo</button></li>
<li role="none"><button id="redo_button" cdkMenuItem>Redo</button></li>
<li role="none"><button id="cut_button" cdkMenuItem>Cut</button></li>
<li role="none"><button id="copy_button" cdkMenuItem>Copy</button></li>
<li role="none"><button id="paste_button" cdkMenuItem>Paste</button></li>
</ul>
</ng-template>
15 changes: 15 additions & 0 deletions src/dev-app/cdk-experimental-menu/cdk-menu-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Component} from '@angular/core';

@Component({
templateUrl: 'cdk-menu-demo.html',
styleUrls: ['cdk-menu-demo.css'],
})
export class CdkMenuDemo {}
1 change: 1 addition & 0 deletions src/dev-app/dev-app/dev-app-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class DevAppLayout {
{name: 'Button Toggle', route: '/button-toggle'},
{name: 'Button', route: '/button'},
{name: 'Card', route: '/card'},
{name: 'Cdk Experimental Menu', route: '/cdk-experimental-menu'},
{name: 'Checkbox', route: '/checkbox'},
{name: 'Chips', route: '/chips'},
{name: 'Clipboard', route: '/clipboard'},
Expand Down
Loading