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 14 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/** @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/** @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
16 changes: 16 additions & 0 deletions src/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 = "menu",
srcs = glob(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
module_name = "@angular/cdk-experimental/menu",
deps = [
"@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 {
/**
* Orientation of the menu - does not affect styling/layout.
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
*/
@Input('cdkMenuBarOrientation') orientation: 'horizontal' | 'vertical' = 'horizontal';
}
33 changes: 33 additions & 0 deletions src/cdk-experimental/menu/menu-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @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, OnDestroy} from '@angular/core';
import {Subject} from 'rxjs';
import {CdkMenuItem} from './menu-item';

/**
* Directive which provides grouping logic for CdkMenuItem components marked with role
* menuitemradio.
* Siblings within the element are part of the same RadioGroup and behave as such.
*/
@Directive({
selector: '[cdkMenuGroup]',
exportAs: 'cdkMenuGroup',
host: {
'role': 'group',
},
})
export class CdkMenuGroup implements OnDestroy {
/** Emits the element when checkbox or radiobutton state changed */
@Output() change: Subject<CdkMenuItem> = new Subject();

/** Cleanup event emitters */
ngOnDestroy() {
this.change.complete();
}
}
66 changes: 66 additions & 0 deletions src/cdk-experimental/menu/menu-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @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, OnDestroy} from '@angular/core';
import {Subject} from 'rxjs';
import {CdkMenuPanel} from './menu-panel';

/**
* 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]': '_ariaChecked',
},
})
export class CdkMenuItem implements OnDestroy {
/** 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() checked: boolean;

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

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

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

/** Cleanup event emitters */
ngOnDestroy() {
this.opened.complete();
}
}
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;
}
78 changes: 78 additions & 0 deletions src/cdk-experimental/menu/menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
**Warning: this component is still experimental. It may have bugs and the API may change at any
time**

# Menu

The CDK's `MenuModule` provides a set of directives which allow developers to build custom Menus and
MenuBars according the the [MenuBar design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#menu).

## Example

```html
<ul cdkMenuBar>
<li role="none"><button id="file_button" [cdkMenuTriggerFor]="file">File</button></li>
<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>
```

## Directives

### cdkMenuBar

`cdkMenuBar` should be applied to one root MenuBar component which contains a set of `cdkMenuItem`
components. The directive should be applied to a unordered list component. Note that the component is
always visible and is the main interaction point for the user.

### cdkMenu

`cdkMenu` is applied to the sub-menu component(s) which should be opened by an associated `cdkMenuItem`.
`cdkMenu` components should not be nested and they should contain `cdkMenuItem` components or
`cdkMenuGroup` components.

### cdkMenuGroup

`cdkMenuGroup` is used to logically group `cdkMenuItem` components when used with MenuItems which are
a menuitemradio.

`cdkMenuItem` components marked as `menuitemradio` inside of a `cdkMenuGroup` can only have a single
active item and follow the RadioButton and RadioGroup standard pattern.

### cdkMenuItem

`cdkMenuItem` is applied to a component and performs one of the following actions based on the set role:

- If menuitem:
- triggers a submenu, or
- performs some developer provided action
- If menuitemcheckbox
- can be toggled on/off and does not open a submenu
- If menuitemradio
- connected to sibling menuitemradio buttons (group) and does not open a submenu

Further note that menuitemradio and menuitemcheckbox items may be grouped inside of either a `cdkMenu`
or `cdkMenuGroup` as both directives logically group their children.

### cdkMenuPanel

`cdkMenuPanel` is to be applied to an `ng-template` component. It is used to reference the component it wraps (should contain a single `cdkMenu`) and opens it in an overlay.

`cdkMenuPanel` is a top level element and should not be nested within any of the other directives mentioned here.
46 changes: 46 additions & 0 deletions src/cdk-experimental/menu/menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @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, OnDestroy} from '@angular/core';
import {CdkMenuItem} from './menu-item';
import {Subject} from 'rxjs';

/**
* 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 implements OnDestroy {
/**
* Orientation of the menu - does not affect styling/layout.
* Sets the aria-orientation attribute and determines where sub-menus will be opened.
*/
@Input('cdkMenuOrientation') orientation: 'horizontal' | 'vertical' = 'vertical';

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

/** Emits the activated element when checkbox or radiobutton state changed */
@Output() change: Subject<CdkMenuItem>;

/** Cleanup event emitters */
ngOnDestroy() {
this.closed.complete();
this.change.complete();
}
}
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
Loading