mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
committed by
Jeremy Elbourn
parent
afed81884f
commit
497a3c147f
@@ -28,3 +28,11 @@ A basic tab group would have the following markup.
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `selectedIndex` | `number` | The index of the currently active tab. |
|
||||
| `focusIndex` | `number` | The index of the currently active tab. |
|
||||
|
||||
### Events
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `focusChange` | `Event` | Fired when focus changes from one label to another |
|
||||
| `selectedChange` | `Event` | Fired when the selected tab changes |
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
(keydown.arrowLeft)="focusPreviousTab()"
|
||||
(keydown.enter)="selectedIndex = focusIndex">
|
||||
<div class="md-tab-label" role="tab" md-tab-label-wrapper
|
||||
*ngFor="let label of labels; let i = index"
|
||||
*ngFor="let tab of tabs; let i = index"
|
||||
[id]="getTabLabelId(i)"
|
||||
[tabIndex]="selectedIndex == i ? 0 : -1"
|
||||
[attr.aria-controls]="getTabContentId(i)"
|
||||
[attr.aria-selected]="selectedIndex == i"
|
||||
[class.md-active]="selectedIndex == i"
|
||||
(click)="focusIndex = selectedIndex = i">
|
||||
<template [portalHost]="label"></template>
|
||||
<template [portalHost]="tab.label"></template>
|
||||
</div>
|
||||
<md-ink-bar></md-ink-bar>
|
||||
</div>
|
||||
<div class="md-tab-body-wrapper">
|
||||
<div class="md-tab-body"
|
||||
*ngFor="let content of contents; let i = index"
|
||||
*ngFor="let tab of tabs; let i = index"
|
||||
[id]="getTabContentId(i)"
|
||||
[class.md-active]="selectedIndex == i"
|
||||
[attr.aria-labelledby]="getTabLabelId(i)">
|
||||
<template role="tabpanel" [portalHost]="content" *ngIf="selectedIndex == i"></template>
|
||||
<template role="tabpanel" [portalHost]="tab.content" *ngIf="selectedIndex == i"></template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,9 @@ import {
|
||||
beforeEach,
|
||||
inject,
|
||||
describe,
|
||||
async
|
||||
async,
|
||||
fakeAsync,
|
||||
tick
|
||||
} from '@angular/core/testing';
|
||||
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
|
||||
import {MD_TABS_DIRECTIVES, MdTabGroup} from './tabs';
|
||||
@@ -47,44 +49,80 @@ describe('MdTabGroup', () => {
|
||||
checkSelectedIndex(2);
|
||||
});
|
||||
|
||||
it('should cycle through tab focus with focusNextTab/focusPreviousTab functions', () => {
|
||||
it('should cycle through tab focus with focusNextTab/focusPreviousTab functions',
|
||||
fakeAsync(() => {
|
||||
let testComponent = fixture.componentInstance;
|
||||
let tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance;
|
||||
|
||||
spyOn(testComponent, 'handleFocus').and.callThrough();
|
||||
fixture.detectChanges();
|
||||
|
||||
tabComponent.focusIndex = 0;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(0);
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(1);
|
||||
expect(testComponent.focusEvent.index).toBe(0);
|
||||
|
||||
tabComponent.focusNextTab();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(1);
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(2);
|
||||
expect(testComponent.focusEvent.index).toBe(1);
|
||||
|
||||
tabComponent.focusNextTab();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(2);
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(3);
|
||||
expect(testComponent.focusEvent.index).toBe(2);
|
||||
|
||||
tabComponent.focusNextTab();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(2); // should stop at 2
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(3);
|
||||
expect(testComponent.focusEvent.index).toBe(2);
|
||||
|
||||
tabComponent.focusPreviousTab();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(1);
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(4);
|
||||
expect(testComponent.focusEvent.index).toBe(1);
|
||||
|
||||
tabComponent.focusPreviousTab();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(0);
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(5);
|
||||
expect(testComponent.focusEvent.index).toBe(0);
|
||||
|
||||
tabComponent.focusPreviousTab();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(tabComponent.focusIndex).toBe(0); // should stop at 0
|
||||
});
|
||||
expect(testComponent.handleFocus).toHaveBeenCalledTimes(5);
|
||||
expect(testComponent.focusEvent.index).toBe(0);
|
||||
}));
|
||||
|
||||
it('should change tabs based on selectedIndex', fakeAsync(() => {
|
||||
let component = fixture.componentInstance;
|
||||
let tabComponent = fixture.debugElement.query(By.css('md-tab-group')).componentInstance;
|
||||
|
||||
spyOn(component, 'handleSelection').and.callThrough();
|
||||
|
||||
it('should change tabs based on selectedIndex', () => {
|
||||
let component = fixture.debugElement.componentInstance;
|
||||
checkSelectedIndex(1);
|
||||
|
||||
component.selectedIndex = 2;
|
||||
tabComponent.selectedIndex = 2;
|
||||
|
||||
checkSelectedIndex(2);
|
||||
});
|
||||
tick();
|
||||
|
||||
expect(component.handleSelection).toHaveBeenCalledTimes(1);
|
||||
expect(component.selectEvent.index).toBe(2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('async tabs', () => {
|
||||
@@ -131,7 +169,10 @@ describe('MdTabGroup', () => {
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template: `
|
||||
<md-tab-group class="tab-group" [selectedIndex]="selectedIndex">
|
||||
<md-tab-group class="tab-group"
|
||||
[selectedIndex]="selectedIndex"
|
||||
(focusChange)="handleFocus($event)"
|
||||
(selectChange)="handleSelection($event)">
|
||||
<md-tab>
|
||||
<template md-tab-label>Tab One</template>
|
||||
<template md-tab-content>Tab one content</template>
|
||||
@@ -150,6 +191,14 @@ describe('MdTabGroup', () => {
|
||||
})
|
||||
class SimpleTabsTestApp {
|
||||
selectedIndex: number = 1;
|
||||
focusEvent: any;
|
||||
selectEvent: any;
|
||||
handleFocus(event: any) {
|
||||
this.focusEvent = event;
|
||||
}
|
||||
handleSelection(event: any) {
|
||||
this.selectEvent = event;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import {Component, Input, ViewChildren, NgZone} from '@angular/core';
|
||||
import {
|
||||
ContentChild,
|
||||
Directive,
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
ViewChildren,
|
||||
NgZone,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import {QueryList} from '@angular/core';
|
||||
import {ContentChildren} from '@angular/core';
|
||||
import {PortalHostDirective} from '@angular2-material/core/portal/portal-directives';
|
||||
@@ -6,10 +15,25 @@ import {MdTabLabel} from './tab-label';
|
||||
import {MdTabContent} from './tab-content';
|
||||
import {MdTabLabelWrapper} from './tab-label-wrapper';
|
||||
import {MdInkBar} from './ink-bar';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
/** Used to generate unique ID's for each tab component */
|
||||
let nextId = 0;
|
||||
|
||||
/** A simple change event emitted on focus or selection changes. */
|
||||
export class MdTabChangeEvent {
|
||||
index: number;
|
||||
tab: MdTab;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'md-tab'
|
||||
})
|
||||
export class MdTab {
|
||||
@ContentChild(MdTabLabel) label: MdTabLabel;
|
||||
@ContentChild(MdTabContent) content: MdTabContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Material design tab-group component. Supports basic tab pairs (label + content) and includes
|
||||
* animated ink-bar, keyboard navigation, and screen reader.
|
||||
@@ -24,15 +48,35 @@ let nextId = 0;
|
||||
})
|
||||
export class MdTabGroup {
|
||||
/** @internal */
|
||||
@ContentChildren(MdTabLabel) labels: QueryList<MdTabLabel>;
|
||||
|
||||
/** @internal */
|
||||
@ContentChildren(MdTabContent) contents: QueryList<MdTabContent>;
|
||||
@ContentChildren(MdTab) tabs: QueryList<MdTab>;
|
||||
|
||||
@ViewChildren(MdTabLabelWrapper) private _labelWrappers: QueryList<MdTabLabelWrapper>;
|
||||
@ViewChildren(MdInkBar) private _inkBar: QueryList<MdInkBar>;
|
||||
|
||||
@Input() selectedIndex: number = 0;
|
||||
private _isInitialized: boolean = false;
|
||||
|
||||
private _selectedIndex: number = 0;
|
||||
@Input()
|
||||
set selectedIndex(value: number) {
|
||||
this._selectedIndex = value;
|
||||
|
||||
if (this._isInitialized) {
|
||||
this._onSelectChange.emit(this._createChangeEvent(value));
|
||||
}
|
||||
}
|
||||
get selectedIndex(): number {
|
||||
return this._selectedIndex;
|
||||
}
|
||||
|
||||
private _onFocusChange: EventEmitter<MdTabChangeEvent> = new EventEmitter<MdTabChangeEvent>();
|
||||
@Output('focusChange') get focusChange(): Observable<MdTabChangeEvent> {
|
||||
return this._onFocusChange.asObservable();
|
||||
}
|
||||
|
||||
private _onSelectChange: EventEmitter<MdTabChangeEvent> = new EventEmitter<MdTabChangeEvent>();
|
||||
@Output('selectChange') get selectChange(): Observable<MdTabChangeEvent> {
|
||||
return this._onSelectChange.asObservable();
|
||||
}
|
||||
|
||||
private _focusIndex: number = 0;
|
||||
private _groupId: number;
|
||||
@@ -52,6 +96,7 @@ export class MdTabGroup {
|
||||
this._updateInkBar();
|
||||
});
|
||||
});
|
||||
this._isInitialized = true;
|
||||
}
|
||||
|
||||
/** Tells the ink-bar to align itself to the current label wrapper */
|
||||
@@ -77,11 +122,25 @@ export class MdTabGroup {
|
||||
/** When the focus index is set, we must manually send focus to the correct label */
|
||||
set focusIndex(value: number) {
|
||||
this._focusIndex = value;
|
||||
|
||||
if (this._isInitialized) {
|
||||
this._onFocusChange.emit(this._createChangeEvent(value));
|
||||
}
|
||||
|
||||
if (this._labelWrappers && this._labelWrappers.length) {
|
||||
this._labelWrappers.toArray()[value].focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _createChangeEvent(index: number): MdTabChangeEvent {
|
||||
const event = new MdTabChangeEvent;
|
||||
event.index = index;
|
||||
if (this.tabs && this.tabs.length) {
|
||||
event.tab = this.tabs.toArray()[index];
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique id for each tab label element
|
||||
* @internal
|
||||
@@ -113,4 +172,4 @@ export class MdTabGroup {
|
||||
}
|
||||
}
|
||||
|
||||
export const MD_TABS_DIRECTIVES = [MdTabGroup, MdTabLabel, MdTabContent];
|
||||
export const MD_TABS_DIRECTIVES = [MdTabGroup, MdTabLabel, MdTabContent, MdTab];
|
||||
|
||||
Reference in New Issue
Block a user