mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
chore(tabs): move tabs to smart portal directive. (#7266)
This commit is contained in:
@@ -9,11 +9,14 @@
|
||||
export * from './tabs-module';
|
||||
export * from './tab-group';
|
||||
export {MatInkBar} from './ink-bar';
|
||||
export {MatTabBody, MatTabBodyOriginState, MatTabBodyPositionState} from './tab-body';
|
||||
export {
|
||||
MatTabBody,
|
||||
MatTabBodyOriginState,
|
||||
MatTabBodyPositionState,
|
||||
MatTabBodyPortal
|
||||
} from './tab-body';
|
||||
export {MatTabHeader, ScrollDirection} from './tab-header';
|
||||
export {MatTabLabelWrapper} from './tab-label-wrapper';
|
||||
export {MatTab} from './tab';
|
||||
export {MatTabLabel} from './tab-label';
|
||||
export {MatTabNav, MatTabLink} from './tab-nav-bar/index';
|
||||
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
[@translateTab]="_position"
|
||||
(@translateTab.start)="_onTranslateTabStarted($event)"
|
||||
(@translateTab.done)="_onTranslateTabComplete($event)">
|
||||
<ng-template cdkPortalOutlet></ng-template>
|
||||
<ng-template matTabBodyHost></ng-template>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,10 @@ import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {PortalModule, TemplatePortal} from '@angular/cdk/portal';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {async, ComponentFixture, fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {MatRippleModule} from '@angular/material/core';
|
||||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MatTabBody} from './tab-body';
|
||||
import {MatTabBody, MatTabBodyPortal} from './tab-body';
|
||||
|
||||
|
||||
describe('MatTabBody', () => {
|
||||
@@ -17,6 +17,7 @@ describe('MatTabBody', () => {
|
||||
imports: [CommonModule, PortalModule, MatRippleModule, NoopAnimationsModule],
|
||||
declarations: [
|
||||
MatTabBody,
|
||||
MatTabBodyPortal,
|
||||
SimpleTabBodyApp,
|
||||
],
|
||||
providers: [
|
||||
@@ -145,30 +146,6 @@ describe('MatTabBody', () => {
|
||||
expect(fixture.componentInstance.tabBody._position).toBe('left');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on centered', () => {
|
||||
let fixture: ComponentFixture<SimpleTabBodyApp>;
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture = TestBed.createComponent(SimpleTabBodyApp);
|
||||
}));
|
||||
|
||||
it('should attach the content when centered and detach when not', fakeAsync(() => {
|
||||
fixture.componentInstance.position = 1;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.tabBody._portalOutlet.hasAttached()).toBe(false);
|
||||
|
||||
fixture.componentInstance.position = 0;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.tabBody._portalOutlet.hasAttached()).toBe(true);
|
||||
|
||||
fixture.componentInstance.position = 1;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks(); // Finish animation and let it detach in animation done handler
|
||||
expect(fixture.componentInstance.tabBody._portalOutlet.hasAttached()).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
+55
-24
@@ -7,17 +7,21 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ViewChild,
|
||||
Component,
|
||||
Input,
|
||||
Inject,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ElementRef,
|
||||
Directive,
|
||||
Optional,
|
||||
AfterViewChecked,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
ComponentFactoryResolver,
|
||||
ViewContainerRef,
|
||||
forwardRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
@@ -29,7 +33,10 @@ import {
|
||||
} from '@angular/animations';
|
||||
import {TemplatePortal, CdkPortalOutlet} from '@angular/cdk/portal';
|
||||
import {Directionality, Direction} from '@angular/cdk/bidi';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
/** Workaround for https://github.com/angular/angular/issues/17849 */
|
||||
export const _MatTabBodyPortalBaseClass = CdkPortalOutlet;
|
||||
|
||||
/**
|
||||
* These position states are used internally as animation states for the tab body. Setting the
|
||||
@@ -52,6 +59,44 @@ export type MatTabBodyPositionState =
|
||||
*/
|
||||
export type MatTabBodyOriginState = 'left' | 'right';
|
||||
|
||||
/**
|
||||
* The portal host directive for the contents of the tab.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[matTabBodyHost]'
|
||||
})
|
||||
export class MatTabBodyPortal extends _MatTabBodyPortalBaseClass implements OnInit, OnDestroy {
|
||||
/** A subscription to events for when the tab body begins centering. */
|
||||
private _centeringSub: Subscription;
|
||||
|
||||
constructor(
|
||||
_componentFactoryResolver: ComponentFactoryResolver,
|
||||
_viewContainerRef: ViewContainerRef,
|
||||
@Inject(forwardRef(() => MatTabBody)) private _host: MatTabBody) {
|
||||
super(_componentFactoryResolver, _viewContainerRef);
|
||||
}
|
||||
|
||||
/** Set initial visibility or set up subscription for changing visibility. */
|
||||
ngOnInit(): void {
|
||||
if (this._host._isCenterPosition(this._host._position)) {
|
||||
this.attach(this._host._content);
|
||||
} else {
|
||||
this._centeringSub = this._host._beforeCentering.subscribe(() => {
|
||||
this.attach(this._host._content);
|
||||
this._centeringSub.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Clean up subscription if necessary. */
|
||||
ngOnDestroy(): void {
|
||||
if (this._centeringSub && !this._centeringSub.closed) {
|
||||
this._centeringSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the contents of a tab.
|
||||
* @docs-private
|
||||
@@ -86,13 +131,13 @@ export type MatTabBodyOriginState = 'left' | 'right';
|
||||
])
|
||||
]
|
||||
})
|
||||
export class MatTabBody implements OnInit, AfterViewChecked {
|
||||
/** The portal outlet inside of this container into which the tab body content will be loaded. */
|
||||
@ViewChild(CdkPortalOutlet) _portalOutlet: CdkPortalOutlet;
|
||||
|
||||
export class MatTabBody implements OnInit {
|
||||
/** Event emitted when the tab begins to animate towards the center as the active tab. */
|
||||
@Output() _onCentering: EventEmitter<number> = new EventEmitter<number>();
|
||||
|
||||
/** Event emitted before the centering of the tab begins. */
|
||||
@Output() _beforeCentering: EventEmitter<number> = new EventEmitter<number>();
|
||||
|
||||
/** Event emitted when the tab completes its animation towards the center. */
|
||||
@Output() _onCentered: EventEmitter<void> = new EventEmitter<void>(true);
|
||||
|
||||
@@ -139,28 +184,14 @@ export class MatTabBody implements OnInit, AfterViewChecked {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After the view has been set, check if the tab content is set to the center and attach the
|
||||
* content if it is not already attached.
|
||||
*/
|
||||
ngAfterViewChecked() {
|
||||
if (this._isCenterPosition(this._position) && !this._portalOutlet.hasAttached()) {
|
||||
this._portalOutlet.attach(this._content);
|
||||
}
|
||||
}
|
||||
|
||||
_onTranslateTabStarted(e: AnimationEvent) {
|
||||
_onTranslateTabStarted(e: AnimationEvent): void {
|
||||
if (this._isCenterPosition(e.toState)) {
|
||||
this._beforeCentering.emit();
|
||||
this._onCentering.emit(this._elementRef.nativeElement.clientHeight);
|
||||
}
|
||||
}
|
||||
|
||||
_onTranslateTabComplete(e: AnimationEvent) {
|
||||
// If the end state is that the tab is not centered, then detach the content.
|
||||
if (!this._isCenterPosition(e.toState) && !this._isCenterPosition(this._position)) {
|
||||
this._portalOutlet.detach();
|
||||
}
|
||||
|
||||
_onTranslateTabComplete(e: AnimationEvent): void {
|
||||
// If the transition to the center is complete, emit an event.
|
||||
if (this._isCenterPosition(e.toState) && this._isCenterPosition(this._position)) {
|
||||
this._onCentered.emit();
|
||||
@@ -173,7 +204,7 @@ export class MatTabBody implements OnInit, AfterViewChecked {
|
||||
}
|
||||
|
||||
/** Whether the provided position state is considered center, regardless of origin. */
|
||||
private _isCenterPosition(position: MatTabBodyPositionState|string): boolean {
|
||||
_isCenterPosition(position: MatTabBodyPositionState|string): boolean {
|
||||
return position == 'center' ||
|
||||
position == 'left-origin-center' ||
|
||||
position == 'right-origin-center';
|
||||
|
||||
@@ -26,15 +26,22 @@ describe('MatTabGroup', () => {
|
||||
|
||||
describe('basic behavior', () => {
|
||||
let fixture: ComponentFixture<SimpleTabsTestApp>;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SimpleTabsTestApp);
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it('should default to the first tab', () => {
|
||||
checkSelectedIndex(1, fixture);
|
||||
});
|
||||
|
||||
it('will properly load content on first change detection pass', () => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('.mat-tab-body')[1].querySelectorAll('span').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should change selected index on click', () => {
|
||||
let component = fixture.debugElement.componentInstance;
|
||||
component.selectedIndex = 0;
|
||||
@@ -318,23 +325,26 @@ describe('MatTabGroup', () => {
|
||||
fixture.debugElement.query(By.directive(MatTabGroup)).componentInstance as MatTabGroup;
|
||||
});
|
||||
|
||||
it('should support a tab-group with the simple api', () => {
|
||||
it('should support a tab-group with the simple api', async(() => {
|
||||
expect(getSelectedLabel(fixture).textContent).toMatch('Junk food');
|
||||
expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries');
|
||||
|
||||
tabGroup.selectedIndex = 2;
|
||||
fixture.detectChanges();
|
||||
// Use whenStable to wait for async observables and change detection to run in content.
|
||||
fixture.whenStable().then(() => {
|
||||
|
||||
expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
|
||||
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
|
||||
expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
|
||||
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
|
||||
|
||||
fixture.componentInstance.otherLabel = 'Chips';
|
||||
fixture.componentInstance.otherContent = 'Salt, vinegar';
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.otherLabel = 'Chips';
|
||||
fixture.componentInstance.otherContent = 'Salt, vinegar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
|
||||
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
|
||||
});
|
||||
expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
|
||||
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support @ViewChild in the tab content', () => {
|
||||
expect(fixture.componentInstance.legumes).toBeTruthy();
|
||||
@@ -415,7 +425,7 @@ describe('nested MatTabGroup with enabled animations', () => {
|
||||
</mat-tab>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>Tab Two</ng-template>
|
||||
Tab two content
|
||||
<span>Tab </span><span>two</span><span>content</span>
|
||||
</mat-tab>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>Tab Three</ng-template>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {NgModule} from '@angular/core';
|
||||
import {MatCommonModule, MatRippleModule} from '@angular/material/core';
|
||||
import {MatInkBar} from './ink-bar';
|
||||
import {MatTab} from './tab';
|
||||
import {MatTabBody} from './tab-body';
|
||||
import {MatTabBody, MatTabBodyPortal} from './tab-body';
|
||||
import {MatTabGroup} from './tab-group';
|
||||
import {MatTabHeader} from './tab-header';
|
||||
import {MatTabLabel} from './tab-label';
|
||||
@@ -49,6 +49,7 @@ import {MatTabLink, MatTabNav} from './tab-nav-bar/tab-nav-bar';
|
||||
MatTabNav,
|
||||
MatTabLink,
|
||||
MatTabBody,
|
||||
MatTabBodyPortal,
|
||||
MatTabHeader
|
||||
],
|
||||
providers: [VIEWPORT_RULER_PROVIDER],
|
||||
|
||||
Reference in New Issue
Block a user