Skip to content

Commit fd2976f

Browse files
authored
feat(material-experimental/menubar): extends components from cdk menu equivalents (#20301)
Extends MatMenuBar and MatMenuBarItem from their CdkMenu equivalent directives and adds MenuBar and MenuItem specific behaviours.
1 parent ebb3998 commit fd2976f

File tree

6 files changed

+211
-3
lines changed

6 files changed

+211
-3
lines changed

src/material-experimental/menubar/BUILD.bazel

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
load("//tools:defaults.bzl", "ng_module", "sass_binary", "sass_library")
1+
load(
2+
"//tools:defaults.bzl",
3+
"ng_module",
4+
"ng_test_library",
5+
"ng_web_test_suite",
6+
"sass_binary",
7+
"sass_library",
8+
)
29

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

@@ -14,6 +21,7 @@ ng_module(
1421
] + glob(["**/*.html"]),
1522
module_name = "@angular/material-experimental/menubar",
1623
deps = [
24+
"//src/cdk-experimental/menu",
1725
"@npm//@angular/core",
1826
],
1927
)
@@ -33,6 +41,26 @@ sass_binary(
3341
src = "menubar-item.scss",
3442
)
3543

44+
ng_test_library(
45+
name = "unit_test_sources",
46+
srcs = glob(
47+
["**/*.spec.ts"],
48+
exclude = ["**/*.e2e.spec.ts"],
49+
),
50+
deps = [
51+
":menubar",
52+
"//src/cdk-experimental/menu",
53+
"//src/cdk/keycodes",
54+
"//src/cdk/testing/private",
55+
"@npm//@angular/platform-browser",
56+
],
57+
)
58+
59+
ng_web_test_suite(
60+
name = "unit_tests",
61+
deps = [":unit_test_sources"],
62+
)
63+
3664
filegroup(
3765
name = "source-files",
3866
srcs = glob(["**/*.ts"]),
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {Component, ElementRef, ViewChild} from '@angular/core';
2+
import {ComponentFixture, async, TestBed} from '@angular/core/testing';
3+
import {CdkMenuItem, CdkMenuModule, CdkMenu} from '@angular/cdk-experimental/menu';
4+
import {MatMenuBarItem} from './menubar-item';
5+
import {MatMenuBarModule} from './menubar-module';
6+
7+
describe('MatMenuBarItem', () => {
8+
let fixture: ComponentFixture<SimpleMenuBarItem>;
9+
let menubarItem: MatMenuBarItem;
10+
let nativeMenubarItem: HTMLElement;
11+
12+
beforeEach(async(() => {
13+
TestBed.configureTestingModule({
14+
imports: [MatMenuBarModule, CdkMenuModule],
15+
declarations: [SimpleMenuBarItem],
16+
}).compileComponents();
17+
}));
18+
19+
beforeEach(() => {
20+
fixture = TestBed.createComponent(SimpleMenuBarItem);
21+
fixture.detectChanges();
22+
23+
menubarItem = fixture.componentInstance.menubarItem;
24+
nativeMenubarItem = fixture.componentInstance.nativeMenubarItem.nativeElement;
25+
});
26+
27+
it('should have the menuitem role', () => {
28+
expect(nativeMenubarItem.getAttribute('role')).toBe('menuitem');
29+
});
30+
31+
it('should be a button type', () => {
32+
expect(nativeMenubarItem.getAttribute('type')).toBe('button');
33+
});
34+
35+
it('should not set the aria-disabled attribute when false', () => {
36+
expect(nativeMenubarItem.hasAttribute('aria.disabled')).toBeFalse();
37+
});
38+
39+
it('should coerce and set aria-disabled attribute', () => {
40+
(menubarItem.disabled as any) = '';
41+
fixture.detectChanges();
42+
43+
expect(nativeMenubarItem.getAttribute('aria-disabled')).toBe('true');
44+
});
45+
46+
it('should have cdk and material classes set', () => {
47+
expect(nativeMenubarItem.classList.contains('cdk-menu-item')).toBeTrue();
48+
expect(nativeMenubarItem.classList.contains('mat-menubar-item')).toBeTrue();
49+
});
50+
51+
it('should open the attached menu on click', () => {
52+
nativeMenubarItem.click();
53+
fixture.detectChanges();
54+
55+
expect(fixture.componentInstance.menu).toBeDefined();
56+
});
57+
58+
it('should have initial tab index set to -1', () => {
59+
expect(nativeMenubarItem.tabIndex).toBe(-1);
60+
});
61+
});
62+
63+
@Component({
64+
template: `
65+
<mat-menubar>
66+
<mat-menubar-item [cdkMenuTriggerFor]="sub">File</mat-menubar-item>
67+
<mat-menubar-item [cdkMenuTriggerFor]="sub">File</mat-menubar-item>
68+
</mat-menubar>
69+
70+
<ng-template cdkMenuPanel #sub="cdkMenuPanel">
71+
<div #menu cdkMenu [cdkMenuPanel]="sub">
72+
<button cdkMenuItem></button>
73+
</div>
74+
</ng-template>
75+
`,
76+
})
77+
class SimpleMenuBarItem {
78+
@ViewChild(CdkMenuItem) menubarItem: MatMenuBarItem;
79+
@ViewChild(CdkMenuItem, {read: ElementRef}) nativeMenubarItem: ElementRef;
80+
81+
@ViewChild('menu') menu: CdkMenu;
82+
}

src/material-experimental/menubar/menubar-item.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core';
10+
import {CdkMenuItem} from '@angular/cdk-experimental/menu';
1011

1112
/**
1213
* A material design MenubarItem adhering to the functionality of CdkMenuItem and
@@ -20,5 +21,13 @@ import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/co
2021
styleUrls: ['menubar-item.css'],
2122
encapsulation: ViewEncapsulation.None,
2223
changeDetection: ChangeDetectionStrategy.OnPush,
24+
host: {
25+
'[tabindex]': '_tabindex',
26+
'type': 'button',
27+
'role': 'menuitem',
28+
'class': 'cdk-menu-item mat-menubar-item',
29+
'[attr.aria-disabled]': 'disabled || null',
30+
},
31+
providers: [{provide: CdkMenuItem, useExisting: MatMenuBarItem}],
2332
})
24-
export class MatMenuBarItem {}
33+
export class MatMenuBarItem extends CdkMenuItem {}

src/material-experimental/menubar/menubar-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10+
import {CdkMenuModule} from '@angular/cdk-experimental/menu';
1011
import {MatMenuBar} from './menubar';
1112
import {MatMenuBarItem} from './menubar-item';
1213

1314
@NgModule({
15+
imports: [CdkMenuModule],
1416
exports: [MatMenuBar, MatMenuBarItem],
1517
declarations: [MatMenuBar, MatMenuBarItem],
1618
})
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {Component, ViewChild, ElementRef} from '@angular/core';
2+
import {RIGHT_ARROW} from '@angular/cdk/keycodes';
3+
import {CdkMenuBar} from '@angular/cdk-experimental/menu';
4+
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
5+
import {dispatchKeyboardEvent} from '@angular/cdk/testing/private';
6+
import {MatMenuBarModule} from './menubar-module';
7+
import {MatMenuBar} from './menubar';
8+
9+
describe('MatMenuBar', () => {
10+
let fixture: ComponentFixture<SimpleMatMenuBar>;
11+
let matMenubar: MatMenuBar;
12+
let nativeMatMenubar: HTMLElement;
13+
14+
beforeEach(async(() => {
15+
TestBed.configureTestingModule({
16+
imports: [MatMenuBarModule],
17+
declarations: [SimpleMatMenuBar],
18+
}).compileComponents();
19+
}));
20+
21+
beforeEach(() => {
22+
fixture = TestBed.createComponent(SimpleMatMenuBar);
23+
fixture.detectChanges();
24+
25+
matMenubar = fixture.componentInstance.matMenubar;
26+
nativeMatMenubar = fixture.componentInstance.nativeMatMenubar.nativeElement;
27+
});
28+
29+
it('should have the menubar role', () => {
30+
expect(nativeMatMenubar.getAttribute('role')).toBe('menubar');
31+
});
32+
33+
it('should have the cdk and material classes set', () => {
34+
expect(nativeMatMenubar.classList.contains('cdk-menu-bar')).toBeTrue();
35+
expect(nativeMatMenubar.classList.contains('mat-menubar')).toBeTrue();
36+
});
37+
38+
it('should have tabindex set to 0', () => {
39+
expect(nativeMatMenubar.getAttribute('tabindex')).toBe('0');
40+
});
41+
42+
it('should toggle aria-orientation attribute', () => {
43+
expect(nativeMatMenubar.getAttribute('aria-orientation')).toBe('horizontal');
44+
45+
matMenubar.orientation = 'vertical';
46+
fixture.detectChanges();
47+
48+
expect(nativeMatMenubar.getAttribute('aria-orientation')).toBe('vertical');
49+
});
50+
51+
it('should toggle focused items on left/right click', () => {
52+
nativeMatMenubar.focus();
53+
54+
expect(document.querySelector(':focus')!.id).toBe('first');
55+
56+
dispatchKeyboardEvent(nativeMatMenubar, 'keydown', RIGHT_ARROW);
57+
fixture.detectChanges();
58+
59+
expect(document.querySelector(':focus')!.id).toBe('second');
60+
});
61+
});
62+
63+
@Component({
64+
template: `
65+
<mat-menubar>
66+
<mat-menubar-item id="first"></mat-menubar-item>
67+
<mat-menubar-item id="second"></mat-menubar-item>
68+
</mat-menubar>
69+
`,
70+
})
71+
class SimpleMatMenuBar {
72+
@ViewChild(CdkMenuBar) matMenubar: MatMenuBar;
73+
@ViewChild(CdkMenuBar, {read: ElementRef}) nativeMatMenubar: ElementRef;
74+
}

src/material-experimental/menubar/menubar.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core';
10+
import {CdkMenuBar, CdkMenuGroup, CDK_MENU, MenuStack} from '@angular/cdk-experimental/menu';
1011

1112
/**
1213
* A material design Menubar adhering to the functionality of CdkMenuBar. MatMenubar
@@ -19,5 +20,17 @@ import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/co
1920
styleUrls: ['menubar.css'],
2021
encapsulation: ViewEncapsulation.None,
2122
changeDetection: ChangeDetectionStrategy.OnPush,
23+
host: {
24+
'role': 'menubar',
25+
'class': 'cdk-menu-bar mat-menubar',
26+
'tabindex': '0',
27+
'[attr.aria-orientation]': 'orientation',
28+
},
29+
providers: [
30+
{provide: CdkMenuGroup, useExisting: MatMenuBar},
31+
{provide: CdkMenuBar, useExisting: MatMenuBar},
32+
{provide: CDK_MENU, useExisting: MatMenuBar},
33+
{provide: MenuStack, useClass: MenuStack},
34+
],
2235
})
23-
export class MatMenuBar {}
36+
export class MatMenuBar extends CdkMenuBar {}

0 commit comments

Comments
 (0)