Skip to content

Commit 4b7060c

Browse files
committed
fixup! fix(cdk-experimental/menu): API, code, and docs cleanup pass
1 parent 7148d2a commit 4b7060c

File tree

8 files changed

+113
-72
lines changed

8 files changed

+113
-72
lines changed

src/cdk-experimental/menu/context-menu-trigger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ export class CdkContextMenuTrigger extends CdkMenuTriggerBase implements OnDestr
212212
private _open(coordinates: ContextMenuCoordinates, ignoreFirstOutsideAuxClick: boolean) {
213213
if (this.disabled) {
214214
return;
215-
} else if (this.isOpen()) {
215+
}
216+
if (this.isOpen()) {
216217
// since we're moving this menu we need to close any submenus first otherwise they end up
217218
// disconnected from this one.
218219
this.menuStack.closeSubMenuOf(this.childMenu!);

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

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ import {
1616
Self,
1717
} from '@angular/core';
1818
import {Directionality} from '@angular/cdk/bidi';
19-
import {DOWN_ARROW, ESCAPE, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes';
19+
import {
20+
DOWN_ARROW,
21+
ESCAPE,
22+
hasModifierKey,
23+
LEFT_ARROW,
24+
RIGHT_ARROW,
25+
TAB,
26+
UP_ARROW,
27+
} from '@angular/cdk/keycodes';
2028
import {takeUntil} from 'rxjs/operators';
2129
import {CdkMenuGroup} from './menu-group';
2230
import {CDK_MENU} from './menu-interface';
@@ -82,31 +90,37 @@ export class CdkMenuBar extends CdkMenuBase implements AfterContentInit {
8290
case DOWN_ARROW:
8391
case LEFT_ARROW:
8492
case RIGHT_ARROW:
85-
const horizontalArrows = event.keyCode === LEFT_ARROW || event.keyCode === RIGHT_ARROW;
86-
// For a horizontal menu if the left/right keys were clicked, or a vertical menu if the
87-
// up/down keys were clicked: if the current menu is open, close it then focus and open the
88-
// next menu.
89-
if (horizontalArrows) {
90-
event.preventDefault();
93+
if (!hasModifierKey(event)) {
94+
const horizontalArrows = event.keyCode === LEFT_ARROW || event.keyCode === RIGHT_ARROW;
95+
// For a horizontal menu if the left/right keys were clicked, or a vertical menu if the
96+
// up/down keys were clicked: if the current menu is open, close it then focus and open the
97+
// next menu.
98+
if (horizontalArrows) {
99+
event.preventDefault();
91100

92-
const prevIsOpen = keyManager.activeItem?.isMenuOpen();
93-
keyManager.activeItem?.getMenuTrigger()?.close();
101+
const prevIsOpen = keyManager.activeItem?.isMenuOpen();
102+
keyManager.activeItem?.getMenuTrigger()?.close();
94103

95-
keyManager.setFocusOrigin('keyboard');
96-
keyManager.onKeydown(event);
97-
if (prevIsOpen) {
98-
keyManager.activeItem?.getMenuTrigger()?.open();
104+
keyManager.setFocusOrigin('keyboard');
105+
keyManager.onKeydown(event);
106+
if (prevIsOpen) {
107+
keyManager.activeItem?.getMenuTrigger()?.open();
108+
}
99109
}
100110
}
101111
break;
102112

103113
case ESCAPE:
104-
event.preventDefault();
105-
keyManager.activeItem?.getMenuTrigger()?.close();
114+
if (!hasModifierKey(event)) {
115+
event.preventDefault();
116+
keyManager.activeItem?.getMenuTrigger()?.close();
117+
}
106118
break;
107119

108120
case TAB:
109-
keyManager.activeItem?.getMenuTrigger()?.close();
121+
if (!hasModifierKey(event, 'altKey', 'metaKey', 'ctrlKey')) {
122+
keyManager.activeItem?.getMenuTrigger()?.close();
123+
}
110124
break;
111125

112126
default:

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ export abstract class CdkMenuBase
7070
/** The menu's native DOM host element. */
7171
readonly nativeElement: HTMLElement;
7272

73-
/** Whether this menu's menu stack has focus. */
74-
_tabIndex: number | null;
73+
/** The tabindex for this menu. */
74+
get _tabIndex() {
75+
const tabindexIfInline = this._menuStackHasFocus ? -1 : 0;
76+
return this.isInline ? tabindexIfInline : null;
77+
}
7578

7679
/** Handles keyboard events for the menu. */
7780
protected keyManager: FocusKeyManager<CdkMenuItem>;
@@ -85,6 +88,9 @@ export abstract class CdkMenuBase
8588
/** Tracks the users mouse movements over the menu. */
8689
protected pointerTracker?: PointerFocusTracker<CdkMenuItem>;
8790

91+
/** Whether this menu's menu stack has focus. */
92+
private _menuStackHasFocus = false;
93+
8894
protected constructor(
8995
/** The host element. */
9096
elementRef: ElementRef<HTMLElement>,
@@ -105,7 +111,6 @@ export abstract class CdkMenuBase
105111
if (!this.isInline) {
106112
this.menuStack.push(this);
107113
}
108-
this._calculateTabIndex(false);
109114
this._setKeyManager();
110115
this._subscribeToMenuStackHasFocus();
111116
this._subscribeToMenuOpen();
@@ -207,17 +212,11 @@ export abstract class CdkMenuBase
207212
private _subscribeToMenuStackHasFocus() {
208213
if (this.isInline) {
209214
this.menuStack.hasFocus.pipe(takeUntil(this.destroyed)).subscribe(hasFocus => {
210-
this._calculateTabIndex(hasFocus);
215+
this._menuStackHasFocus = hasFocus;
211216
});
212217
}
213218
}
214219

215-
/** Calculate the tabindex for the menu host element. */
216-
private _calculateTabIndex(menuStackHasFocus: boolean) {
217-
const tabindexIfInline = menuStackHasFocus ? -1 : 0;
218-
this._tabIndex = this.isInline ? tabindexIfInline : null;
219-
}
220-
221220
/**
222221
* Set the PointerFocusTracker and ensure that when mouse focus changes the key manager is updated
223222
* with the latest menu item under mouse focus.

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('MenuItem', () => {
9898
it('should get the text for menu item with a single nested mat icon component', () => {
9999
const fixture = createComponent(MenuItemWithIcon);
100100
expect(menuItem.getLabel()).toEqual('unicorn Click me!');
101-
fixture.componentInstance.typeahead = 'Click me!';
101+
fixture.componentInstance.typeaheadLabel = 'Click me!';
102102
fixture.detectChanges();
103103
expect(menuItem.getLabel()).toEqual('Click me!');
104104
});
@@ -109,7 +109,7 @@ describe('MenuItem', () => {
109109
() => {
110110
const fixture = createComponent(MenuItemWithIconClass);
111111
expect(menuItem.getLabel()).toEqual('unicorn Click me!');
112-
fixture.componentInstance.typeahead = 'Click me!';
112+
fixture.componentInstance.typeaheadLabel = 'Click me!';
113113
fixture.detectChanges();
114114
expect(menuItem.getLabel()).toEqual('Click me!');
115115
},
@@ -126,7 +126,7 @@ describe('MenuItem', () => {
126126
() => {
127127
const fixture = createComponent(MenuItemWithMultipleNestings);
128128
expect(menuItem.getLabel()).toEqual('unicorn Click menume!');
129-
fixture.componentInstance.typeahead = 'Click me!';
129+
fixture.componentInstance.typeaheadLabel = 'Click me!';
130130
fixture.detectChanges();
131131
expect(menuItem.getLabel()).toEqual('Click me!');
132132
},
@@ -141,7 +141,7 @@ class SingleMenuItem {}
141141

142142
@Component({
143143
template: `
144-
<button cdkMenuItem [cdkMenuItemTypeahead]="typeahead">
144+
<button cdkMenuItem [cdkMenuitemTypeaheadLabel]="typeahead">
145145
<mat-icon>unicorn</mat-icon>
146146
Click me!
147147
</button>
@@ -153,7 +153,7 @@ class MenuItemWithIcon {
153153

154154
@Component({
155155
template: `
156-
<button cdkMenuItem [cdkMenuItemTypeahead]="typeahead">
156+
<button cdkMenuItem [cdkMenuitemTypeaheadLabel]="typeahead">
157157
<div class="material-icons">unicorn</div>
158158
Click me!
159159
</button>
@@ -170,7 +170,7 @@ class MenuItemWithBoldElement {}
170170

171171
@Component({
172172
template: `
173-
<button cdkMenuItem [cdkMenuItemTypeahead]="typeahead">
173+
<button cdkMenuItem [cdkMenuitemTypeaheadLabel]="typeahead">
174174
<div>
175175
<div class="material-icons">unicorn</div>
176176
<div>

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

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
} from '@angular/core';
2121
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
2222
import {FocusableOption} from '@angular/cdk/a11y';
23-
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
23+
import {ENTER, hasModifierKey, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
2424
import {Directionality} from '@angular/cdk/bidi';
2525
import {fromEvent, Subject} from 'rxjs';
2626
import {filter, takeUntil} from 'rxjs/operators';
@@ -64,7 +64,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
6464
* The text used to locate this item during menu typeahead. If not specified,
6565
* the `textContent` of the item will be used.
6666
*/
67-
@Input('cdkMenuItemTypeahead') typeahead: string;
67+
@Input('cdkMenuitemTypeaheadLabel') typeaheadLabel: string | null;
6868

6969
/**
7070
* If this MenuItem is a regular MenuItem, outputs when it is triggered by a keyboard or mouse
@@ -113,6 +113,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
113113

114114
ngOnDestroy() {
115115
this.destroyed.next();
116+
this.destroyed.complete();
116117
}
117118

118119
/** Place focus on the element. */
@@ -156,7 +157,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
156157

157158
/** Get the label for this element which is required by the FocusableOption interface. */
158159
getLabel(): string {
159-
return this.typeahead || this._elementRef.nativeElement.textContent?.trim() || '';
160+
return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || '';
160161
}
161162

162163
/** Reset the tabindex to -1. */
@@ -191,26 +192,32 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
191192
switch (event.keyCode) {
192193
case SPACE:
193194
case ENTER:
194-
event.preventDefault();
195-
this.trigger({keepOpen: event.keyCode === SPACE && !this.closeOnSpacebarTrigger});
195+
if (!hasModifierKey(event)) {
196+
event.preventDefault();
197+
this.trigger({keepOpen: event.keyCode === SPACE && !this.closeOnSpacebarTrigger});
198+
}
196199
break;
197200

198201
case RIGHT_ARROW:
199-
if (this._parentMenu && this._isParentVertical()) {
200-
if (this._dir?.value !== 'rtl') {
201-
this._forwardArrowPressed(event);
202-
} else {
203-
this._backArrowPressed(event);
202+
if (!hasModifierKey(event)) {
203+
if (this._parentMenu && this._isParentVertical()) {
204+
if (this._dir?.value !== 'rtl') {
205+
this._forwardArrowPressed(event);
206+
} else {
207+
this._backArrowPressed(event);
208+
}
204209
}
205210
}
206211
break;
207212

208213
case LEFT_ARROW:
209-
if (this._parentMenu && this._isParentVertical()) {
210-
if (this._dir?.value !== 'rtl') {
211-
this._backArrowPressed(event);
212-
} else {
213-
this._forwardArrowPressed(event);
214+
if (!hasModifierKey(event)) {
215+
if (this._parentMenu && this._isParentVertical()) {
216+
if (this._dir?.value !== 'rtl') {
217+
this._backArrowPressed(event);
218+
} else {
219+
this._forwardArrowPressed(event);
220+
}
214221
}
215222
}
216223
break;

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

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ import {
2525
STANDARD_DROPDOWN_ADJACENT_POSITIONS,
2626
STANDARD_DROPDOWN_BELOW_POSITIONS,
2727
} from '@angular/cdk/overlay';
28-
import {DOWN_ARROW, ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE, UP_ARROW} from '@angular/cdk/keycodes';
28+
import {
29+
DOWN_ARROW,
30+
ENTER,
31+
hasModifierKey,
32+
LEFT_ARROW,
33+
RIGHT_ARROW,
34+
SPACE,
35+
UP_ARROW,
36+
} from '@angular/cdk/keycodes';
2937
import {fromEvent} from 'rxjs';
3038
import {filter, takeUntil} from 'rxjs/operators';
3139
import {CDK_MENU, Menu} from './menu-interface';
@@ -130,35 +138,43 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnDestroy {
130138
switch (keyCode) {
131139
case SPACE:
132140
case ENTER:
133-
event.preventDefault();
134-
this.toggle();
135-
this.childMenu?.focusFirstItem('keyboard');
141+
if (!hasModifierKey(event)) {
142+
event.preventDefault();
143+
this.toggle();
144+
this.childMenu?.focusFirstItem('keyboard');
145+
}
136146
break;
137147

138148
case RIGHT_ARROW:
139-
if (this._parentMenu && isParentVertical && this._directionality?.value !== 'rtl') {
140-
event.preventDefault();
141-
this.open();
142-
this.childMenu?.focusFirstItem('keyboard');
149+
if (!hasModifierKey(event)) {
150+
if (this._parentMenu && isParentVertical && this._directionality?.value !== 'rtl') {
151+
event.preventDefault();
152+
this.open();
153+
this.childMenu?.focusFirstItem('keyboard');
154+
}
143155
}
144156
break;
145157

146158
case LEFT_ARROW:
147-
if (this._parentMenu && isParentVertical && this._directionality?.value === 'rtl') {
148-
event.preventDefault();
149-
this.open();
150-
this.childMenu?.focusFirstItem('keyboard');
159+
if (!hasModifierKey(event)) {
160+
if (this._parentMenu && isParentVertical && this._directionality?.value === 'rtl') {
161+
event.preventDefault();
162+
this.open();
163+
this.childMenu?.focusFirstItem('keyboard');
164+
}
151165
}
152166
break;
153167

154168
case DOWN_ARROW:
155169
case UP_ARROW:
156-
if (!isParentVertical) {
157-
event.preventDefault();
158-
this.open();
159-
keyCode === DOWN_ARROW
160-
? this.childMenu?.focusFirstItem('keyboard')
161-
: this.childMenu?.focusLastItem('keyboard');
170+
if (!hasModifierKey(event)) {
171+
if (!isParentVertical) {
172+
event.preventDefault();
173+
this.open();
174+
keyCode === DOWN_ARROW
175+
? this.childMenu?.focusFirstItem('keyboard')
176+
: this.childMenu?.focusLastItem('keyboard');
177+
}
162178
}
163179
break;
164180
}

src/cdk-experimental/menu/menu.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,11 @@ export class CdkMenu extends CdkMenuBase implements AfterContentInit, OnDestroy
103103
switch (event.keyCode) {
104104
case LEFT_ARROW:
105105
case RIGHT_ARROW:
106-
event.preventDefault();
107-
keyManager.setFocusOrigin('keyboard');
108-
keyManager.onKeydown(event);
106+
if (!hasModifierKey(event)) {
107+
event.preventDefault();
108+
keyManager.setFocusOrigin('keyboard');
109+
keyManager.onKeydown(event);
110+
}
109111
break;
110112

111113
case ESCAPE:
@@ -119,7 +121,9 @@ export class CdkMenu extends CdkMenuBase implements AfterContentInit, OnDestroy
119121
break;
120122

121123
case TAB:
122-
this.menuStack.closeAll({focusParentTrigger: true});
124+
if (!hasModifierKey(event, 'altKey', 'metaKey', 'ctrlKey')) {
125+
this.menuStack.closeAll({focusParentTrigger: true});
126+
}
123127
break;
124128

125129
default:

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ function removeIcons(element: Element) {
3939
})
4040
export class MatMenuBarItem extends CdkMenuItem {
4141
override getLabel(): string {
42-
if (this.typeahead !== undefined) {
43-
return this.typeahead;
42+
if (this.typeaheadLabel !== undefined) {
43+
return this.typeaheadLabel || '';
4444
}
4545
const clone = this._elementRef.nativeElement.cloneNode(true) as Element;
4646
removeIcons(clone);

0 commit comments

Comments
 (0)