feat(menu): support typeahead focus (#7385)

This commit is contained in:
Kristiyan Kostadinov
2017-10-04 23:53:14 +02:00
committed by Kara
parent 31e97756dd
commit f0d20ca6cf
3 changed files with 51 additions and 3 deletions
+1 -1
View File
@@ -150,7 +150,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy {
@Inject(MAT_MENU_DEFAULT_OPTIONS) private _defaultOptions: MatMenuDefaultOptions) { }
ngAfterContentInit() {
this._keyManager = new FocusKeyManager<MatMenuItem>(this.items).withWrap();
this._keyManager = new FocusKeyManager<MatMenuItem>(this.items).withWrap().withTypeAhead();
this._tabSubscription = this._keyManager.tabOut.subscribe(() => this.close.emit('keydown'));
}
+21
View File
@@ -97,5 +97,26 @@ export class MatMenuItem extends _MatMenuItemMixinBase implements FocusableOptio
}
}
/** Gets the label to be used when determining whether the option should be focused. */
getLabel(): string {
const element: HTMLElement = this._elementRef.nativeElement;
let output = '';
if (element.childNodes) {
const length = element.childNodes.length;
// Go through all the top-level text nodes and extract their text.
// We skip anything that's not a text node to prevent the text from
// being thrown off by something like an icon.
for (let i = 0; i < length; i++) {
if (element.childNodes[i].nodeType === Node.TEXT_NODE) {
output += element.childNodes[i].textContent;
}
}
}
return output.trim();
}
}
+29 -2
View File
@@ -9,6 +9,8 @@ import {
Output,
TemplateRef,
ViewChild,
ViewChildren,
QueryList,
} from '@angular/core';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {OverlayContainer} from '@angular/cdk/overlay';
@@ -21,6 +23,7 @@ import {
MatMenuTrigger,
MenuPositionX,
MenuPositionY,
MatMenuItem,
} from './index';
import {MENU_PANEL_TOP_PADDING} from './menu-trigger';
import {extendObject} from '@angular/material/core';
@@ -49,7 +52,8 @@ describe('MatMenu', () => {
CustomMenu,
NestedMenu,
NestedMenuCustomElevation,
NestedMenuRepeater
NestedMenuRepeater,
FakeIcon
],
providers: [
{provide: OverlayContainer, useFactory: () => {
@@ -175,6 +179,18 @@ describe('MatMenu', () => {
expect(fixture.destroy.bind(fixture)).not.toThrow();
});
it('should be able to extract the menu item text', () => {
const fixture = TestBed.createComponent(SimpleMenu);
fixture.detectChanges();
expect(fixture.componentInstance.items.first.getLabel()).toBe('Item');
});
it('should filter out non-text nodes when figuring out the label', () => {
const fixture = TestBed.createComponent(SimpleMenu);
fixture.detectChanges();
expect(fixture.componentInstance.items.last.getLabel()).toBe('Item with an icon');
});
describe('positions', () => {
let fixture: ComponentFixture<PositionedMenu>;
let panel: HTMLElement;
@@ -1070,7 +1086,7 @@ describe('MatMenu default overrides', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatMenuModule, NoopAnimationsModule],
declarations: [SimpleMenu],
declarations: [SimpleMenu, FakeIcon],
providers: [{
provide: MAT_MENU_DEFAULT_OPTIONS,
useValue: {overlapTrigger: false, xPosition: 'before', yPosition: 'above'},
@@ -1095,6 +1111,10 @@ describe('MatMenu default overrides', () => {
<mat-menu class="custom-one custom-two" #menu="matMenu" (close)="closeCallback($event)">
<button mat-menu-item> Item </button>
<button mat-menu-item disabled> Disabled </button>
<button mat-menu-item>
<fake-icon>unicorn</fake-icon>
Item with an icon
</button>
</mat-menu>
`
})
@@ -1102,6 +1122,7 @@ class SimpleMenu {
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
@ViewChild('triggerEl') triggerEl: ElementRef;
@ViewChild(MatMenu) menu: MatMenu;
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
closeCallback = jasmine.createSpy('menu closed callback');
}
@@ -1284,3 +1305,9 @@ class NestedMenuRepeater {
items = ['one', 'two', 'three'];
}
@Component({
selector: 'fake-icon',
template: '<ng-content></ng-content>'
})
class FakeIcon { }